From: Sascha Wildner Date: Tue, 25 Sep 2012 21:11:40 +0000 (+0200) Subject: usb4bsd: Bring in FreeBSD's libusbhid, usbhidctl and USB kernel code. X-Git-Tag: v3.4.0rc~998^2~18 X-Git-Url: https://gitweb.dragonflybsd.org/dragonfly.git/commitdiff_plain/12bd3c8bdb153b265dcce6f8cde3b86911661898 usb4bsd: Bring in FreeBSD's libusbhid, usbhidctl and USB kernel code. In order to make it live peacefully along with our old USB code, name all directories with new USB code *u4b* instead of *usb*. This is FreeBSD SVN r231881. Submitted-by: Markus Pfeiffer --- diff --git a/lib/libu4bhid/Makefile b/lib/libu4bhid/Makefile new file mode 100644 index 0000000000..7dba7ffc91 --- /dev/null +++ b/lib/libu4bhid/Makefile @@ -0,0 +1,26 @@ +# $NetBSD: Makefile,v 1.5 1999/07/23 09:44:38 mrg Exp $ +# $FreeBSD$ + +LIB= usbhid +MAN= usbhid.3 + +SHLIB_MAJOR= 4 + +MLINKS= usbhid.3 libusbhid.3 usbhid.3 hid_get_report_desc.3 \ + usbhid.3 hid_dispose_report_desc.3 \ + usbhid.3 hid_start_parse.3 usbhid.3 hid_end_parse.3 \ + usbhid.3 hid_get_item.3 usbhid.3 hid_report_size.3 \ + usbhid.3 hid_locate.3 \ + usbhid.3 hid_usage_page.3 usbhid.3 hid_usage_in_page.3 \ + usbhid.3 hid_init.3 \ + usbhid.3 hid_get_data.3 usbhid.3 hid_set_data.3 + +SRCS= descr.c descr_compat.c parse.c usage.c data.c + +INCS= usbhid.h + +.if defined(COMPAT_32BIT) +CFLAGS+= -DCOMPAT_32BIT +.endif + +.include diff --git a/lib/libu4bhid/data.c b/lib/libu4bhid/data.c new file mode 100644 index 0000000000..f607737522 --- /dev/null +++ b/lib/libu4bhid/data.c @@ -0,0 +1,143 @@ +/* $NetBSD: data.c,v 1.8 2000/04/02 11:10:53 augustss Exp $ */ + +/* + * Copyright (c) 1999 Lennart Augustsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include "usbhid.h" +#include "usbvar.h" + +int32_t +hid_get_data(const void *p, const hid_item_t *h) +{ + const uint8_t *buf; + uint32_t hpos; + uint32_t hsize; + uint32_t data; + int i, end, offs; + + buf = p; + + /* Skip report ID byte. */ + if (h->report_ID > 0) + buf++; + + hpos = h->pos; /* bit position of data */ + hsize = h->report_size; /* bit length of data */ + + /* Range check and limit */ + if (hsize == 0) + return (0); + if (hsize > 32) + hsize = 32; + + offs = hpos / 8; + end = (hpos + hsize) / 8 - offs; + data = 0; + for (i = 0; i <= end; i++) + data |= buf[offs + i] << (i*8); + + /* Correctly shift down data */ + data >>= hpos % 8; + hsize = 32 - hsize; + + /* Mask and sign extend in one */ + if ((h->logical_minimum < 0) || (h->logical_maximum < 0)) + data = (int32_t)((int32_t)data << hsize) >> hsize; + else + data = (uint32_t)((uint32_t)data << hsize) >> hsize; + + return (data); +} + +void +hid_set_data(void *p, const hid_item_t *h, int32_t data) +{ + uint8_t *buf; + uint32_t hpos; + uint32_t hsize; + uint32_t mask; + int i; + int end; + int offs; + + buf = p; + + /* Set report ID byte. */ + if (h->report_ID > 0) + *buf++ = h->report_ID & 0xff; + + hpos = h->pos; /* bit position of data */ + hsize = h->report_size; /* bit length of data */ + + if (hsize != 32) { + mask = (1 << hsize) - 1; + data &= mask; + } else + mask = ~0; + + data <<= (hpos % 8); + mask <<= (hpos % 8); + mask = ~mask; + + offs = hpos / 8; + end = (hpos + hsize) / 8 - offs; + + for (i = 0; i <= end; i++) + buf[offs + i] = (buf[offs + i] & (mask >> (i*8))) | + ((data >> (i*8)) & 0xff); +} + +int +hid_get_report(int fd, enum hid_kind k, unsigned char *data, unsigned int size) +{ + struct usb_gen_descriptor ugd; + + memset(&ugd, 0, sizeof(ugd)); + ugd.ugd_data = hid_pass_ptr(data); + ugd.ugd_maxlen = size; + ugd.ugd_report_type = k + 1; + return (ioctl(fd, USB_GET_REPORT, &ugd)); +} + +int +hid_set_report(int fd, enum hid_kind k, unsigned char *data, unsigned int size) +{ + struct usb_gen_descriptor ugd; + + memset(&ugd, 0, sizeof(ugd)); + ugd.ugd_data = hid_pass_ptr(data); + ugd.ugd_maxlen = size; + ugd.ugd_report_type = k + 1; + return (ioctl(fd, USB_SET_REPORT, &ugd)); +} diff --git a/lib/libu4bhid/descr.c b/lib/libu4bhid/descr.c new file mode 100644 index 0000000000..def90da3b7 --- /dev/null +++ b/lib/libu4bhid/descr.c @@ -0,0 +1,176 @@ +/* $NetBSD: descr.c,v 1.9 2000/09/24 02:13:24 augustss Exp $ */ + +/* + * Copyright (c) 1999 Lennart Augustsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbhid.h" +#include "usbvar.h" + +int +hid_set_immed(int fd, int enable) +{ + int ret; + ret = ioctl(fd, USB_SET_IMMED, &enable); +#ifdef HID_COMPAT7 + if (ret < 0) + ret = hid_set_immed_compat7(fd, enable); +#endif + return (ret); +} + +int +hid_get_report_id(int fd) +{ + report_desc_t rep; + hid_data_t d; + hid_item_t h; + int kindset; + int temp = -1; + int ret; + + if ((rep = hid_get_report_desc(fd)) == NULL) + goto use_ioctl; + kindset = 1 << hid_input | 1 << hid_output | 1 << hid_feature; + for (d = hid_start_parse(rep, kindset, 0); hid_get_item(d, &h); ) { + /* Return the first report ID we met. */ + if (h.report_ID != 0) { + temp = h.report_ID; + break; + } + } + hid_end_parse(d); + hid_dispose_report_desc(rep); + + if (temp > 0) + return (temp); + +use_ioctl: + ret = ioctl(fd, USB_GET_REPORT_ID, &temp); +#ifdef HID_COMPAT7 + if (ret < 0) + ret = hid_get_report_id_compat7(fd); + else +#endif + ret = temp; + + return (ret); +} + +report_desc_t +hid_get_report_desc(int fd) +{ + struct usb_gen_descriptor ugd; + report_desc_t rep; + void *data; + + memset(&ugd, 0, sizeof(ugd)); + + /* get actual length first */ + ugd.ugd_data = hid_pass_ptr(NULL); + ugd.ugd_maxlen = 65535; + if (ioctl(fd, USB_GET_REPORT_DESC, &ugd) < 0) { +#ifdef HID_COMPAT7 + /* could not read descriptor */ + /* try FreeBSD 7 compat code */ + return (hid_get_report_desc_compat7(fd)); +#else + return (NULL); +#endif + } + + /* + * NOTE: The kernel will return a failure if + * "ugd_actlen" is zero. + */ + data = malloc(ugd.ugd_actlen); + if (data == NULL) + return (NULL); + + /* fetch actual descriptor */ + ugd.ugd_data = hid_pass_ptr(data); + ugd.ugd_maxlen = ugd.ugd_actlen; + if (ioctl(fd, USB_GET_REPORT_DESC, &ugd) < 0) { + /* could not read descriptor */ + free(data); + return (NULL); + } + + /* sanity check */ + if (ugd.ugd_actlen < 1) { + /* invalid report descriptor */ + free(data); + return (NULL); + } + + /* check END_COLLECTION */ + if (((unsigned char *)data)[ugd.ugd_actlen -1] != 0xC0) { + /* invalid end byte */ + free(data); + return (NULL); + } + + rep = hid_use_report_desc(data, ugd.ugd_actlen); + + free(data); + + return (rep); +} + +report_desc_t +hid_use_report_desc(unsigned char *data, unsigned int size) +{ + report_desc_t r; + + r = malloc(sizeof(*r) + size); + if (r == 0) { + errno = ENOMEM; + return (NULL); + } + r->size = size; + memcpy(r->data, data, size); + return (r); +} + +void +hid_dispose_report_desc(report_desc_t r) +{ + + free(r); +} diff --git a/lib/libu4bhid/descr_compat.c b/lib/libu4bhid/descr_compat.c new file mode 100644 index 0000000000..a38d8d705e --- /dev/null +++ b/lib/libu4bhid/descr_compat.c @@ -0,0 +1,79 @@ +/* + * Copyright (c) 1999 Lennart Augustsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains fallback-compatibility code for the old FreeBSD + * USB stack. + */ +#ifdef HID_COMPAT7 + +#include +__FBSDID("$FreeBSD$"); + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "usbhid.h" +#include "usbvar.h" + +int +hid_set_immed_compat7(int fd, int enable) +{ + return (ioctl(fd, USB_SET_IMMED, &enable)); +} + +int +hid_get_report_id_compat7(int fd) +{ + int temp = -1; + + if (ioctl(fd, USB_GET_REPORT_ID, &temp) < 0) + return (-1); + + return (temp); +} + +report_desc_t +hid_get_report_desc_compat7(int fd) +{ + struct usb_ctl_report_desc rep; + + rep.ucrd_size = 0; + if (ioctl(fd, USB_GET_REPORT_DESC, &rep) < 0) + return (NULL); + + return (hid_use_report_desc(rep.ucrd_data, (unsigned int)rep.ucrd_size)); +} +#endif /* HID_COMPAT7 */ diff --git a/lib/libu4bhid/parse.c b/lib/libu4bhid/parse.c new file mode 100644 index 0000000000..f7c2cb195b --- /dev/null +++ b/lib/libu4bhid/parse.c @@ -0,0 +1,566 @@ +/* $NetBSD: parse.c,v 1.11 2000/09/24 02:19:54 augustss Exp $ */ + +/* + * Copyright (c) 1999, 2001 Lennart Augustsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include "usbhid.h" +#include "usbvar.h" + +#define MAXUSAGE 100 +#define MAXPUSH 4 +#define MAXID 64 +#define ITEMTYPES 3 + +struct hid_pos_data { + int32_t rid; + uint32_t pos[ITEMTYPES]; +}; + +struct hid_data { + const uint8_t *start; + const uint8_t *end; + const uint8_t *p; + struct hid_item cur[MAXPUSH]; + struct hid_pos_data last_pos[MAXID]; + uint32_t pos[ITEMTYPES]; + int32_t usages_min[MAXUSAGE]; + int32_t usages_max[MAXUSAGE]; + int32_t usage_last; /* last seen usage */ + uint32_t loc_size; /* last seen size */ + uint32_t loc_count; /* last seen count */ + uint8_t kindset; /* we have 5 kinds so 8 bits are enough */ + uint8_t pushlevel; /* current pushlevel */ + uint8_t ncount; /* end usage item count */ + uint8_t icount; /* current usage item count */ + uint8_t nusage; /* end "usages_min/max" index */ + uint8_t iusage; /* current "usages_min/max" index */ + uint8_t ousage; /* current "usages_min/max" offset */ + uint8_t susage; /* usage set flags */ +}; + +/*------------------------------------------------------------------------* + * hid_clear_local + *------------------------------------------------------------------------*/ +static void +hid_clear_local(hid_item_t *c) +{ + + c->usage = 0; + c->usage_minimum = 0; + c->usage_maximum = 0; + c->designator_index = 0; + c->designator_minimum = 0; + c->designator_maximum = 0; + c->string_index = 0; + c->string_minimum = 0; + c->string_maximum = 0; + c->set_delimiter = 0; +} + +static void +hid_switch_rid(struct hid_data *s, struct hid_item *c, int32_t next_rID) +{ + uint8_t i, j; + + /* check for same report ID - optimise */ + + if (c->report_ID == next_rID) + return; + + /* save current position for current rID */ + + if (c->report_ID == 0) { + i = 0; + } else { + for (i = 1; i != MAXID; i++) { + if (s->last_pos[i].rid == c->report_ID) + break; + if (s->last_pos[i].rid == 0) + break; + } + } + if (i != MAXID) { + s->last_pos[i].rid = c->report_ID; + for (j = 0; j < ITEMTYPES; j++) + s->last_pos[i].pos[j] = s->pos[j]; + } + + /* store next report ID */ + + c->report_ID = next_rID; + + /* lookup last position for next rID */ + + if (next_rID == 0) { + i = 0; + } else { + for (i = 1; i != MAXID; i++) { + if (s->last_pos[i].rid == next_rID) + break; + if (s->last_pos[i].rid == 0) + break; + } + } + if (i != MAXID) { + s->last_pos[i].rid = next_rID; + for (j = 0; j < ITEMTYPES; j++) + s->pos[j] = s->last_pos[i].pos[j]; + } else { + for (j = 0; j < ITEMTYPES; j++) + s->pos[j] = 0; /* Out of RID entries. */ + } +} + +/*------------------------------------------------------------------------* + * hid_start_parse + *------------------------------------------------------------------------*/ +hid_data_t +hid_start_parse(report_desc_t d, int kindset, int id __unused) +{ + struct hid_data *s; + + s = malloc(sizeof *s); + memset(s, 0, sizeof *s); + s->start = s->p = d->data; + s->end = d->data + d->size; + s->kindset = kindset; + return (s); +} + +/*------------------------------------------------------------------------* + * hid_end_parse + *------------------------------------------------------------------------*/ +void +hid_end_parse(hid_data_t s) +{ + + if (s == NULL) + return; + + free(s); +} + +/*------------------------------------------------------------------------* + * get byte from HID descriptor + *------------------------------------------------------------------------*/ +static uint8_t +hid_get_byte(struct hid_data *s, const uint16_t wSize) +{ + const uint8_t *ptr; + uint8_t retval; + + ptr = s->p; + + /* check if end is reached */ + if (ptr == s->end) + return (0); + + /* read out a byte */ + retval = *ptr; + + /* check if data pointer can be advanced by "wSize" bytes */ + if ((s->end - ptr) < wSize) + ptr = s->end; + else + ptr += wSize; + + /* update pointer */ + s->p = ptr; + + return (retval); +} + +/*------------------------------------------------------------------------* + * hid_get_item + *------------------------------------------------------------------------*/ +int +hid_get_item(hid_data_t s, hid_item_t *h) +{ + hid_item_t *c; + unsigned int bTag, bType, bSize; + int32_t mask; + int32_t dval; + + if (s == NULL) + return (0); + + c = &s->cur[s->pushlevel]; + + top: + /* check if there is an array of items */ + if (s->icount < s->ncount) { + /* get current usage */ + if (s->iusage < s->nusage) { + dval = s->usages_min[s->iusage] + s->ousage; + c->usage = dval; + s->usage_last = dval; + if (dval == s->usages_max[s->iusage]) { + s->iusage ++; + s->ousage = 0; + } else { + s->ousage ++; + } + } else { + /* Using last usage */ + dval = s->usage_last; + } + s->icount ++; + /* + * Only copy HID item, increment position and return + * if correct kindset! + */ + if (s->kindset & (1 << c->kind)) { + *h = *c; + h->pos = s->pos[c->kind]; + s->pos[c->kind] += c->report_size * c->report_count; + return (1); + } + } + + /* reset state variables */ + s->icount = 0; + s->ncount = 0; + s->iusage = 0; + s->nusage = 0; + s->susage = 0; + s->ousage = 0; + hid_clear_local(c); + + /* get next item */ + while (s->p != s->end) { + + bSize = hid_get_byte(s, 1); + if (bSize == 0xfe) { + /* long item */ + bSize = hid_get_byte(s, 1); + bSize |= hid_get_byte(s, 1) << 8; + bTag = hid_get_byte(s, 1); + bType = 0xff; /* XXX what should it be */ + } else { + /* short item */ + bTag = bSize >> 4; + bType = (bSize >> 2) & 3; + bSize &= 3; + if (bSize == 3) + bSize = 4; + } + + switch(bSize) { + case 0: + dval = 0; + mask = 0; + break; + case 1: + dval = (int8_t)hid_get_byte(s, 1); + mask = 0xFF; + break; + case 2: + dval = hid_get_byte(s, 1); + dval |= hid_get_byte(s, 1) << 8; + dval = (int16_t)dval; + mask = 0xFFFF; + break; + case 4: + dval = hid_get_byte(s, 1); + dval |= hid_get_byte(s, 1) << 8; + dval |= hid_get_byte(s, 1) << 16; + dval |= hid_get_byte(s, 1) << 24; + mask = 0xFFFFFFFF; + break; + default: + dval = hid_get_byte(s, bSize); + continue; + } + + switch (bType) { + case 0: /* Main */ + switch (bTag) { + case 8: /* Input */ + c->kind = hid_input; + c->flags = dval; + ret: + c->report_count = s->loc_count; + c->report_size = s->loc_size; + + if (c->flags & HIO_VARIABLE) { + /* range check usage count */ + if (c->report_count > 255) { + s->ncount = 255; + } else + s->ncount = c->report_count; + + /* + * The "top" loop will return + * one and one item: + */ + c->report_count = 1; + c->usage_minimum = 0; + c->usage_maximum = 0; + } else { + s->ncount = 1; + } + goto top; + + case 9: /* Output */ + c->kind = hid_output; + c->flags = dval; + goto ret; + case 10: /* Collection */ + c->kind = hid_collection; + c->collection = dval; + c->collevel++; + c->usage = s->usage_last; + *h = *c; + return (1); + case 11: /* Feature */ + c->kind = hid_feature; + c->flags = dval; + goto ret; + case 12: /* End collection */ + c->kind = hid_endcollection; + if (c->collevel == 0) { + /* Invalid end collection. */ + return (0); + } + c->collevel--; + *h = *c; + return (1); + default: + break; + } + break; + + case 1: /* Global */ + switch (bTag) { + case 0: + c->_usage_page = dval << 16; + break; + case 1: + c->logical_minimum = dval; + break; + case 2: + c->logical_maximum = dval; + break; + case 3: + c->physical_minimum = dval; + break; + case 4: + c->physical_maximum = dval; + break; + case 5: + c->unit_exponent = dval; + break; + case 6: + c->unit = dval; + break; + case 7: + /* mask because value is unsigned */ + s->loc_size = dval & mask; + break; + case 8: + hid_switch_rid(s, c, dval); + break; + case 9: + /* mask because value is unsigned */ + s->loc_count = dval & mask; + break; + case 10: /* Push */ + s->pushlevel ++; + if (s->pushlevel < MAXPUSH) { + s->cur[s->pushlevel] = *c; + /* store size and count */ + c->report_size = s->loc_size; + c->report_count = s->loc_count; + /* update current item pointer */ + c = &s->cur[s->pushlevel]; + } + break; + case 11: /* Pop */ + s->pushlevel --; + if (s->pushlevel < MAXPUSH) { + c = &s->cur[s->pushlevel]; + /* restore size and count */ + s->loc_size = c->report_size; + s->loc_count = c->report_count; + c->report_size = 0; + c->report_count = 0; + } + break; + default: + break; + } + break; + case 2: /* Local */ + switch (bTag) { + case 0: + if (bSize != 4) + dval = (dval & mask) | c->_usage_page; + + /* set last usage, in case of a collection */ + s->usage_last = dval; + + if (s->nusage < MAXUSAGE) { + s->usages_min[s->nusage] = dval; + s->usages_max[s->nusage] = dval; + s->nusage ++; + } + /* else XXX */ + + /* clear any pending usage sets */ + s->susage = 0; + break; + case 1: + s->susage |= 1; + + if (bSize != 4) + dval = (dval & mask) | c->_usage_page; + c->usage_minimum = dval; + + goto check_set; + case 2: + s->susage |= 2; + + if (bSize != 4) + dval = (dval & mask) | c->_usage_page; + c->usage_maximum = dval; + + check_set: + if (s->susage != 3) + break; + + /* sanity check */ + if ((s->nusage < MAXUSAGE) && + (c->usage_minimum <= c->usage_maximum)) { + /* add usage range */ + s->usages_min[s->nusage] = + c->usage_minimum; + s->usages_max[s->nusage] = + c->usage_maximum; + s->nusage ++; + } + /* else XXX */ + + s->susage = 0; + break; + case 3: + c->designator_index = dval; + break; + case 4: + c->designator_minimum = dval; + break; + case 5: + c->designator_maximum = dval; + break; + case 7: + c->string_index = dval; + break; + case 8: + c->string_minimum = dval; + break; + case 9: + c->string_maximum = dval; + break; + case 10: + c->set_delimiter = dval; + break; + default: + break; + } + break; + default: + break; + } + } + return (0); +} + +int +hid_report_size(report_desc_t r, enum hid_kind k, int id) +{ + struct hid_data *d; + struct hid_item h; + uint32_t temp; + uint32_t hpos; + uint32_t lpos; + int report_id = 0; + + hpos = 0; + lpos = 0xFFFFFFFF; + + memset(&h, 0, sizeof h); + for (d = hid_start_parse(r, 1 << k, id); hid_get_item(d, &h); ) { + if ((h.report_ID == id || id < 0) && h.kind == k) { + /* compute minimum */ + if (lpos > h.pos) + lpos = h.pos; + /* compute end position */ + temp = h.pos + (h.report_size * h.report_count); + /* compute maximum */ + if (hpos < temp) + hpos = temp; + if (h.report_ID != 0) + report_id = 1; + } + } + hid_end_parse(d); + + /* safety check - can happen in case of currupt descriptors */ + if (lpos > hpos) + temp = 0; + else + temp = hpos - lpos; + + /* return length in bytes rounded up */ + return ((temp + 7) / 8 + report_id); +} + +int +hid_locate(report_desc_t desc, unsigned int u, enum hid_kind k, + hid_item_t *h, int id) +{ + struct hid_data *d; + + for (d = hid_start_parse(desc, 1 << k, id); hid_get_item(d, h); ) { + if (h->kind == k && !(h->flags & HIO_CONST) && h->usage == u) { + hid_end_parse(d); + return (1); + } + } + hid_end_parse(d); + h->report_size = 0; + return (0); +} diff --git a/lib/libu4bhid/usage.c b/lib/libu4bhid/usage.c new file mode 100644 index 0000000000..3960dad807 --- /dev/null +++ b/lib/libu4bhid/usage.c @@ -0,0 +1,239 @@ +/* $NetBSD: usage.c,v 1.8 2000/10/10 19:23:58 is Exp $ */ + +/* + * Copyright (c) 1999 Lennart Augustsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include "usbhid.h" + +#define _PATH_HIDTABLE "/usr/share/misc/usb_hid_usages" + +struct usage_in_page { + const char *name; + int usage; +}; + +static struct usage_page { + const char *name; + int usage; + struct usage_in_page *page_contents; + int pagesize, pagesizemax; +} *pages; +static int npages, npagesmax; + +#ifdef DEBUG +void +dump_hid_table(void) +{ + int i, j; + + for (i = 0; i < npages; i++) { + printf("%d\t%s\n", pages[i].usage, pages[i].name); + for (j = 0; j < pages[i].pagesize; j++) { + printf("\t%d\t%s\n", pages[i].page_contents[j].usage, + pages[i].page_contents[j].name); + } + } +} +#endif + +void +hid_init(const char *hidname) +{ + FILE *f; + char line[100], name[100], *p, *n; + int no; + int lineno; + struct usage_page *curpage = NULL; + + if (hidname == NULL) + hidname = _PATH_HIDTABLE; + + f = fopen(hidname, "r"); + if (f == NULL) + err(1, "%s", hidname); + for (lineno = 1; ; lineno++) { + if (fgets(line, sizeof line, f) == NULL) + break; + if (line[0] == '#') + continue; + for (p = line; *p && isspace(*p); p++) + ; + if (!*p) + continue; + if (sscanf(line, " * %[^\n]", name) == 1) + no = -1; + else if (sscanf(line, " 0x%x %[^\n]", &no, name) != 2 && + sscanf(line, " %d %[^\n]", &no, name) != 2) + errx(1, "file %s, line %d, syntax error", + hidname, lineno); + for (p = name; *p; p++) + if (isspace(*p) || *p == '.') + *p = '_'; + n = strdup(name); + if (!n) + err(1, "strdup"); + if (isspace(line[0])) { + if (!curpage) + errx(1, "file %s, line %d, syntax error", + hidname, lineno); + if (curpage->pagesize >= curpage->pagesizemax) { + curpage->pagesizemax += 10; + curpage->page_contents = + realloc(curpage->page_contents, + curpage->pagesizemax * + sizeof (struct usage_in_page)); + if (!curpage->page_contents) + err(1, "realloc"); + } + curpage->page_contents[curpage->pagesize].name = n; + curpage->page_contents[curpage->pagesize].usage = no; + curpage->pagesize++; + } else { + if (npages >= npagesmax) { + if (pages == NULL) { + npagesmax = 5; + pages = malloc(npagesmax * + sizeof (struct usage_page)); + } else { + npagesmax += 5; + pages = realloc(pages, + npagesmax * + sizeof (struct usage_page)); + } + if (!pages) + err(1, "alloc"); + } + curpage = &pages[npages++]; + curpage->name = n; + curpage->usage = no; + curpage->pagesize = 0; + curpage->pagesizemax = 10; + curpage->page_contents = + malloc(curpage->pagesizemax * + sizeof (struct usage_in_page)); + if (!curpage->page_contents) + err(1, "malloc"); + } + } + fclose(f); +#ifdef DEBUG + dump_hid_table(); +#endif +} + +const char * +hid_usage_page(int i) +{ + static char b[10]; + int k; + + if (!pages) + errx(1, "no hid table"); + + for (k = 0; k < npages; k++) + if (pages[k].usage == i) + return pages[k].name; + sprintf(b, "0x%04x", i); + return b; +} + +const char * +hid_usage_in_page(unsigned int u) +{ + int page = HID_PAGE(u); + int i = HID_USAGE(u); + static char b[100]; + int j, k, us; + + for (k = 0; k < npages; k++) + if (pages[k].usage == page) + break; + if (k >= npages) + goto bad; + for (j = 0; j < pages[k].pagesize; j++) { + us = pages[k].page_contents[j].usage; + if (us == -1) { + sprintf(b, + fmtcheck(pages[k].page_contents[j].name, "%d"), + i); + return b; + } + if (us == i) + return pages[k].page_contents[j].name; + } + bad: + sprintf(b, "0x%04x", i); + return b; +} + +int +hid_parse_usage_page(const char *name) +{ + int k; + + if (!pages) + errx(1, "no hid table"); + + for (k = 0; k < npages; k++) + if (strcmp(pages[k].name, name) == 0) + return pages[k].usage; + return -1; +} + +/* XXX handle hex */ +int +hid_parse_usage_in_page(const char *name) +{ + const char *sep; + int k, j; + unsigned int l; + + sep = strchr(name, ':'); + if (sep == NULL) + return -1; + l = sep - name; + for (k = 0; k < npages; k++) + if (strncmp(pages[k].name, name, l) == 0) + goto found; + return -1; + found: + sep++; + for (j = 0; j < pages[k].pagesize; j++) + if (strcmp(pages[k].page_contents[j].name, sep) == 0) + return (pages[k].usage << 16) | pages[k].page_contents[j].usage; + return (-1); +} diff --git a/lib/libu4bhid/usbhid.3 b/lib/libu4bhid/usbhid.3 new file mode 100644 index 0000000000..c34a109d58 --- /dev/null +++ b/lib/libu4bhid/usbhid.3 @@ -0,0 +1,249 @@ +.\" $NetBSD: usb.3,v 1.13 2000/09/24 02:17:52 augustss Exp $ +.\" +.\" Copyright (c) 1999, 2001 Lennart Augustsson +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd January 27, 2009 +.Dt USBHID 3 +.Os +.Sh NAME +.Nm usbhid , +.Nm hid_get_report_desc , +.Nm hid_get_report_id , +.Nm hid_use_report_desc , +.Nm hid_dispose_report_desc , +.Nm hid_start_parse , +.Nm hid_end_parse , +.Nm hid_get_item , +.Nm hid_report_size , +.Nm hid_locate , +.Nm hid_usage_page , +.Nm hid_usage_in_page , +.Nm hid_init , +.Nm hid_get_data , +.Nm hid_set_data , +.Nm hid_get_report , +.Nm hid_set_report +.Nd USB HID access routines +.Sh LIBRARY +.Lb libusbhid +.Sh SYNOPSIS +.In usbhid.h +.Ft report_desc_t +.Fn hid_get_report_desc "int file" +.Ft int +.Fn hid_get_report_id "int file" +.Ft int +.Fn hid_set_immed "int fd" "int enable" +.Ft report_desc_t +.Fn hid_use_report_desc "unsigned char *data" "unsigned int size" +.Ft void +.Fn hid_dispose_report_desc "report_desc_t d" +.Ft hid_data_t +.Fn hid_start_parse "report_desc_t d" "int kindset" "int id" +.Ft void +.Fn hid_end_parse "hid_data_t s" +.Ft int +.Fn hid_get_item "hid_data_t s" "hid_item_t *h" +.Ft int +.Fn hid_report_size "report_desc_t d" "hid_kind_t k" "int id" +.Ft int +.Fn hid_locate "report_desc_t d" "u_int usage" "hid_kind_t k" "hid_item_t *h" "int id" +.Ft "const char *" +.Fn hid_usage_page "int i" +.Ft "const char *" +.Fn hid_usage_in_page "u_int u" +.Ft int +.Fn hid_parse_usage_page "const char *" +.Ft int +.Fn hid_parse_usage_in_page "const char *" +.Ft void +.Fn hid_init "const char *file" +.Ft int +.Fn hid_get_data "const void *data" "const hid_item_t *h" +.Ft void +.Fn hid_set_data "void *buf" "const hid_item_t *h" "int data" +.Ft int +.Fn hid_get_report "int fd" "enum hid_kind k" "unsigned char *data" "unsigned int size" +.Ft int +.Fn hid_set_report "int fd" "enum hid_kind k" "unsigned char *data" "unsigned int size" +.Sh DESCRIPTION +The +.Nm +library provides routines to extract data from USB Human Interface Devices. +.Ss Introduction +USB HID devices send and receive data layed out in a device dependent way. +The +.Nm +library contains routines to extract the +.Em "report descriptor" +which contains the data layout information and then use this information. +.Pp +The routines can be divided into four parts: extraction of the descriptor, +parsing of the descriptor, translating to/from symbolic names, and +data manipulation. +.Ss Synchronous HID operation +Synchronous HID operation can be enabled or disabled by a call to +.Fn hid_set_immed . +If the second argument is zero synchronous HID operation is disabled. +Else synchronous HID operation is enabled. +The function returns a negative value on failure. +.Pp +.Fn hid_get_report +and +.Fn hid_set_report +functions allow to synchronously get and set specific report if device +supports it. +For devices with multiple report IDs, wanted ID should be provided in the +first byte of the buffer for both get and set. +.Ss Descriptor Functions +The report descriptor ID can be obtained by calling +.Fn hid_get_report_id . +A report descriptor can be obtained by calling +.Fn hid_get_report_desc +with a file descriptor obtained by opening a +.Xr uhid 4 +device. +Alternatively a data buffer containing the report descriptor can be +passed into +.Fn hid_use_report_desc . +The data is copied into an internal structure. +When the report descriptor +is no longer needed it should be freed by calling +.Fn hid_dispose_report_desc . +The type +.Vt report_desc_t +is opaque and should be used when calling the parsing functions. +If +.Fn hid_dispose_report_desc +fails it will return +.Dv NULL . +.Ss Descriptor Parsing Functions +To parse the report descriptor the +.Fn hid_start_parse +function should be called with a report descriptor and a set that +describes which items that are interesting. +The set is obtained by OR-ing together values +.Fa "(1 << k)" +where +.Fa k +is an item of type +.Vt hid_kind_t . +The report ID (if present) is given by +.Fa id . +The function returns +.Dv NULL +if the initialization fails, otherwise an opaque value to be used +in subsequent calls. +After parsing the +.Fn hid_end_parse +function should be called to free internal data structures. +.Pp +To iterate through all the items in the report descriptor +.Fn hid_get_item +should be called while it returns a value greater than 0. +When the report descriptor ends it will returns 0; a syntax +error within the report descriptor will cause a return value less +than 0. +The struct pointed to by +.Fa h +will be filled with the relevant data for the item. +The definition of +.Vt hid_item_t +can be found in +.In usbhid.h +and the meaning of the components in the USB HID documentation. +.Pp +Data should be read/written to the device in the size of +the report. +The size of a report (of a certain kind) can be computed by the +.Fn hid_report_size +function. +If the report is prefixed by an ID byte it is given by +.Fa id . +.Pp +To locate a single item the +.Fn hid_locate +function can be used. +It should be given the usage code of +the item and its kind and it will fill the item and return +non-zero if the item was found. +.Ss Name Translation Functions +The function +.Fn hid_usage_page +will return the symbolic name of a usage page, and the function +.Fn hid_usage_in_page +will return the symbolic name of the usage within the page. +Both these functions may return a pointer to static data. +.Pp +The functions +.Fn hid_parse_usage_page +and +.Fn hid_parse_usage_in_page +are the inverses of +.Fn hid_usage_page +and +.Fn hid_usage_in_page . +They take a usage string and return the number of the usage, or \-1 +if it cannot be found. +.Pp +Before any of these functions can be called the usage table +must be parsed, this is done by calling +.Fn hid_init +with the name of the table. +Passing +.Dv NULL +to this function will cause it to use the default table. +.Ss Data Extraction Functions +Given the data obtained from a HID device and an item in the +report descriptor the +.Fn hid_get_data +function extracts the value of the item. +Conversely +.Fn hid_set_data +can be used to put data into a report (which must be zeroed first). +.Sh FILES +.Bl -tag -width ".Pa /usr/share/misc/usb_hid_usages" +.It Pa /usr/share/misc/usb_hid_usages +The default HID usage table. +.El +.Sh EXAMPLES +Not yet. +.Sh SEE ALSO +The +.Tn USB +specifications can be found at +.Pa http://www.usb.org/developers/docs/ . +.Pp +.Xr uhid 4 , +.Xr usb 4 +.Sh HISTORY +The +.Nm +library first appeared in +.Nx 1.5 . +.Sh BUGS +This man page is woefully incomplete. diff --git a/lib/libu4bhid/usbhid.h b/lib/libu4bhid/usbhid.h new file mode 100644 index 0000000000..231ee1fe8a --- /dev/null +++ b/lib/libu4bhid/usbhid.h @@ -0,0 +1,113 @@ +/* $NetBSD: usb.h,v 1.8 2000/08/13 22:22:02 augustss Exp $ */ + +/* + * Copyright (c) 1999 Lennart Augustsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#include + +typedef struct report_desc *report_desc_t; + +typedef struct hid_data *hid_data_t; + +typedef enum hid_kind { + hid_input, hid_output, hid_feature, hid_collection, hid_endcollection +} hid_kind_t; + +typedef struct hid_item { + /* Global */ + uint32_t _usage_page; + int32_t logical_minimum; + int32_t logical_maximum; + int32_t physical_minimum; + int32_t physical_maximum; + int32_t unit_exponent; + int32_t unit; + int32_t report_size; + int32_t report_ID; +#define NO_REPORT_ID 0 + int32_t report_count; + /* Local */ + uint32_t usage; + int32_t usage_minimum; + int32_t usage_maximum; + int32_t designator_index; + int32_t designator_minimum; + int32_t designator_maximum; + int32_t string_index; + int32_t string_minimum; + int32_t string_maximum; + int32_t set_delimiter; + /* Misc */ + int32_t collection; + int collevel; + enum hid_kind kind; + uint32_t flags; + /* Location */ + uint32_t pos; + /* unused */ + struct hid_item *next; +} hid_item_t; + +#define HID_PAGE(u) (((u) >> 16) & 0xffff) +#define HID_USAGE(u) ((u) & 0xffff) +#define HID_HAS_GET_SET_REPORT 1 + +__BEGIN_DECLS + +/* Obtaining a report descriptor, descr.c: */ +report_desc_t hid_get_report_desc(int file); +report_desc_t hid_use_report_desc(unsigned char *data, unsigned int size); +void hid_dispose_report_desc(report_desc_t); +int hid_get_report_id(int file); +int hid_set_immed(int fd, int enable); + +/* Parsing of a HID report descriptor, parse.c: */ +hid_data_t hid_start_parse(report_desc_t d, int kindset, int id); +void hid_end_parse(hid_data_t s); +int hid_get_item(hid_data_t s, hid_item_t *h); +int hid_report_size(report_desc_t d, enum hid_kind k, int id); +int hid_locate(report_desc_t d, unsigned int usage, enum hid_kind k, + hid_item_t *h, int id); + +/* Conversion to/from usage names, usage.c: */ +const char *hid_usage_page(int i); +const char *hid_usage_in_page(unsigned int u); +void hid_init(const char *file); +int hid_parse_usage_in_page(const char *name); +int hid_parse_usage_page(const char *name); + +/* Extracting/insertion of data, data.c: */ +int32_t hid_get_data(const void *p, const hid_item_t *h); +void hid_set_data(void *p, const hid_item_t *h, int32_t data); +int hid_get_report(int fd, enum hid_kind k, + unsigned char *data, unsigned int size); +int hid_set_report(int fd, enum hid_kind k, + unsigned char *data, unsigned int size); + +__END_DECLS diff --git a/lib/libu4bhid/usbvar.h b/lib/libu4bhid/usbvar.h new file mode 100644 index 0000000000..2722a3783f --- /dev/null +++ b/lib/libu4bhid/usbvar.h @@ -0,0 +1,54 @@ +/* $NetBSD: usbvar.h,v 1.2 1999/05/11 21:15:46 augustss Exp $ */ + +/* + * Copyright (c) 1999 Lennart Augustsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#ifndef _USBVAR_H_ +#define _USBVAR_H_ + +struct report_desc { + uint32_t size; + uint8_t data[1]; +}; + +/* internal backwards compatibility functions */ + +#ifdef HID_COMPAT7 +int hid_set_immed_compat7(int fd, int enable); +int hid_get_report_id_compat7(int fd); +report_desc_t hid_get_report_desc_compat7(int fd); +#endif + +#ifdef COMPAT_32BIT +#define hid_pass_ptr(ptr) ((uint64_t)(uintptr_t)(ptr)) +#else +#define hid_pass_ptr(ptr) (ptr) +#endif + +#endif /* _USBVAR_H_ */ diff --git a/sys/bus/u4b/Makefile b/sys/bus/u4b/Makefile new file mode 100644 index 0000000000..5dd6b6475f --- /dev/null +++ b/sys/bus/u4b/Makefile @@ -0,0 +1,6 @@ +# $DragonFly: src/sys/bus/cam/Makefile,v 1.2 2007/11/12 07:27:50 pavalos Exp $ +# + +#SUBDIR= controller input misc net quirk serial storage template + +.include diff --git a/sys/bus/u4b/Makefile.usbdevs b/sys/bus/u4b/Makefile.usbdevs new file mode 100644 index 0000000000..f2a211e0f7 --- /dev/null +++ b/sys/bus/u4b/Makefile.usbdevs @@ -0,0 +1,16 @@ +# $DragonFly: src/sys/bus/pci/Makefile.pcidevs,v 1.1 2004/02/19 20:46:15 joerg Exp $ +# $NetBSD: Makefile.pcidevs,v 1.2 1999/03/16 22:41:56 mjacob Exp $ +# +# Update procedure: +# 1.) Change "src/sys/bus/usb/usbdevs". +# 2.) Commit "src/sys/bus/usb/usbdevs". +# 3.) Execute "make -f Makefile.usbdevs" in "src/sys/bus/usb". +# 4.) Commit "src/sys/bus/usb/usbdevs.h" and "src/sys/bus/usb/usbdevs_data.h". + + +AWK= awk + +usbdevs.h usbdevs_data.h: usbdevs usbdevs2h.awk + /bin/rm -f usbdevs.h usbdevs_data.h + ${AWK} -f usbdevs2h.awk usbdevs -d + ${AWK} -f usbdevs2h.awk usbdevs -h diff --git a/sys/bus/u4b/controller/at91dci.c b/sys/bus/u4b/controller/at91dci.c new file mode 100644 index 0000000000..d3258127dc --- /dev/null +++ b/sys/bus/u4b/controller/at91dci.c @@ -0,0 +1,2340 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2007-2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains the driver for the AT91 series USB Device + * Controller + */ + +/* + * Thanks to "David Brownell" for helping out regarding the hardware + * endpoint profiles. + */ + +/* + * NOTE: The "fifo_bank" is not reset in hardware when the endpoint is + * reset. + * + * NOTE: When the chip detects BUS-reset it will also reset the + * endpoints, Function-address and more. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define USB_DEBUG_VAR at91dcidebug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define AT9100_DCI_BUS2SC(bus) \ + ((struct at91dci_softc *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((struct at91dci_softc *)0)->sc_bus)))) + +#define AT9100_DCI_PC2SC(pc) \ + AT9100_DCI_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus) + +#ifdef USB_DEBUG +static int at91dcidebug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, at91dci, CTLFLAG_RW, 0, "USB at91dci"); +SYSCTL_INT(_hw_usb_at91dci, OID_AUTO, debug, CTLFLAG_RW, + &at91dcidebug, 0, "at91dci debug level"); +#endif + +#define AT9100_DCI_INTR_ENDPT 1 + +/* prototypes */ + +struct usb_bus_methods at91dci_bus_methods; +struct usb_pipe_methods at91dci_device_bulk_methods; +struct usb_pipe_methods at91dci_device_ctrl_methods; +struct usb_pipe_methods at91dci_device_intr_methods; +struct usb_pipe_methods at91dci_device_isoc_fs_methods; + +static at91dci_cmd_t at91dci_setup_rx; +static at91dci_cmd_t at91dci_data_rx; +static at91dci_cmd_t at91dci_data_tx; +static at91dci_cmd_t at91dci_data_tx_sync; +static void at91dci_device_done(struct usb_xfer *, usb_error_t); +static void at91dci_do_poll(struct usb_bus *); +static void at91dci_standard_done(struct usb_xfer *); +static void at91dci_root_intr(struct at91dci_softc *sc); + +/* + * NOTE: Some of the bits in the CSR register have inverse meaning so + * we need a helper macro when acknowledging events: + */ +#define AT91_CSR_ACK(csr, what) do { \ + (csr) &= ~((AT91_UDP_CSR_FORCESTALL| \ + AT91_UDP_CSR_TXPKTRDY| \ + AT91_UDP_CSR_RXBYTECNT) ^ (what));\ + (csr) |= ((AT91_UDP_CSR_RX_DATA_BK0| \ + AT91_UDP_CSR_RX_DATA_BK1| \ + AT91_UDP_CSR_TXCOMP| \ + AT91_UDP_CSR_RXSETUP| \ + AT91_UDP_CSR_STALLSENT) ^ (what)); \ +} while (0) + +/* + * Here is a list of what the chip supports. + * Probably it supports more than listed here! + */ +static const struct usb_hw_ep_profile + at91dci_ep_profile[AT91_UDP_EP_MAX] = { + + [0] = { + .max_in_frame_size = 8, + .max_out_frame_size = 8, + .is_simplex = 1, + .support_control = 1, + }, + [1] = { + .max_in_frame_size = 64, + .max_out_frame_size = 64, + .is_simplex = 1, + .support_multi_buffer = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, + [2] = { + .max_in_frame_size = 64, + .max_out_frame_size = 64, + .is_simplex = 1, + .support_multi_buffer = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, + [3] = { + /* can also do BULK */ + .max_in_frame_size = 8, + .max_out_frame_size = 8, + .is_simplex = 1, + .support_interrupt = 1, + .support_in = 1, + .support_out = 1, + }, + [4] = { + .max_in_frame_size = 256, + .max_out_frame_size = 256, + .is_simplex = 1, + .support_multi_buffer = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, + [5] = { + .max_in_frame_size = 256, + .max_out_frame_size = 256, + .is_simplex = 1, + .support_multi_buffer = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, +}; + +static void +at91dci_get_hw_ep_profile(struct usb_device *udev, + const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) +{ + if (ep_addr < AT91_UDP_EP_MAX) { + *ppf = (at91dci_ep_profile + ep_addr); + } else { + *ppf = NULL; + } +} + +static void +at91dci_clocks_on(struct at91dci_softc *sc) +{ + if (sc->sc_flags.clocks_off && + sc->sc_flags.port_powered) { + + DPRINTFN(5, "\n"); + + if (sc->sc_clocks_on) { + (sc->sc_clocks_on) (sc->sc_clocks_arg); + } + sc->sc_flags.clocks_off = 0; + + /* enable Transceiver */ + AT91_UDP_WRITE_4(sc, AT91_UDP_TXVC, 0); + } +} + +static void +at91dci_clocks_off(struct at91dci_softc *sc) +{ + if (!sc->sc_flags.clocks_off) { + + DPRINTFN(5, "\n"); + + /* disable Transceiver */ + AT91_UDP_WRITE_4(sc, AT91_UDP_TXVC, AT91_UDP_TXVC_DIS); + + if (sc->sc_clocks_off) { + (sc->sc_clocks_off) (sc->sc_clocks_arg); + } + sc->sc_flags.clocks_off = 1; + } +} + +static void +at91dci_pull_up(struct at91dci_softc *sc) +{ + /* pullup D+, if possible */ + + if (!sc->sc_flags.d_pulled_up && + sc->sc_flags.port_powered) { + sc->sc_flags.d_pulled_up = 1; + (sc->sc_pull_up) (sc->sc_pull_arg); + } +} + +static void +at91dci_pull_down(struct at91dci_softc *sc) +{ + /* pulldown D+, if possible */ + + if (sc->sc_flags.d_pulled_up) { + sc->sc_flags.d_pulled_up = 0; + (sc->sc_pull_down) (sc->sc_pull_arg); + } +} + +static void +at91dci_wakeup_peer(struct at91dci_softc *sc) +{ + if (!(sc->sc_flags.status_suspend)) { + return; + } + + AT91_UDP_WRITE_4(sc, AT91_UDP_GSTATE, AT91_UDP_GSTATE_ESR); + + /* wait 8 milliseconds */ + /* Wait for reset to complete. */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125); + + AT91_UDP_WRITE_4(sc, AT91_UDP_GSTATE, 0); +} + +static void +at91dci_set_address(struct at91dci_softc *sc, uint8_t addr) +{ + DPRINTFN(5, "addr=%d\n", addr); + + AT91_UDP_WRITE_4(sc, AT91_UDP_FADDR, addr | + AT91_UDP_FADDR_EN); +} + +static uint8_t +at91dci_setup_rx(struct at91dci_td *td) +{ + struct at91dci_softc *sc; + struct usb_device_request req; + uint32_t csr; + uint32_t temp; + uint16_t count; + + /* read out FIFO status */ + csr = bus_space_read_4(td->io_tag, td->io_hdl, + td->status_reg); + + DPRINTFN(5, "csr=0x%08x rem=%u\n", csr, td->remainder); + + temp = csr; + temp &= (AT91_UDP_CSR_RX_DATA_BK0 | + AT91_UDP_CSR_RX_DATA_BK1 | + AT91_UDP_CSR_STALLSENT | + AT91_UDP_CSR_RXSETUP | + AT91_UDP_CSR_TXCOMP); + + if (!(csr & AT91_UDP_CSR_RXSETUP)) { + goto not_complete; + } + /* clear did stall */ + td->did_stall = 0; + + /* get the packet byte count */ + count = (csr & AT91_UDP_CSR_RXBYTECNT) >> 16; + + /* verify data length */ + if (count != td->remainder) { + DPRINTFN(0, "Invalid SETUP packet " + "length, %d bytes\n", count); + goto not_complete; + } + if (count != sizeof(req)) { + DPRINTFN(0, "Unsupported SETUP packet " + "length, %d bytes\n", count); + goto not_complete; + } + /* receive data */ + bus_space_read_multi_1(td->io_tag, td->io_hdl, + td->fifo_reg, (void *)&req, sizeof(req)); + + /* copy data into real buffer */ + usbd_copy_in(td->pc, 0, &req, sizeof(req)); + + td->offset = sizeof(req); + td->remainder = 0; + + /* get pointer to softc */ + sc = AT9100_DCI_PC2SC(td->pc); + + /* sneak peek the set address */ + if ((req.bmRequestType == UT_WRITE_DEVICE) && + (req.bRequest == UR_SET_ADDRESS)) { + sc->sc_dv_addr = req.wValue[0] & 0x7F; + } else { + sc->sc_dv_addr = 0xFF; + } + + /* sneak peek the endpoint direction */ + if (req.bmRequestType & UE_DIR_IN) { + csr |= AT91_UDP_CSR_DIR; + } else { + csr &= ~AT91_UDP_CSR_DIR; + } + + /* write the direction of the control transfer */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + return (0); /* complete */ + +not_complete: + /* abort any ongoing transfer */ + if (!td->did_stall) { + DPRINTFN(5, "stalling\n"); + temp |= AT91_UDP_CSR_FORCESTALL; + td->did_stall = 1; + } + + /* clear interrupts, if any */ + if (temp) { + DPRINTFN(5, "clearing 0x%08x\n", temp); + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + } + return (1); /* not complete */ + +} + +static uint8_t +at91dci_data_rx(struct at91dci_td *td) +{ + struct usb_page_search buf_res; + uint32_t csr; + uint32_t temp; + uint16_t count; + uint8_t to; + uint8_t got_short; + + to = 2; /* don't loop forever! */ + got_short = 0; + + /* check if any of the FIFO banks have data */ +repeat: + /* read out FIFO status */ + csr = bus_space_read_4(td->io_tag, td->io_hdl, + td->status_reg); + + DPRINTFN(5, "csr=0x%08x rem=%u\n", csr, td->remainder); + + if (csr & AT91_UDP_CSR_RXSETUP) { + if (td->remainder == 0) { + /* + * We are actually complete and have + * received the next SETUP + */ + DPRINTFN(5, "faking complete\n"); + return (0); /* complete */ + } + /* + * USB Host Aborted the transfer. + */ + td->error = 1; + return (0); /* complete */ + } + /* Make sure that "STALLSENT" gets cleared */ + temp = csr; + temp &= AT91_UDP_CSR_STALLSENT; + + /* check status */ + if (!(csr & (AT91_UDP_CSR_RX_DATA_BK0 | + AT91_UDP_CSR_RX_DATA_BK1))) { + if (temp) { + /* write command */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + } + return (1); /* not complete */ + } + /* get the packet byte count */ + count = (csr & AT91_UDP_CSR_RXBYTECNT) >> 16; + + /* verify the packet byte count */ + if (count != td->max_packet_size) { + if (count < td->max_packet_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + } + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + while (count > 0) { + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* receive data */ + bus_space_read_multi_1(td->io_tag, td->io_hdl, + td->fifo_reg, buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* clear status bits */ + if (td->support_multi_buffer) { + if (td->fifo_bank) { + td->fifo_bank = 0; + temp |= AT91_UDP_CSR_RX_DATA_BK1; + } else { + td->fifo_bank = 1; + temp |= AT91_UDP_CSR_RX_DATA_BK0; + } + } else { + temp |= (AT91_UDP_CSR_RX_DATA_BK0 | + AT91_UDP_CSR_RX_DATA_BK1); + } + + /* write command */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + + /* + * NOTE: We may have to delay a little bit before + * proceeding after clearing the DATA_BK bits. + */ + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) { + /* we are complete */ + return (0); + } + /* else need to receive a zero length packet */ + } + if (--to) { + goto repeat; + } + return (1); /* not complete */ +} + +static uint8_t +at91dci_data_tx(struct at91dci_td *td) +{ + struct usb_page_search buf_res; + uint32_t csr; + uint32_t temp; + uint16_t count; + uint8_t to; + + to = 2; /* don't loop forever! */ + +repeat: + + /* read out FIFO status */ + csr = bus_space_read_4(td->io_tag, td->io_hdl, + td->status_reg); + + DPRINTFN(5, "csr=0x%08x rem=%u\n", csr, td->remainder); + + if (csr & AT91_UDP_CSR_RXSETUP) { + /* + * The current transfer was aborted + * by the USB Host + */ + td->error = 1; + return (0); /* complete */ + } + /* Make sure that "STALLSENT" gets cleared */ + temp = csr; + temp &= AT91_UDP_CSR_STALLSENT; + + if (csr & AT91_UDP_CSR_TXPKTRDY) { + if (temp) { + /* write command */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + } + return (1); /* not complete */ + } else { + /* clear TXCOMP and set TXPKTRDY */ + temp |= (AT91_UDP_CSR_TXCOMP | + AT91_UDP_CSR_TXPKTRDY); + } + + count = td->max_packet_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + while (count > 0) { + + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* transmit data */ + bus_space_write_multi_1(td->io_tag, td->io_hdl, + td->fifo_reg, buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* write command */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) { + return (0); /* complete */ + } + /* else we need to transmit a short packet */ + } + if (--to) { + goto repeat; + } + return (1); /* not complete */ +} + +static uint8_t +at91dci_data_tx_sync(struct at91dci_td *td) +{ + struct at91dci_softc *sc; + uint32_t csr; + uint32_t temp; + +#if 0 +repeat: +#endif + + /* read out FIFO status */ + csr = bus_space_read_4(td->io_tag, td->io_hdl, + td->status_reg); + + DPRINTFN(5, "csr=0x%08x\n", csr); + + if (csr & AT91_UDP_CSR_RXSETUP) { + DPRINTFN(5, "faking complete\n"); + /* Race condition */ + return (0); /* complete */ + } + temp = csr; + temp &= (AT91_UDP_CSR_STALLSENT | + AT91_UDP_CSR_TXCOMP); + + /* check status */ + if (csr & AT91_UDP_CSR_TXPKTRDY) { + goto not_complete; + } + if (!(csr & AT91_UDP_CSR_TXCOMP)) { + goto not_complete; + } + sc = AT9100_DCI_PC2SC(td->pc); + if (sc->sc_dv_addr != 0xFF) { + /* + * The AT91 has a special requirement with regard to + * setting the address and that is to write the new + * address before clearing TXCOMP: + */ + at91dci_set_address(sc, sc->sc_dv_addr); + } + /* write command */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + + return (0); /* complete */ + +not_complete: + if (temp) { + /* write command */ + AT91_CSR_ACK(csr, temp); + bus_space_write_4(td->io_tag, td->io_hdl, + td->status_reg, csr); + } + return (1); /* not complete */ +} + +static uint8_t +at91dci_xfer_do_fifo(struct usb_xfer *xfer) +{ + struct at91dci_softc *sc; + struct at91dci_td *td; + uint8_t temp; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + while (1) { + if ((td->func) (td)) { + /* operation in progress */ + break; + } + if (((void *)td) == xfer->td_transfer_last) { + goto done; + } + if (td->error) { + goto done; + } else if (td->remainder > 0) { + /* + * We had a short transfer. If there is no alternate + * next, stop processing ! + */ + if (!td->alt_next) { + goto done; + } + } + /* + * Fetch the next transfer descriptor and transfer + * some flags to the next transfer descriptor + */ + temp = 0; + if (td->fifo_bank) + temp |= 1; + td = td->obj_next; + xfer->td_transfer_cache = td; + if (temp & 1) + td->fifo_bank = 1; + } + return (1); /* not complete */ + +done: + sc = AT9100_DCI_BUS2SC(xfer->xroot->bus); + temp = (xfer->endpointno & UE_ADDR); + + /* update FIFO bank flag and multi buffer */ + if (td->fifo_bank) { + sc->sc_ep_flags[temp].fifo_bank = 1; + } else { + sc->sc_ep_flags[temp].fifo_bank = 0; + } + + /* compute all actual lengths */ + + at91dci_standard_done(xfer); + + return (0); /* complete */ +} + +static void +at91dci_interrupt_poll(struct at91dci_softc *sc) +{ + struct usb_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + if (!at91dci_xfer_do_fifo(xfer)) { + /* queue has been modified */ + goto repeat; + } + } +} + +void +at91dci_vbus_interrupt(struct at91dci_softc *sc, uint8_t is_on) +{ + DPRINTFN(5, "vbus = %u\n", is_on); + + USB_BUS_LOCK(&sc->sc_bus); + if (is_on) { + if (!sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 1; + + /* complete root HUB interrupt endpoint */ + at91dci_root_intr(sc); + } + } else { + if (sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* complete root HUB interrupt endpoint */ + at91dci_root_intr(sc); + } + } + USB_BUS_UNLOCK(&sc->sc_bus); +} + +void +at91dci_interrupt(struct at91dci_softc *sc) +{ + uint32_t status; + + USB_BUS_LOCK(&sc->sc_bus); + + status = AT91_UDP_READ_4(sc, AT91_UDP_ISR); + status &= AT91_UDP_INT_DEFAULT; + + if (!status) { + USB_BUS_UNLOCK(&sc->sc_bus); + return; + } + /* acknowledge interrupts */ + + AT91_UDP_WRITE_4(sc, AT91_UDP_ICR, status); + + /* check for any bus state change interrupts */ + + if (status & AT91_UDP_INT_BUS) { + + DPRINTFN(5, "real bus interrupt 0x%08x\n", status); + + if (status & AT91_UDP_INT_END_BR) { + + /* set correct state */ + sc->sc_flags.status_bus_reset = 1; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* disable resume interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, + AT91_UDP_INT_RXRSM); + /* enable suspend interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IER, + AT91_UDP_INT_RXSUSP); + } + /* + * If RXRSM and RXSUSP is set at the same time we interpret + * that like RESUME. Resume is set when there is at least 3 + * milliseconds of inactivity on the USB BUS. + */ + if (status & AT91_UDP_INT_RXRSM) { + if (sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 1; + + /* disable resume interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, + AT91_UDP_INT_RXRSM); + /* enable suspend interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IER, + AT91_UDP_INT_RXSUSP); + } + } else if (status & AT91_UDP_INT_RXSUSP) { + if (!sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 1; + sc->sc_flags.change_suspend = 1; + + /* disable suspend interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, + AT91_UDP_INT_RXSUSP); + + /* enable resume interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IER, + AT91_UDP_INT_RXRSM); + } + } + /* complete root HUB interrupt endpoint */ + at91dci_root_intr(sc); + } + /* check for any endpoint interrupts */ + + if (status & AT91_UDP_INT_EPS) { + + DPRINTFN(5, "real endpoint interrupt 0x%08x\n", status); + + at91dci_interrupt_poll(sc); + } + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +at91dci_setup_standard_chain_sub(struct at91dci_std_temp *temp) +{ + struct at91dci_td *td; + + /* get current Transfer Descriptor */ + td = temp->td_next; + temp->td = td; + + /* prepare for next TD */ + temp->td_next = td->obj_next; + + /* fill out the Transfer Descriptor */ + td->func = temp->func; + td->pc = temp->pc; + td->offset = temp->offset; + td->remainder = temp->len; + td->fifo_bank = 0; + td->error = 0; + td->did_stall = temp->did_stall; + td->short_pkt = temp->short_pkt; + td->alt_next = temp->setup_alt_next; +} + +static void +at91dci_setup_standard_chain(struct usb_xfer *xfer) +{ + struct at91dci_std_temp temp; + struct at91dci_softc *sc; + struct at91dci_td *td; + uint32_t x; + uint8_t ep_no; + uint8_t need_sync; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpointno), + xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); + + temp.max_frame_size = xfer->max_frame_size; + + td = xfer->td_start[0]; + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + /* setup temp */ + + temp.pc = NULL; + temp.td = NULL; + temp.td_next = xfer->td_start[0]; + temp.offset = 0; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.did_stall = !xfer->flags_int.control_stall; + + sc = AT9100_DCI_BUS2SC(xfer->xroot->bus); + ep_no = (xfer->endpointno & UE_ADDR); + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.func = &at91dci_setup_rx; + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.short_pkt = temp.len ? 1 : 0; + /* check for last frame */ + if (xfer->nframes == 1) { + /* no STATUS stage yet, SETUP is last */ + if (xfer->flags_int.control_act) + temp.setup_alt_next = 0; + } + + at91dci_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + if (x != xfer->nframes) { + if (xfer->endpointno & UE_DIR_IN) { + temp.func = &at91dci_data_tx; + need_sync = 1; + } else { + temp.func = &at91dci_data_rx; + need_sync = 0; + } + + /* setup "pc" pointer */ + temp.pc = xfer->frbuffers + x; + } else { + need_sync = 0; + } + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + + x++; + + if (x == xfer->nframes) { + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_act) { + temp.setup_alt_next = 0; + } + } else { + temp.setup_alt_next = 0; + } + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.short_pkt = 0; + + } else { + + /* regular data transfer */ + + temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + at91dci_setup_standard_chain_sub(&temp); + + if (xfer->flags_int.isochronous_xfr) { + temp.offset += temp.len; + } else { + /* get next Page Cache pointer */ + temp.pc = xfer->frbuffers + x; + } + } + + /* check for control transfer */ + if (xfer->flags_int.control_xfr) { + + /* always setup a valid "pc" pointer for status and sync */ + temp.pc = xfer->frbuffers + 0; + temp.len = 0; + temp.short_pkt = 0; + temp.setup_alt_next = 0; + + /* check if we need to sync */ + if (need_sync) { + /* we need a SYNC point after TX */ + temp.func = &at91dci_data_tx_sync; + at91dci_setup_standard_chain_sub(&temp); + } + + /* check if we should append a status stage */ + if (!xfer->flags_int.control_act) { + + /* + * Send a DATA1 message and invert the current + * endpoint direction. + */ + if (xfer->endpointno & UE_DIR_IN) { + temp.func = &at91dci_data_rx; + need_sync = 0; + } else { + temp.func = &at91dci_data_tx; + need_sync = 1; + } + + at91dci_setup_standard_chain_sub(&temp); + if (need_sync) { + /* we need a SYNC point after TX */ + temp.func = &at91dci_data_tx_sync; + at91dci_setup_standard_chain_sub(&temp); + } + } + } + + /* must have at least one frame! */ + td = temp.td; + xfer->td_transfer_last = td; + + /* setup the correct fifo bank */ + if (sc->sc_ep_flags[ep_no].fifo_bank) { + td = xfer->td_transfer_first; + td->fifo_bank = 1; + } +} + +static void +at91dci_timeout(void *arg) +{ + struct usb_xfer *xfer = arg; + + DPRINTF("xfer=%p\n", xfer); + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* transfer is transferred */ + at91dci_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +at91dci_start_standard_chain(struct usb_xfer *xfer) +{ + DPRINTFN(9, "\n"); + + /* poll one time */ + if (at91dci_xfer_do_fifo(xfer)) { + + struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus); + uint8_t ep_no = xfer->endpointno & UE_ADDR; + + /* + * Only enable the endpoint interrupt when we are actually + * waiting for data, hence we are dealing with level + * triggered interrupts ! + */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IER, AT91_UDP_INT_EP(ep_no)); + + DPRINTFN(15, "enable interrupts on endpoint %d\n", ep_no); + + /* put transfer on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usbd_transfer_timeout_ms(xfer, + &at91dci_timeout, xfer->timeout); + } + } +} + +static void +at91dci_root_intr(struct at91dci_softc *sc) +{ + DPRINTFN(9, "\n"); + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* set port bit */ + sc->sc_hub_idata[0] = 0x02; /* we only have one port */ + + uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, + sizeof(sc->sc_hub_idata)); +} + +static usb_error_t +at91dci_standard_done_sub(struct usb_xfer *xfer) +{ + struct at91dci_td *td; + uint32_t len; + uint8_t error; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + + do { + len = td->remainder; + + if (xfer->aframes != xfer->nframes) { + /* + * Verify the length and subtract + * the remainder from "frlengths[]": + */ + if (len > xfer->frlengths[xfer->aframes]) { + td->error = 1; + } else { + xfer->frlengths[xfer->aframes] -= len; + } + } + /* Check for transfer error */ + if (td->error) { + /* the transfer is finished */ + error = 1; + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + td = td->obj_next; + } else { + td = NULL; + } + } else { + /* the transfer is finished */ + td = NULL; + } + error = 0; + break; + } + td = td->obj_next; + + /* this USB frame is complete */ + error = 0; + break; + + } while (0); + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + return (error ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); +} + +static void +at91dci_standard_done(struct usb_xfer *xfer) +{ + usb_error_t err = 0; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = at91dci_standard_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = at91dci_standard_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = at91dci_standard_done_sub(xfer); + } +done: + at91dci_device_done(xfer, err); +} + +/*------------------------------------------------------------------------* + * at91dci_device_done + * + * NOTE: this function can be called more than one time on the + * same USB transfer! + *------------------------------------------------------------------------*/ +static void +at91dci_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus); + uint8_t ep_no; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); + + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + ep_no = (xfer->endpointno & UE_ADDR); + + /* disable endpoint interrupt */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, AT91_UDP_INT_EP(ep_no)); + + DPRINTFN(15, "disable interrupts on endpoint %d\n", ep_no); + } + /* dequeue transfer and start next transfer */ + usbd_transfer_done(xfer, error); +} + +static void +at91dci_set_stall(struct usb_device *udev, struct usb_xfer *xfer, + struct usb_endpoint *ep, uint8_t *did_stall) +{ + struct at91dci_softc *sc; + uint32_t csr_val; + uint8_t csr_reg; + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + DPRINTFN(5, "endpoint=%p\n", ep); + + if (xfer) { + /* cancel any ongoing transfers */ + at91dci_device_done(xfer, USB_ERR_STALLED); + } + /* set FORCESTALL */ + sc = AT9100_DCI_BUS2SC(udev->bus); + csr_reg = (ep->edesc->bEndpointAddress & UE_ADDR); + csr_reg = AT91_UDP_CSR(csr_reg); + csr_val = AT91_UDP_READ_4(sc, csr_reg); + AT91_CSR_ACK(csr_val, AT91_UDP_CSR_FORCESTALL); + AT91_UDP_WRITE_4(sc, csr_reg, csr_val); +} + +static void +at91dci_clear_stall_sub(struct at91dci_softc *sc, uint8_t ep_no, + uint8_t ep_type, uint8_t ep_dir) +{ + const struct usb_hw_ep_profile *pf; + uint32_t csr_val; + uint32_t temp; + uint8_t csr_reg; + uint8_t to; + + if (ep_type == UE_CONTROL) { + /* clearing stall is not needed */ + return; + } + /* compute CSR register offset */ + csr_reg = AT91_UDP_CSR(ep_no); + + /* compute default CSR value */ + csr_val = 0; + AT91_CSR_ACK(csr_val, 0); + + /* disable endpoint */ + AT91_UDP_WRITE_4(sc, csr_reg, csr_val); + + /* get endpoint profile */ + at91dci_get_hw_ep_profile(NULL, &pf, ep_no); + + /* reset FIFO */ + AT91_UDP_WRITE_4(sc, AT91_UDP_RST, AT91_UDP_RST_EP(ep_no)); + AT91_UDP_WRITE_4(sc, AT91_UDP_RST, 0); + + /* + * NOTE: One would assume that a FIFO reset would release the + * FIFO banks aswell, but it doesn't! We have to do this + * manually! + */ + + /* release FIFO banks, if any */ + for (to = 0; to != 2; to++) { + + /* get csr value */ + csr_val = AT91_UDP_READ_4(sc, csr_reg); + + if (csr_val & (AT91_UDP_CSR_RX_DATA_BK0 | + AT91_UDP_CSR_RX_DATA_BK1)) { + /* clear status bits */ + if (pf->support_multi_buffer) { + if (sc->sc_ep_flags[ep_no].fifo_bank) { + sc->sc_ep_flags[ep_no].fifo_bank = 0; + temp = AT91_UDP_CSR_RX_DATA_BK1; + } else { + sc->sc_ep_flags[ep_no].fifo_bank = 1; + temp = AT91_UDP_CSR_RX_DATA_BK0; + } + } else { + temp = (AT91_UDP_CSR_RX_DATA_BK0 | + AT91_UDP_CSR_RX_DATA_BK1); + } + } else { + temp = 0; + } + + /* clear FORCESTALL */ + temp |= AT91_UDP_CSR_STALLSENT; + + AT91_CSR_ACK(csr_val, temp); + AT91_UDP_WRITE_4(sc, csr_reg, csr_val); + } + + /* compute default CSR value */ + csr_val = 0; + AT91_CSR_ACK(csr_val, 0); + + /* enable endpoint */ + csr_val &= ~AT91_UDP_CSR_ET_MASK; + csr_val |= AT91_UDP_CSR_EPEDS; + + if (ep_type == UE_CONTROL) { + csr_val |= AT91_UDP_CSR_ET_CTRL; + } else { + if (ep_type == UE_BULK) { + csr_val |= AT91_UDP_CSR_ET_BULK; + } else if (ep_type == UE_INTERRUPT) { + csr_val |= AT91_UDP_CSR_ET_INT; + } else { + csr_val |= AT91_UDP_CSR_ET_ISO; + } + if (ep_dir & UE_DIR_IN) { + csr_val |= AT91_UDP_CSR_ET_DIR_IN; + } + } + + /* enable endpoint */ + AT91_UDP_WRITE_4(sc, AT91_UDP_CSR(ep_no), csr_val); +} + +static void +at91dci_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) +{ + struct at91dci_softc *sc; + struct usb_endpoint_descriptor *ed; + + DPRINTFN(5, "endpoint=%p\n", ep); + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + /* check mode */ + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + /* get softc */ + sc = AT9100_DCI_BUS2SC(udev->bus); + + /* get endpoint descriptor */ + ed = ep->edesc; + + /* reset endpoint */ + at91dci_clear_stall_sub(sc, + (ed->bEndpointAddress & UE_ADDR), + (ed->bmAttributes & UE_XFERTYPE), + (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); +} + +usb_error_t +at91dci_init(struct at91dci_softc *sc) +{ + uint32_t csr_val; + uint8_t n; + + DPRINTF("start\n"); + + /* set up the bus structure */ + sc->sc_bus.usbrev = USB_REV_1_1; + sc->sc_bus.methods = &at91dci_bus_methods; + + USB_BUS_LOCK(&sc->sc_bus); + + /* turn on clocks */ + + if (sc->sc_clocks_on) { + (sc->sc_clocks_on) (sc->sc_clocks_arg); + } + /* wait a little for things to stabilise */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000); + + /* disable and clear all interrupts */ + + AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, 0xFFFFFFFF); + AT91_UDP_WRITE_4(sc, AT91_UDP_ICR, 0xFFFFFFFF); + + /* compute default CSR value */ + + csr_val = 0; + AT91_CSR_ACK(csr_val, 0); + + /* disable all endpoints */ + + for (n = 0; n != AT91_UDP_EP_MAX; n++) { + + /* disable endpoint */ + AT91_UDP_WRITE_4(sc, AT91_UDP_CSR(n), csr_val); + } + + /* enable the control endpoint */ + + AT91_CSR_ACK(csr_val, AT91_UDP_CSR_ET_CTRL | + AT91_UDP_CSR_EPEDS); + + /* write to FIFO control register */ + + AT91_UDP_WRITE_4(sc, AT91_UDP_CSR(0), csr_val); + + /* enable the interrupts we want */ + + AT91_UDP_WRITE_4(sc, AT91_UDP_IER, AT91_UDP_INT_BUS); + + /* turn off clocks */ + + at91dci_clocks_off(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); + + /* catch any lost interrupts */ + + at91dci_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +at91dci_uninit(struct at91dci_softc *sc) +{ + USB_BUS_LOCK(&sc->sc_bus); + + /* disable and clear all interrupts */ + AT91_UDP_WRITE_4(sc, AT91_UDP_IDR, 0xFFFFFFFF); + AT91_UDP_WRITE_4(sc, AT91_UDP_ICR, 0xFFFFFFFF); + + sc->sc_flags.port_powered = 0; + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + at91dci_pull_down(sc); + at91dci_clocks_off(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +at91dci_suspend(struct at91dci_softc *sc) +{ + /* TODO */ +} + +static void +at91dci_resume(struct at91dci_softc *sc) +{ + /* TODO */ +} + +static void +at91dci_do_poll(struct usb_bus *bus) +{ + struct at91dci_softc *sc = AT9100_DCI_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + at91dci_interrupt_poll(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/*------------------------------------------------------------------------* + * at91dci bulk support + *------------------------------------------------------------------------*/ +static void +at91dci_device_bulk_open(struct usb_xfer *xfer) +{ + return; +} + +static void +at91dci_device_bulk_close(struct usb_xfer *xfer) +{ + at91dci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +at91dci_device_bulk_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +at91dci_device_bulk_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + at91dci_setup_standard_chain(xfer); + at91dci_start_standard_chain(xfer); +} + +struct usb_pipe_methods at91dci_device_bulk_methods = +{ + .open = at91dci_device_bulk_open, + .close = at91dci_device_bulk_close, + .enter = at91dci_device_bulk_enter, + .start = at91dci_device_bulk_start, +}; + +/*------------------------------------------------------------------------* + * at91dci control support + *------------------------------------------------------------------------*/ +static void +at91dci_device_ctrl_open(struct usb_xfer *xfer) +{ + return; +} + +static void +at91dci_device_ctrl_close(struct usb_xfer *xfer) +{ + at91dci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +at91dci_device_ctrl_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +at91dci_device_ctrl_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + at91dci_setup_standard_chain(xfer); + at91dci_start_standard_chain(xfer); +} + +struct usb_pipe_methods at91dci_device_ctrl_methods = +{ + .open = at91dci_device_ctrl_open, + .close = at91dci_device_ctrl_close, + .enter = at91dci_device_ctrl_enter, + .start = at91dci_device_ctrl_start, +}; + +/*------------------------------------------------------------------------* + * at91dci interrupt support + *------------------------------------------------------------------------*/ +static void +at91dci_device_intr_open(struct usb_xfer *xfer) +{ + return; +} + +static void +at91dci_device_intr_close(struct usb_xfer *xfer) +{ + at91dci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +at91dci_device_intr_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +at91dci_device_intr_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + at91dci_setup_standard_chain(xfer); + at91dci_start_standard_chain(xfer); +} + +struct usb_pipe_methods at91dci_device_intr_methods = +{ + .open = at91dci_device_intr_open, + .close = at91dci_device_intr_close, + .enter = at91dci_device_intr_enter, + .start = at91dci_device_intr_start, +}; + +/*------------------------------------------------------------------------* + * at91dci full speed isochronous support + *------------------------------------------------------------------------*/ +static void +at91dci_device_isoc_fs_open(struct usb_xfer *xfer) +{ + return; +} + +static void +at91dci_device_isoc_fs_close(struct usb_xfer *xfer) +{ + at91dci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +at91dci_device_isoc_fs_enter(struct usb_xfer *xfer) +{ + struct at91dci_softc *sc = AT9100_DCI_BUS2SC(xfer->xroot->bus); + uint32_t temp; + uint32_t nframes; + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes); + + /* get the current frame index */ + + nframes = AT91_UDP_READ_4(sc, AT91_UDP_FRM); + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + temp = (nframes - xfer->endpoint->isoc_next) & AT91_UDP_FRM_MASK; + + if ((xfer->endpoint->is_synced == 0) || + (temp < xfer->nframes)) { + /* + * If there is data underflow or the endpoint queue is + * empty we schedule the transfer a few frames ahead + * of the current frame position. Else two isochronous + * transfers might overlap. + */ + xfer->endpoint->isoc_next = (nframes + 3) & AT91_UDP_FRM_MASK; + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + temp = (xfer->endpoint->isoc_next - nframes) & AT91_UDP_FRM_MASK; + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + + xfer->nframes; + + /* compute frame number for next insertion */ + xfer->endpoint->isoc_next += xfer->nframes; + + /* setup TDs */ + at91dci_setup_standard_chain(xfer); +} + +static void +at91dci_device_isoc_fs_start(struct usb_xfer *xfer) +{ + /* start TD chain */ + at91dci_start_standard_chain(xfer); +} + +struct usb_pipe_methods at91dci_device_isoc_fs_methods = +{ + .open = at91dci_device_isoc_fs_open, + .close = at91dci_device_isoc_fs_close, + .enter = at91dci_device_isoc_fs_enter, + .start = at91dci_device_isoc_fs_start, +}; + +/*------------------------------------------------------------------------* + * at91dci root control support + *------------------------------------------------------------------------* + * Simulate a hardware HUB by handling all the necessary requests. + *------------------------------------------------------------------------*/ + +static const struct usb_device_descriptor at91dci_devd = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = UDESC_DEVICE, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_FSHUB, + .bMaxPacketSize = 64, + .bcdDevice = {0x00, 0x01}, + .iManufacturer = 1, + .iProduct = 2, + .bNumConfigurations = 1, +}; + +static const struct at91dci_config_desc at91dci_confd = { + .confd = { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(at91dci_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0, + }, + .ifcd = { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = 0, + }, + .endpd = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = (UE_DIR_IN | AT9100_DCI_INTR_ENDPT), + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, + .bInterval = 255, + }, +}; + +static const struct usb_hub_descriptor_min at91dci_hubd = { + .bDescLength = sizeof(at91dci_hubd), + .bDescriptorType = UDESC_HUB, + .bNbrPorts = 1, + .wHubCharacteristics[0] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF, + .wHubCharacteristics[1] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 8, + .bPwrOn2PwrGood = 50, + .bHubContrCurrent = 0, + .DeviceRemovable = {0}, /* port is removable */ +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_VENDOR \ + 'A', 0, 'T', 0, 'M', 0, 'E', 0, 'L', 0 + +#define STRING_PRODUCT \ + 'D', 0, 'C', 0, 'I', 0, ' ', 0, 'R', 0, \ + 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \ + 'U', 0, 'B', 0, + +USB_MAKE_STRING_DESC(STRING_LANG, at91dci_langtab); +USB_MAKE_STRING_DESC(STRING_VENDOR, at91dci_vendor); +USB_MAKE_STRING_DESC(STRING_PRODUCT, at91dci_product); + +static usb_error_t +at91dci_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + struct at91dci_softc *sc = AT9100_DCI_BUS2SC(udev->bus); + const void *ptr; + uint16_t len; + uint16_t value; + uint16_t index; + usb_error_t err; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* buffer reset */ + ptr = (const void *)&sc->sc_hub_temp; + len = 0; + err = 0; + + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + /* demultiplex the control request */ + + switch (req->bmRequestType) { + case UT_READ_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (req->bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_DESCRIPTOR: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SYNCH_FRAME: + goto tr_valid; /* nop */ + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (req->bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_INTERFACE: + switch (req->bRequest) { + case UR_SET_INTERFACE: + goto tr_handle_set_interface; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (req->bRequest) { + case UR_GET_INTERFACE: + goto tr_handle_get_interface; + case UR_GET_STATUS: + goto tr_handle_get_iface_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_INTERFACE: + case UT_WRITE_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_READ_CLASS_INTERFACE: + case UT_READ_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_WRITE_CLASS_DEVICE: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_valid; + case UR_SET_DESCRIPTOR: + case UR_SET_FEATURE: + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_OTHER: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_handle_clear_port_feature; + case UR_SET_FEATURE: + goto tr_handle_set_port_feature; + case UR_CLEAR_TT_BUFFER: + case UR_RESET_TT: + case UR_STOP_TT: + goto tr_valid; + + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_OTHER: + switch (req->bRequest) { + case UR_GET_TT_STATE: + goto tr_handle_get_tt_state; + case UR_GET_STATUS: + goto tr_handle_get_port_status; + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_class_descriptor; + case UR_GET_STATUS: + goto tr_handle_get_class_status; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + goto tr_valid; + +tr_handle_get_descriptor: + switch (value >> 8) { + case UDESC_DEVICE: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(at91dci_devd); + ptr = (const void *)&at91dci_devd; + goto tr_valid; + case UDESC_CONFIG: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(at91dci_confd); + ptr = (const void *)&at91dci_confd; + goto tr_valid; + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + len = sizeof(at91dci_langtab); + ptr = (const void *)&at91dci_langtab; + goto tr_valid; + + case 1: /* Vendor */ + len = sizeof(at91dci_vendor); + ptr = (const void *)&at91dci_vendor; + goto tr_valid; + + case 2: /* Product */ + len = sizeof(at91dci_product); + ptr = (const void *)&at91dci_product; + goto tr_valid; + default: + break; + } + break; + default: + goto tr_stalled; + } + goto tr_stalled; + +tr_handle_get_config: + len = 1; + sc->sc_hub_temp.wValue[0] = sc->sc_conf; + goto tr_valid; + +tr_handle_get_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); + goto tr_valid; + +tr_handle_set_address: + if (value & 0xFF00) { + goto tr_stalled; + } + sc->sc_rt_addr = value; + goto tr_valid; + +tr_handle_set_config: + if (value >= 2) { + goto tr_stalled; + } + sc->sc_conf = value; + goto tr_valid; + +tr_handle_get_interface: + len = 1; + sc->sc_hub_temp.wValue[0] = 0; + goto tr_valid; + +tr_handle_get_tt_state: +tr_handle_get_class_status: +tr_handle_get_iface_status: +tr_handle_get_ep_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, 0); + goto tr_valid; + +tr_handle_set_halt: +tr_handle_set_interface: +tr_handle_set_wakeup: +tr_handle_clear_wakeup: +tr_handle_clear_halt: + goto tr_valid; + +tr_handle_clear_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index); + + switch (value) { + case UHF_PORT_SUSPEND: + at91dci_wakeup_peer(sc); + break; + + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 0; + break; + + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 0; + at91dci_pull_down(sc); + at91dci_clocks_off(sc); + break; + case UHF_C_PORT_CONNECTION: + sc->sc_flags.change_connect = 0; + break; + case UHF_C_PORT_SUSPEND: + sc->sc_flags.change_suspend = 0; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_set_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_SET_PORT_FEATURE\n"); + + switch (value) { + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 1; + break; + case UHF_PORT_SUSPEND: + case UHF_PORT_RESET: + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 1; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_get_port_status: + + DPRINTFN(9, "UR_GET_PORT_STATUS\n"); + + if (index != 1) { + goto tr_stalled; + } + if (sc->sc_flags.status_vbus) { + at91dci_clocks_on(sc); + at91dci_pull_up(sc); + } else { + at91dci_pull_down(sc); + at91dci_clocks_off(sc); + } + + /* Select FULL-speed and Device Side Mode */ + + value = UPS_PORT_MODE_DEVICE; + + if (sc->sc_flags.port_powered) { + value |= UPS_PORT_POWER; + } + if (sc->sc_flags.port_enabled) { + value |= UPS_PORT_ENABLED; + } + if (sc->sc_flags.status_vbus && + sc->sc_flags.status_bus_reset) { + value |= UPS_CURRENT_CONNECT_STATUS; + } + if (sc->sc_flags.status_suspend) { + value |= UPS_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortStatus, value); + + value = 0; + + if (sc->sc_flags.change_connect) { + value |= UPS_C_CONNECT_STATUS; + + if (sc->sc_flags.status_vbus && + sc->sc_flags.status_bus_reset) { + /* reset endpoint flags */ + memset(sc->sc_ep_flags, 0, sizeof(sc->sc_ep_flags)); + } + } + if (sc->sc_flags.change_suspend) { + value |= UPS_C_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortChange, value); + len = sizeof(sc->sc_hub_temp.ps); + goto tr_valid; + +tr_handle_get_class_descriptor: + if (value & 0xFF) { + goto tr_stalled; + } + ptr = (const void *)&at91dci_hubd; + len = sizeof(at91dci_hubd); + goto tr_valid; + +tr_stalled: + err = USB_ERR_STALLED; +tr_valid: +done: + *plength = len; + *pptr = ptr; + return (err); +} + +static void +at91dci_xfer_setup(struct usb_setup_params *parm) +{ + const struct usb_hw_ep_profile *pf; + struct at91dci_softc *sc; + struct usb_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t n; + uint8_t ep_no; + + sc = AT9100_DCI_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + /* + * NOTE: This driver does not use any of the parameters that + * are computed from the following values. Just set some + * reasonable dummies: + */ + parm->hc_max_packet_size = 0x500; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x500; + + usbd_transfer_setup_sub(parm); + + /* + * compute maximum number of TDs + */ + if (parm->methods == &at91dci_device_ctrl_methods) { + + ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */ + + 1 /* SYNC 2 */ ; + + } else if (parm->methods == &at91dci_device_bulk_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else if (parm->methods == &at91dci_device_intr_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else if (parm->methods == &at91dci_device_isoc_fs_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else { + + ntd = 0; + } + + /* + * check if "usbd_transfer_setup_sub" set an error + */ + if (parm->err) { + return; + } + /* + * allocate transfer descriptors + */ + last_obj = NULL; + + /* + * get profile stuff + */ + if (ntd) { + + ep_no = xfer->endpointno & UE_ADDR; + at91dci_get_hw_ep_profile(parm->udev, &pf, ep_no); + + if (pf == NULL) { + /* should not happen */ + parm->err = USB_ERR_INVAL; + return; + } + } else { + ep_no = 0; + pf = NULL; + } + + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + for (n = 0; n != ntd; n++) { + + struct at91dci_td *td; + + if (parm->buf) { + + td = USB_ADD_BYTES(parm->buf, parm->size[0]); + + /* init TD */ + td->io_tag = sc->sc_io_tag; + td->io_hdl = sc->sc_io_hdl; + td->max_packet_size = xfer->max_packet_size; + td->status_reg = AT91_UDP_CSR(ep_no); + td->fifo_reg = AT91_UDP_FDR(ep_no); + if (pf->support_multi_buffer) { + td->support_multi_buffer = 1; + } + td->obj_next = last_obj; + + last_obj = td; + } + parm->size[0] += sizeof(*td); + } + + xfer->td_start[0] = last_obj; +} + +static void +at91dci_xfer_unsetup(struct usb_xfer *xfer) +{ + return; +} + +static void +at91dci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *ep) +{ + struct at91dci_softc *sc = AT9100_DCI_BUS2SC(udev->bus); + + DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", + ep, udev->address, + edesc->bEndpointAddress, udev->flags.usb_mode, + sc->sc_rt_addr); + + if (udev->device_index != sc->sc_rt_addr) { + + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + if (udev->speed != USB_SPEED_FULL) { + /* not supported */ + return; + } + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + ep->methods = &at91dci_device_ctrl_methods; + break; + case UE_INTERRUPT: + ep->methods = &at91dci_device_intr_methods; + break; + case UE_ISOCHRONOUS: + ep->methods = &at91dci_device_isoc_fs_methods; + break; + case UE_BULK: + ep->methods = &at91dci_device_bulk_methods; + break; + default: + /* do nothing */ + break; + } + } +} + +static void +at91dci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) +{ + struct at91dci_softc *sc = AT9100_DCI_BUS2SC(bus); + + switch (state) { + case USB_HW_POWER_SUSPEND: + at91dci_suspend(sc); + break; + case USB_HW_POWER_SHUTDOWN: + at91dci_uninit(sc); + break; + case USB_HW_POWER_RESUME: + at91dci_resume(sc); + break; + default: + break; + } +} + +struct usb_bus_methods at91dci_bus_methods = +{ + .endpoint_init = &at91dci_ep_init, + .xfer_setup = &at91dci_xfer_setup, + .xfer_unsetup = &at91dci_xfer_unsetup, + .get_hw_ep_profile = &at91dci_get_hw_ep_profile, + .set_stall = &at91dci_set_stall, + .clear_stall = &at91dci_clear_stall, + .roothub_exec = &at91dci_roothub_exec, + .xfer_poll = &at91dci_do_poll, + .set_hw_power_sleep = &at91dci_set_hw_power_sleep, +}; diff --git a/sys/bus/u4b/controller/at91dci.h b/sys/bus/u4b/controller/at91dci.h new file mode 100644 index 0000000000..b079eb5b27 --- /dev/null +++ b/sys/bus/u4b/controller/at91dci.h @@ -0,0 +1,241 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2006 ATMEL + * Copyright (c) 2007 Hans Petter Selasky + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * USB Device Port (UDP) register definition, based on "AT91RM9200.h" provided + * by ATMEL. + */ + +#ifndef _AT9100_DCI_H_ +#define _AT9100_DCI_H_ + +#define AT91_MAX_DEVICES (USB_MIN_DEVICES + 1) + +#define AT91_UDP_FRM 0x00 /* Frame number register */ +#define AT91_UDP_FRM_MASK (0x7FF << 0) /* Frame Number as Defined in + * the Packet Field Formats */ +#define AT91_UDP_FRM_ERR (0x1 << 16) /* Frame Error */ +#define AT91_UDP_FRM_OK (0x1 << 17) /* Frame OK */ + +#define AT91_UDP_GSTATE 0x04 /* Global state register */ +#define AT91_UDP_GSTATE_ADDR (0x1 << 0) /* Addressed state */ +#define AT91_UDP_GSTATE_CONFG (0x1 << 1) /* Configured */ +#define AT91_UDP_GSTATE_ESR (0x1 << 2) /* Enable Send Resume */ +#define AT91_UDP_GSTATE_RSM (0x1 << 3) /* A Resume Has Been Sent to + * the Host */ +#define AT91_UDP_GSTATE_RMW (0x1 << 4) /* Remote Wake Up Enable */ + +#define AT91_UDP_FADDR 0x08 /* Function Address Register */ +#define AT91_UDP_FADDR_MASK (0x7F << 0)/* Function Address Mask */ +#define AT91_UDP_FADDR_EN (0x1 << 8)/* Function Enable */ + +#define AT91_UDP_RES0 0x0C /* Reserved 0 */ + +#define AT91_UDP_IER 0x10 /* Interrupt Enable Register */ +#define AT91_UDP_IDR 0x14 /* Interrupt Disable Register */ +#define AT91_UDP_IMR 0x18 /* Interrupt Mask Register */ +#define AT91_UDP_ISR 0x1C /* Interrupt Status Register */ +#define AT91_UDP_ICR 0x20 /* Interrupt Clear Register */ +#define AT91_UDP_INT_EP(n) (0x1 <<(n))/* Endpoint "n" Interrupt */ +#define AT91_UDP_INT_RXSUSP (0x1 << 8)/* USB Suspend Interrupt */ +#define AT91_UDP_INT_RXRSM (0x1 << 9)/* USB Resume Interrupt */ +#define AT91_UDP_INT_EXTRSM (0x1 << 10)/* USB External Resume Interrupt */ +#define AT91_UDP_INT_SOFINT (0x1 << 11)/* USB Start Of frame Interrupt */ +#define AT91_UDP_INT_END_BR (0x1 << 12)/* USB End Of Bus Reset Interrupt */ +#define AT91_UDP_INT_WAKEUP (0x1 << 13)/* USB Resume Interrupt */ + +#define AT91_UDP_INT_BUS \ + (AT91_UDP_INT_RXSUSP|AT91_UDP_INT_RXRSM| \ + AT91_UDP_INT_END_BR) + +#define AT91_UDP_INT_EPS \ + (AT91_UDP_INT_EP(0)|AT91_UDP_INT_EP(1)| \ + AT91_UDP_INT_EP(2)|AT91_UDP_INT_EP(3)| \ + AT91_UDP_INT_EP(4)|AT91_UDP_INT_EP(5)) + +#define AT91_UDP_INT_DEFAULT \ + (AT91_UDP_INT_EPS|AT91_UDP_INT_BUS) + +#define AT91_UDP_RES1 0x24 /* Reserved 1 */ +#define AT91_UDP_RST 0x28 /* Reset Endpoint Register */ +#define AT91_UDP_RST_EP(n) (0x1 << (n))/* Reset Endpoint "n" */ + +#define AT91_UDP_RES2 0x2C /* Reserved 2 */ + +#define AT91_UDP_CSR(n) (0x30 + (4*(n)))/* Endpoint Control and Status + * Register */ +#define AT91_UDP_CSR_TXCOMP (0x1 << 0) /* Generates an IN packet with data + * previously written in the DPR */ +#define AT91_UDP_CSR_RX_DATA_BK0 (0x1 << 1) /* Receive Data Bank 0 */ +#define AT91_UDP_CSR_RXSETUP (0x1 << 2) /* Sends STALL to the Host + * (Control endpoints) */ +#define AT91_UDP_CSR_ISOERROR (0x1 << 3) /* Isochronous error + * (Isochronous endpoints) */ +#define AT91_UDP_CSR_STALLSENT (0x1 << 3) /* Stall sent (Control, bulk, + * interrupt endpoints) */ +#define AT91_UDP_CSR_TXPKTRDY (0x1 << 4) /* Transmit Packet Ready */ +#define AT91_UDP_CSR_FORCESTALL (0x1 << 5) /* Force Stall (used by + * Control, Bulk and + * Isochronous endpoints). */ +#define AT91_UDP_CSR_RX_DATA_BK1 (0x1 << 6) /* Receive Data Bank 1 (only + * used by endpoints with + * ping-pong attributes). */ +#define AT91_UDP_CSR_DIR (0x1 << 7) /* Transfer Direction */ +#define AT91_UDP_CSR_ET_MASK (0x7 << 8) /* Endpoint transfer type mask */ +#define AT91_UDP_CSR_ET_CTRL (0x0 << 8) /* Control IN+OUT */ +#define AT91_UDP_CSR_ET_ISO (0x1 << 8) /* Isochronous */ +#define AT91_UDP_CSR_ET_BULK (0x2 << 8) /* Bulk */ +#define AT91_UDP_CSR_ET_INT (0x3 << 8) /* Interrupt */ +#define AT91_UDP_CSR_ET_DIR_OUT (0x0 << 8) /* OUT tokens */ +#define AT91_UDP_CSR_ET_DIR_IN (0x4 << 8) /* IN tokens */ +#define AT91_UDP_CSR_DTGLE (0x1 << 11) /* Data Toggle */ +#define AT91_UDP_CSR_EPEDS (0x1 << 15) /* Endpoint Enable Disable */ +#define AT91_UDP_CSR_RXBYTECNT (0x7FF << 16) /* Number Of Bytes Available + * in the FIFO */ + +#define AT91_UDP_FDR(n) (0x50 + (4*(n)))/* Endpoint FIFO Data Register */ +#define AT91_UDP_RES3 0x70 /* Reserved 3 */ +#define AT91_UDP_TXVC 0x74 /* Transceiver Control Register */ +#define AT91_UDP_TXVC_DIS (0x1 << 8) + +#define AT91_UDP_EP_MAX 6 /* maximum number of endpoints + * supported */ + +#define AT91_UDP_READ_4(sc, reg) \ + bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) + +#define AT91_UDP_WRITE_4(sc, reg, data) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data) + +struct at91dci_td; + +typedef uint8_t (at91dci_cmd_t)(struct at91dci_td *td); + +struct at91dci_td { + bus_space_tag_t io_tag; + bus_space_handle_t io_hdl; + struct at91dci_td *obj_next; + at91dci_cmd_t *func; + struct usb_page_cache *pc; + uint32_t offset; + uint32_t remainder; + uint16_t max_packet_size; + uint8_t status_reg; + uint8_t fifo_reg; + uint8_t fifo_bank:1; + uint8_t error:1; + uint8_t alt_next:1; + uint8_t short_pkt:1; + uint8_t support_multi_buffer:1; + uint8_t did_stall:1; +}; + +struct at91dci_std_temp { + at91dci_cmd_t *func; + struct usb_page_cache *pc; + struct at91dci_td *td; + struct at91dci_td *td_next; + uint32_t len; + uint32_t offset; + uint16_t max_frame_size; + uint8_t short_pkt; + /* + * short_pkt = 0: transfer should be short terminated + * short_pkt = 1: transfer should not be short terminated + */ + uint8_t setup_alt_next; + uint8_t did_stall; +}; + +struct at91dci_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union at91dci_hub_temp { + uWord wValue; + struct usb_port_status ps; +}; + +struct at91dci_ep_flags { + uint8_t fifo_bank:1; /* hardware specific */ +}; + +struct at91dci_flags { + uint8_t change_connect:1; + uint8_t change_suspend:1; + uint8_t status_suspend:1; /* set if suspended */ + uint8_t status_vbus:1; /* set if present */ + uint8_t status_bus_reset:1; /* set if reset complete */ + uint8_t remote_wakeup:1; + uint8_t self_powered:1; + uint8_t clocks_off:1; + uint8_t port_powered:1; + uint8_t port_enabled:1; + uint8_t d_pulled_up:1; +}; + +struct at91dci_softc { + struct usb_bus sc_bus; + union at91dci_hub_temp sc_hub_temp; + + struct usb_device *sc_devices[AT91_MAX_DEVICES]; + struct resource *sc_io_res; + struct resource *sc_irq_res; + void *sc_intr_hdl; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + void (*sc_clocks_on) (void *arg); + void (*sc_clocks_off) (void *arg); + void *sc_clocks_arg; + + void (*sc_pull_up) (void *arg); + void (*sc_pull_down) (void *arg); + void *sc_pull_arg; + + uint8_t sc_rt_addr; /* root HUB address */ + uint8_t sc_dv_addr; /* device address */ + uint8_t sc_conf; /* root HUB config */ + + uint8_t sc_hub_idata[1]; + + struct at91dci_flags sc_flags; + struct at91dci_ep_flags sc_ep_flags[AT91_UDP_EP_MAX]; +}; + +/* prototypes */ + +usb_error_t at91dci_init(struct at91dci_softc *sc); +void at91dci_uninit(struct at91dci_softc *sc); +void at91dci_interrupt(struct at91dci_softc *sc); +void at91dci_vbus_interrupt(struct at91dci_softc *sc, uint8_t is_on); + +#endif /* _AT9100_DCI_H_ */ diff --git a/sys/bus/u4b/controller/at91dci_atmelarm.c b/sys/bus/u4b/controller/at91dci_atmelarm.c new file mode 100644 index 0000000000..da9ba36e63 --- /dev/null +++ b/sys/bus/u4b/controller/at91dci_atmelarm.c @@ -0,0 +1,346 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2007-2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#define MEM_RID 0 + +/* Pin Definitions - do they belong here or somewhere else ? */ + +#define VBUS_MASK AT91C_PIO_PB24 +#define VBUS_BASE AT91RM92_PIOB_BASE + +#define PULLUP_MASK AT91C_PIO_PB22 +#define PULLUP_BASE AT91RM92_PIOB_BASE + +static device_probe_t at91_udp_probe; +static device_attach_t at91_udp_attach; +static device_detach_t at91_udp_detach; + +struct at91_udp_softc { + struct at91dci_softc sc_dci; /* must be first */ + struct at91_pmc_clock *sc_iclk; + struct at91_pmc_clock *sc_fclk; + struct resource *sc_vbus_irq_res; + void *sc_vbus_intr_hdl; +}; + +static void +at91_vbus_poll(struct at91_udp_softc *sc) +{ + uint32_t temp; + uint8_t vbus_val; + + /* XXX temporary clear interrupts here */ + + temp = at91_pio_gpio_clear_interrupt(VBUS_BASE); + + /* just forward it */ + + vbus_val = at91_pio_gpio_get(VBUS_BASE, VBUS_MASK); + at91dci_vbus_interrupt(&sc->sc_dci, vbus_val); +} + +static void +at91_udp_clocks_on(void *arg) +{ + struct at91_udp_softc *sc = arg; + + at91_pmc_clock_enable(sc->sc_iclk); + at91_pmc_clock_enable(sc->sc_fclk); +} + +static void +at91_udp_clocks_off(void *arg) +{ + struct at91_udp_softc *sc = arg; + + at91_pmc_clock_disable(sc->sc_fclk); + at91_pmc_clock_disable(sc->sc_iclk); +} + +static void +at91_udp_pull_up(void *arg) +{ + at91_pio_gpio_set(PULLUP_BASE, PULLUP_MASK); +} + +static void +at91_udp_pull_down(void *arg) +{ + at91_pio_gpio_clear(PULLUP_BASE, PULLUP_MASK); +} + +static int +at91_udp_probe(device_t dev) +{ + device_set_desc(dev, "AT91 integrated AT91_UDP controller"); + return (0); +} + +static int +at91_udp_attach(device_t dev) +{ + struct at91_udp_softc *sc = device_get_softc(dev); + int err; + int rid; + + /* setup AT9100 USB device controller interface softc */ + + sc->sc_dci.sc_clocks_on = &at91_udp_clocks_on; + sc->sc_dci.sc_clocks_off = &at91_udp_clocks_off; + sc->sc_dci.sc_clocks_arg = sc; + sc->sc_dci.sc_pull_up = &at91_udp_pull_up; + sc->sc_dci.sc_pull_down = &at91_udp_pull_down; + sc->sc_dci.sc_pull_arg = sc; + + /* initialise some bus fields */ + sc->sc_dci.sc_bus.parent = dev; + sc->sc_dci.sc_bus.devices = sc->sc_dci.sc_devices; + sc->sc_dci.sc_bus.devices_max = AT91_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_dci.sc_bus, + USB_GET_DMA_TAG(dev), NULL)) { + return (ENOMEM); + } + /* + * configure VBUS input pin, enable deglitch and enable + * interrupt : + */ + at91_pio_use_gpio(VBUS_BASE, VBUS_MASK); + at91_pio_gpio_input(VBUS_BASE, VBUS_MASK); + at91_pio_gpio_set_deglitch(VBUS_BASE, VBUS_MASK, 1); + at91_pio_gpio_set_interrupt(VBUS_BASE, VBUS_MASK, 1); + + /* + * configure PULLUP output pin : + */ + at91_pio_use_gpio(PULLUP_BASE, PULLUP_MASK); + at91_pio_gpio_output(PULLUP_BASE, PULLUP_MASK, 0); + + at91_udp_pull_down(sc); + + /* wait 10ms for pulldown to stabilise */ + usb_pause_mtx(NULL, hz / 100); + + sc->sc_iclk = at91_pmc_clock_ref("udc_clk"); + sc->sc_fclk = at91_pmc_clock_ref("udpck"); + + rid = MEM_RID; + sc->sc_dci.sc_io_res = + bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); + + if (!(sc->sc_dci.sc_io_res)) { + err = ENOMEM; + goto error; + } + sc->sc_dci.sc_io_tag = rman_get_bustag(sc->sc_dci.sc_io_res); + sc->sc_dci.sc_io_hdl = rman_get_bushandle(sc->sc_dci.sc_io_res); + sc->sc_dci.sc_io_size = rman_get_size(sc->sc_dci.sc_io_res); + + rid = 0; + sc->sc_dci.sc_irq_res = + bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); + if (!(sc->sc_dci.sc_irq_res)) { + goto error; + } + rid = 1; + sc->sc_vbus_irq_res = + bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); + if (!(sc->sc_vbus_irq_res)) { + goto error; + } + sc->sc_dci.sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (!(sc->sc_dci.sc_bus.bdev)) { + goto error; + } + device_set_ivars(sc->sc_dci.sc_bus.bdev, &sc->sc_dci.sc_bus); + +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(dev, sc->sc_dci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)at91dci_interrupt, sc, &sc->sc_dci.sc_intr_hdl); +#else + err = bus_setup_intr(dev, sc->sc_dci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (driver_intr_t *)at91dci_interrupt, sc, &sc->sc_dci.sc_intr_hdl); +#endif + if (err) { + sc->sc_dci.sc_intr_hdl = NULL; + goto error; + } +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(dev, sc->sc_vbus_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)at91_vbus_poll, sc, &sc->sc_vbus_intr_hdl); +#else + err = bus_setup_intr(dev, sc->sc_vbus_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (driver_intr_t *)at91_vbus_poll, sc, &sc->sc_vbus_intr_hdl); +#endif + if (err) { + sc->sc_vbus_intr_hdl = NULL; + goto error; + } + err = at91dci_init(&sc->sc_dci); + if (!err) { + err = device_probe_and_attach(sc->sc_dci.sc_bus.bdev); + } + if (err) { + goto error; + } else { + /* poll VBUS one time */ + at91_vbus_poll(sc); + } + return (0); + +error: + at91_udp_detach(dev); + return (ENXIO); +} + +static int +at91_udp_detach(device_t dev) +{ + struct at91_udp_softc *sc = device_get_softc(dev); + device_t bdev; + int err; + + if (sc->sc_dci.sc_bus.bdev) { + bdev = sc->sc_dci.sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(dev); + + /* disable Transceiver */ + AT91_UDP_WRITE_4(&sc->sc_dci, AT91_UDP_TXVC, AT91_UDP_TXVC_DIS); + + /* disable and clear all interrupts */ + AT91_UDP_WRITE_4(&sc->sc_dci, AT91_UDP_IDR, 0xFFFFFFFF); + AT91_UDP_WRITE_4(&sc->sc_dci, AT91_UDP_ICR, 0xFFFFFFFF); + + /* disable VBUS interrupt */ + at91_pio_gpio_set_interrupt(VBUS_BASE, VBUS_MASK, 0); + + if (sc->sc_vbus_irq_res && sc->sc_vbus_intr_hdl) { + err = bus_teardown_intr(dev, sc->sc_vbus_irq_res, + sc->sc_vbus_intr_hdl); + sc->sc_vbus_intr_hdl = NULL; + } + if (sc->sc_vbus_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 1, + sc->sc_vbus_irq_res); + sc->sc_vbus_irq_res = NULL; + } + if (sc->sc_dci.sc_irq_res && sc->sc_dci.sc_intr_hdl) { + /* + * only call at91_udp_uninit() after at91_udp_init() + */ + at91dci_uninit(&sc->sc_dci); + + err = bus_teardown_intr(dev, sc->sc_dci.sc_irq_res, + sc->sc_dci.sc_intr_hdl); + sc->sc_dci.sc_intr_hdl = NULL; + } + if (sc->sc_dci.sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 0, + sc->sc_dci.sc_irq_res); + sc->sc_dci.sc_irq_res = NULL; + } + if (sc->sc_dci.sc_io_res) { + bus_release_resource(dev, SYS_RES_MEMORY, MEM_RID, + sc->sc_dci.sc_io_res); + sc->sc_dci.sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_dci.sc_bus, NULL); + + /* disable clocks */ + at91_pmc_clock_disable(sc->sc_iclk); + at91_pmc_clock_disable(sc->sc_fclk); + at91_pmc_clock_deref(sc->sc_fclk); + at91_pmc_clock_deref(sc->sc_iclk); + + return (0); +} + +static device_method_t at91_udp_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, at91_udp_probe), + DEVMETHOD(device_attach, at91_udp_attach), + DEVMETHOD(device_detach, at91_udp_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD_END +}; + +static driver_t at91_udp_driver = { + .name = "at91_udp", + .methods = at91_udp_methods, + .size = sizeof(struct at91_udp_softc), +}; + +static devclass_t at91_udp_devclass; + +DRIVER_MODULE(at91_udp, atmelarm, at91_udp_driver, at91_udp_devclass, 0, 0); diff --git a/sys/bus/u4b/controller/atmegadci.c b/sys/bus/u4b/controller/atmegadci.c new file mode 100644 index 0000000000..7db3bdbb44 --- /dev/null +++ b/sys/bus/u4b/controller/atmegadci.c @@ -0,0 +1,2160 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2009 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains the driver for the ATMEGA series USB OTG Controller. This + * driver currently only supports the DCI mode of the USB hardware. + */ + +/* + * NOTE: When the chip detects BUS-reset it will also reset the + * endpoints, Function-address and more. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define USB_DEBUG_VAR atmegadci_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define ATMEGA_BUS2SC(bus) \ + ((struct atmegadci_softc *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((struct atmegadci_softc *)0)->sc_bus)))) + +#define ATMEGA_PC2SC(pc) \ + ATMEGA_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus) + +#ifdef USB_DEBUG +static int atmegadci_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, atmegadci, CTLFLAG_RW, 0, + "USB ATMEGA DCI"); +SYSCTL_INT(_hw_usb_atmegadci, OID_AUTO, debug, CTLFLAG_RW, + &atmegadci_debug, 0, "ATMEGA DCI debug level"); +#endif + +#define ATMEGA_INTR_ENDPT 1 + +/* prototypes */ + +struct usb_bus_methods atmegadci_bus_methods; +struct usb_pipe_methods atmegadci_device_non_isoc_methods; +struct usb_pipe_methods atmegadci_device_isoc_fs_methods; + +static atmegadci_cmd_t atmegadci_setup_rx; +static atmegadci_cmd_t atmegadci_data_rx; +static atmegadci_cmd_t atmegadci_data_tx; +static atmegadci_cmd_t atmegadci_data_tx_sync; +static void atmegadci_device_done(struct usb_xfer *, usb_error_t); +static void atmegadci_do_poll(struct usb_bus *); +static void atmegadci_standard_done(struct usb_xfer *); +static void atmegadci_root_intr(struct atmegadci_softc *sc); + +/* + * Here is a list of what the chip supports: + */ +static const struct usb_hw_ep_profile + atmegadci_ep_profile[2] = { + + [0] = { + .max_in_frame_size = 64, + .max_out_frame_size = 64, + .is_simplex = 1, + .support_control = 1, + }, + [1] = { + .max_in_frame_size = 64, + .max_out_frame_size = 64, + .is_simplex = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, +}; + +static void +atmegadci_get_hw_ep_profile(struct usb_device *udev, + const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) +{ + if (ep_addr == 0) + *ppf = atmegadci_ep_profile; + else if (ep_addr < ATMEGA_EP_MAX) + *ppf = atmegadci_ep_profile + 1; + else + *ppf = NULL; +} + +static void +atmegadci_clocks_on(struct atmegadci_softc *sc) +{ + if (sc->sc_flags.clocks_off && + sc->sc_flags.port_powered) { + + DPRINTFN(5, "\n"); + + /* turn on clocks */ + (sc->sc_clocks_on) (&sc->sc_bus); + + ATMEGA_WRITE_1(sc, ATMEGA_USBCON, + ATMEGA_USBCON_USBE | + ATMEGA_USBCON_OTGPADE | + ATMEGA_USBCON_VBUSTE); + + sc->sc_flags.clocks_off = 0; + + /* enable transceiver ? */ + } +} + +static void +atmegadci_clocks_off(struct atmegadci_softc *sc) +{ + if (!sc->sc_flags.clocks_off) { + + DPRINTFN(5, "\n"); + + /* disable Transceiver ? */ + + ATMEGA_WRITE_1(sc, ATMEGA_USBCON, + ATMEGA_USBCON_USBE | + ATMEGA_USBCON_OTGPADE | + ATMEGA_USBCON_FRZCLK | + ATMEGA_USBCON_VBUSTE); + + /* turn clocks off */ + (sc->sc_clocks_off) (&sc->sc_bus); + + sc->sc_flags.clocks_off = 1; + } +} + +static void +atmegadci_pull_up(struct atmegadci_softc *sc) +{ + /* pullup D+, if possible */ + + if (!sc->sc_flags.d_pulled_up && + sc->sc_flags.port_powered) { + sc->sc_flags.d_pulled_up = 1; + ATMEGA_WRITE_1(sc, ATMEGA_UDCON, 0); + } +} + +static void +atmegadci_pull_down(struct atmegadci_softc *sc) +{ + /* pulldown D+, if possible */ + + if (sc->sc_flags.d_pulled_up) { + sc->sc_flags.d_pulled_up = 0; + ATMEGA_WRITE_1(sc, ATMEGA_UDCON, ATMEGA_UDCON_DETACH); + } +} + +static void +atmegadci_wakeup_peer(struct atmegadci_softc *sc) +{ + uint8_t temp; + + if (!sc->sc_flags.status_suspend) { + return; + } + + temp = ATMEGA_READ_1(sc, ATMEGA_UDCON); + ATMEGA_WRITE_1(sc, ATMEGA_UDCON, temp | ATMEGA_UDCON_RMWKUP); + + /* wait 8 milliseconds */ + /* Wait for reset to complete. */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125); + + /* hardware should have cleared RMWKUP bit */ +} + +static void +atmegadci_set_address(struct atmegadci_softc *sc, uint8_t addr) +{ + DPRINTFN(5, "addr=%d\n", addr); + + addr |= ATMEGA_UDADDR_ADDEN; + + ATMEGA_WRITE_1(sc, ATMEGA_UDADDR, addr); +} + +static uint8_t +atmegadci_setup_rx(struct atmegadci_td *td) +{ + struct atmegadci_softc *sc; + struct usb_device_request req; + uint16_t count; + uint8_t temp; + + /* get pointer to softc */ + sc = ATMEGA_PC2SC(td->pc); + + /* select endpoint number */ + ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no); + + /* check endpoint status */ + temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX); + + DPRINTFN(5, "UEINTX=0x%02x\n", temp); + + if (!(temp & ATMEGA_UEINTX_RXSTPI)) { + goto not_complete; + } + /* clear did stall */ + td->did_stall = 0; + /* get the packet byte count */ + count = + (ATMEGA_READ_1(sc, ATMEGA_UEBCHX) << 8) | + (ATMEGA_READ_1(sc, ATMEGA_UEBCLX)); + + /* mask away undefined bits */ + count &= 0x7FF; + + /* verify data length */ + if (count != td->remainder) { + DPRINTFN(0, "Invalid SETUP packet " + "length, %d bytes\n", count); + goto not_complete; + } + if (count != sizeof(req)) { + DPRINTFN(0, "Unsupported SETUP packet " + "length, %d bytes\n", count); + goto not_complete; + } + /* receive data */ + ATMEGA_READ_MULTI_1(sc, ATMEGA_UEDATX, + (void *)&req, sizeof(req)); + + /* copy data into real buffer */ + usbd_copy_in(td->pc, 0, &req, sizeof(req)); + + td->offset = sizeof(req); + td->remainder = 0; + + /* sneak peek the set address */ + if ((req.bmRequestType == UT_WRITE_DEVICE) && + (req.bRequest == UR_SET_ADDRESS)) { + sc->sc_dv_addr = req.wValue[0] & 0x7F; + /* must write address before ZLP */ + ATMEGA_WRITE_1(sc, ATMEGA_UDADDR, sc->sc_dv_addr); + } else { + sc->sc_dv_addr = 0xFF; + } + + /* Clear SETUP packet interrupt and all other previous interrupts */ + ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0); + return (0); /* complete */ + +not_complete: + /* abort any ongoing transfer */ + if (!td->did_stall) { + DPRINTFN(5, "stalling\n"); + ATMEGA_WRITE_1(sc, ATMEGA_UECONX, + ATMEGA_UECONX_EPEN | + ATMEGA_UECONX_STALLRQ); + td->did_stall = 1; + } + if (temp & ATMEGA_UEINTX_RXSTPI) { + /* clear SETUP packet interrupt */ + ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ~ATMEGA_UEINTX_RXSTPI); + } + /* we only want to know if there is a SETUP packet */ + ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, ATMEGA_UEIENX_RXSTPE); + return (1); /* not complete */ +} + +static uint8_t +atmegadci_data_rx(struct atmegadci_td *td) +{ + struct atmegadci_softc *sc; + struct usb_page_search buf_res; + uint16_t count; + uint8_t temp; + uint8_t to; + uint8_t got_short; + + to = 3; /* don't loop forever! */ + got_short = 0; + + /* get pointer to softc */ + sc = ATMEGA_PC2SC(td->pc); + + /* select endpoint number */ + ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no); + +repeat: + /* check if any of the FIFO banks have data */ + /* check endpoint status */ + temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX); + + DPRINTFN(5, "temp=0x%02x rem=%u\n", temp, td->remainder); + + if (temp & ATMEGA_UEINTX_RXSTPI) { + if (td->remainder == 0) { + /* + * We are actually complete and have + * received the next SETUP + */ + DPRINTFN(5, "faking complete\n"); + return (0); /* complete */ + } + /* + * USB Host Aborted the transfer. + */ + td->error = 1; + return (0); /* complete */ + } + /* check status */ + if (!(temp & (ATMEGA_UEINTX_FIFOCON | + ATMEGA_UEINTX_RXOUTI))) { + /* no data */ + goto not_complete; + } + /* get the packet byte count */ + count = + (ATMEGA_READ_1(sc, ATMEGA_UEBCHX) << 8) | + (ATMEGA_READ_1(sc, ATMEGA_UEBCLX)); + + /* mask away undefined bits */ + count &= 0x7FF; + + /* verify the packet byte count */ + if (count != td->max_packet_size) { + if (count < td->max_packet_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + } + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + while (count > 0) { + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* receive data */ + ATMEGA_READ_MULTI_1(sc, ATMEGA_UEDATX, + buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* clear OUT packet interrupt */ + ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ATMEGA_UEINTX_RXOUTI ^ 0xFF); + + /* release FIFO bank */ + ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ATMEGA_UEINTX_FIFOCON ^ 0xFF); + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) { + /* we are complete */ + return (0); + } + /* else need to receive a zero length packet */ + } + if (--to) { + goto repeat; + } +not_complete: + /* we only want to know if there is a SETUP packet or OUT packet */ + ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, + ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_RXOUTE); + return (1); /* not complete */ +} + +static uint8_t +atmegadci_data_tx(struct atmegadci_td *td) +{ + struct atmegadci_softc *sc; + struct usb_page_search buf_res; + uint16_t count; + uint8_t to; + uint8_t temp; + + to = 3; /* don't loop forever! */ + + /* get pointer to softc */ + sc = ATMEGA_PC2SC(td->pc); + + /* select endpoint number */ + ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no); + +repeat: + + /* check endpoint status */ + temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX); + + DPRINTFN(5, "temp=0x%02x rem=%u\n", temp, td->remainder); + + if (temp & ATMEGA_UEINTX_RXSTPI) { + /* + * The current transfer was aborted + * by the USB Host + */ + td->error = 1; + return (0); /* complete */ + } + + temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X); + if (temp & 3) { + /* cannot write any data - a bank is busy */ + goto not_complete; + } + + count = td->max_packet_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + while (count > 0) { + + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* transmit data */ + ATMEGA_WRITE_MULTI_1(sc, ATMEGA_UEDATX, + buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* clear IN packet interrupt */ + ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0xFF ^ ATMEGA_UEINTX_TXINI); + + /* allocate FIFO bank */ + ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0xFF ^ ATMEGA_UEINTX_FIFOCON); + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) { + return (0); /* complete */ + } + /* else we need to transmit a short packet */ + } + if (--to) { + goto repeat; + } +not_complete: + /* we only want to know if there is a SETUP packet or free IN packet */ + ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, + ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_TXINE); + return (1); /* not complete */ +} + +static uint8_t +atmegadci_data_tx_sync(struct atmegadci_td *td) +{ + struct atmegadci_softc *sc; + uint8_t temp; + + /* get pointer to softc */ + sc = ATMEGA_PC2SC(td->pc); + + /* select endpoint number */ + ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no); + + /* check endpoint status */ + temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX); + + DPRINTFN(5, "temp=0x%02x\n", temp); + + if (temp & ATMEGA_UEINTX_RXSTPI) { + DPRINTFN(5, "faking complete\n"); + /* Race condition */ + return (0); /* complete */ + } + /* + * The control endpoint has only got one bank, so if that bank + * is free the packet has been transferred! + */ + temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X); + if (temp & 3) { + /* cannot write any data - a bank is busy */ + goto not_complete; + } + if (sc->sc_dv_addr != 0xFF) { + /* set new address */ + atmegadci_set_address(sc, sc->sc_dv_addr); + } + return (0); /* complete */ + +not_complete: + /* we only want to know if there is a SETUP packet or free IN packet */ + ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, + ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_TXINE); + return (1); /* not complete */ +} + +static uint8_t +atmegadci_xfer_do_fifo(struct usb_xfer *xfer) +{ + struct atmegadci_td *td; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + while (1) { + if ((td->func) (td)) { + /* operation in progress */ + break; + } + if (((void *)td) == xfer->td_transfer_last) { + goto done; + } + if (td->error) { + goto done; + } else if (td->remainder > 0) { + /* + * We had a short transfer. If there is no alternate + * next, stop processing ! + */ + if (!td->alt_next) { + goto done; + } + } + /* + * Fetch the next transfer descriptor and transfer + * some flags to the next transfer descriptor + */ + td = td->obj_next; + xfer->td_transfer_cache = td; + } + return (1); /* not complete */ + +done: + /* compute all actual lengths */ + + atmegadci_standard_done(xfer); + return (0); /* complete */ +} + +static void +atmegadci_interrupt_poll(struct atmegadci_softc *sc) +{ + struct usb_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + if (!atmegadci_xfer_do_fifo(xfer)) { + /* queue has been modified */ + goto repeat; + } + } +} + +static void +atmegadci_vbus_interrupt(struct atmegadci_softc *sc, uint8_t is_on) +{ + DPRINTFN(5, "vbus = %u\n", is_on); + + if (is_on) { + if (!sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 1; + + /* complete root HUB interrupt endpoint */ + + atmegadci_root_intr(sc); + } + } else { + if (sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* complete root HUB interrupt endpoint */ + + atmegadci_root_intr(sc); + } + } +} + +void +atmegadci_interrupt(struct atmegadci_softc *sc) +{ + uint8_t status; + + USB_BUS_LOCK(&sc->sc_bus); + + /* read interrupt status */ + status = ATMEGA_READ_1(sc, ATMEGA_UDINT); + + /* clear all set interrupts */ + ATMEGA_WRITE_1(sc, ATMEGA_UDINT, (~status) & 0x7D); + + DPRINTFN(14, "UDINT=0x%02x\n", status); + + /* check for any bus state change interrupts */ + if (status & ATMEGA_UDINT_EORSTI) { + + DPRINTFN(5, "end of reset\n"); + + /* set correct state */ + sc->sc_flags.status_bus_reset = 1; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* disable resume interrupt */ + ATMEGA_WRITE_1(sc, ATMEGA_UDIEN, + ATMEGA_UDINT_SUSPE | + ATMEGA_UDINT_EORSTE); + + /* complete root HUB interrupt endpoint */ + atmegadci_root_intr(sc); + } + /* + * If resume and suspend is set at the same time we interpret + * that like RESUME. Resume is set when there is at least 3 + * milliseconds of inactivity on the USB BUS. + */ + if (status & ATMEGA_UDINT_WAKEUPI) { + + DPRINTFN(5, "resume interrupt\n"); + + if (sc->sc_flags.status_suspend) { + /* update status bits */ + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 1; + + /* disable resume interrupt */ + ATMEGA_WRITE_1(sc, ATMEGA_UDIEN, + ATMEGA_UDINT_SUSPE | + ATMEGA_UDINT_EORSTE); + + /* complete root HUB interrupt endpoint */ + atmegadci_root_intr(sc); + } + } else if (status & ATMEGA_UDINT_SUSPI) { + + DPRINTFN(5, "suspend interrupt\n"); + + if (!sc->sc_flags.status_suspend) { + /* update status bits */ + sc->sc_flags.status_suspend = 1; + sc->sc_flags.change_suspend = 1; + + /* disable suspend interrupt */ + ATMEGA_WRITE_1(sc, ATMEGA_UDIEN, + ATMEGA_UDINT_WAKEUPE | + ATMEGA_UDINT_EORSTE); + + /* complete root HUB interrupt endpoint */ + atmegadci_root_intr(sc); + } + } + /* check VBUS */ + status = ATMEGA_READ_1(sc, ATMEGA_USBINT); + + /* clear all set interrupts */ + ATMEGA_WRITE_1(sc, ATMEGA_USBINT, (~status) & 0x03); + + if (status & ATMEGA_USBINT_VBUSTI) { + uint8_t temp; + + DPRINTFN(5, "USBINT=0x%02x\n", status); + + temp = ATMEGA_READ_1(sc, ATMEGA_USBSTA); + atmegadci_vbus_interrupt(sc, temp & ATMEGA_USBSTA_VBUS); + } + /* check for any endpoint interrupts */ + status = ATMEGA_READ_1(sc, ATMEGA_UEINT); + /* the hardware will clear the UEINT bits automatically */ + if (status) { + + DPRINTFN(5, "real endpoint interrupt UEINT=0x%02x\n", status); + + atmegadci_interrupt_poll(sc); + } + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +atmegadci_setup_standard_chain_sub(struct atmegadci_std_temp *temp) +{ + struct atmegadci_td *td; + + /* get current Transfer Descriptor */ + td = temp->td_next; + temp->td = td; + + /* prepare for next TD */ + temp->td_next = td->obj_next; + + /* fill out the Transfer Descriptor */ + td->func = temp->func; + td->pc = temp->pc; + td->offset = temp->offset; + td->remainder = temp->len; + td->error = 0; + td->did_stall = temp->did_stall; + td->short_pkt = temp->short_pkt; + td->alt_next = temp->setup_alt_next; +} + +static void +atmegadci_setup_standard_chain(struct usb_xfer *xfer) +{ + struct atmegadci_std_temp temp; + struct atmegadci_softc *sc; + struct atmegadci_td *td; + uint32_t x; + uint8_t ep_no; + uint8_t need_sync; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpointno), + xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); + + temp.max_frame_size = xfer->max_frame_size; + + td = xfer->td_start[0]; + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + /* setup temp */ + + temp.pc = NULL; + temp.td = NULL; + temp.td_next = xfer->td_start[0]; + temp.offset = 0; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.did_stall = !xfer->flags_int.control_stall; + + sc = ATMEGA_BUS2SC(xfer->xroot->bus); + ep_no = (xfer->endpointno & UE_ADDR); + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.func = &atmegadci_setup_rx; + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.short_pkt = temp.len ? 1 : 0; + /* check for last frame */ + if (xfer->nframes == 1) { + /* no STATUS stage yet, SETUP is last */ + if (xfer->flags_int.control_act) + temp.setup_alt_next = 0; + } + + atmegadci_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + if (x != xfer->nframes) { + if (xfer->endpointno & UE_DIR_IN) { + temp.func = &atmegadci_data_tx; + need_sync = 1; + } else { + temp.func = &atmegadci_data_rx; + need_sync = 0; + } + + /* setup "pc" pointer */ + temp.pc = xfer->frbuffers + x; + } else { + need_sync = 0; + } + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + + x++; + + if (x == xfer->nframes) { + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_act) { + temp.setup_alt_next = 0; + } + } else { + temp.setup_alt_next = 0; + } + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.short_pkt = 0; + + } else { + + /* regular data transfer */ + + temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + atmegadci_setup_standard_chain_sub(&temp); + + if (xfer->flags_int.isochronous_xfr) { + temp.offset += temp.len; + } else { + /* get next Page Cache pointer */ + temp.pc = xfer->frbuffers + x; + } + } + + if (xfer->flags_int.control_xfr) { + + /* always setup a valid "pc" pointer for status and sync */ + temp.pc = xfer->frbuffers + 0; + temp.len = 0; + temp.short_pkt = 0; + temp.setup_alt_next = 0; + + /* check if we need to sync */ + if (need_sync) { + /* we need a SYNC point after TX */ + temp.func = &atmegadci_data_tx_sync; + atmegadci_setup_standard_chain_sub(&temp); + } + + /* check if we should append a status stage */ + if (!xfer->flags_int.control_act) { + + /* + * Send a DATA1 message and invert the current + * endpoint direction. + */ + if (xfer->endpointno & UE_DIR_IN) { + temp.func = &atmegadci_data_rx; + need_sync = 0; + } else { + temp.func = &atmegadci_data_tx; + need_sync = 1; + } + + atmegadci_setup_standard_chain_sub(&temp); + if (need_sync) { + /* we need a SYNC point after TX */ + temp.func = &atmegadci_data_tx_sync; + atmegadci_setup_standard_chain_sub(&temp); + } + } + } + /* must have at least one frame! */ + td = temp.td; + xfer->td_transfer_last = td; +} + +static void +atmegadci_timeout(void *arg) +{ + struct usb_xfer *xfer = arg; + + DPRINTF("xfer=%p\n", xfer); + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* transfer is transferred */ + atmegadci_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +atmegadci_start_standard_chain(struct usb_xfer *xfer) +{ + DPRINTFN(9, "\n"); + + /* poll one time - will turn on interrupts */ + if (atmegadci_xfer_do_fifo(xfer)) { + + /* put transfer on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usbd_transfer_timeout_ms(xfer, + &atmegadci_timeout, xfer->timeout); + } + } +} + +static void +atmegadci_root_intr(struct atmegadci_softc *sc) +{ + DPRINTFN(9, "\n"); + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* set port bit */ + sc->sc_hub_idata[0] = 0x02; /* we only have one port */ + + uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, + sizeof(sc->sc_hub_idata)); + } + +static usb_error_t +atmegadci_standard_done_sub(struct usb_xfer *xfer) +{ + struct atmegadci_td *td; + uint32_t len; + uint8_t error; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + + do { + len = td->remainder; + + if (xfer->aframes != xfer->nframes) { + /* + * Verify the length and subtract + * the remainder from "frlengths[]": + */ + if (len > xfer->frlengths[xfer->aframes]) { + td->error = 1; + } else { + xfer->frlengths[xfer->aframes] -= len; + } + } + /* Check for transfer error */ + if (td->error) { + /* the transfer is finished */ + error = 1; + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + td = td->obj_next; + } else { + td = NULL; + } + } else { + /* the transfer is finished */ + td = NULL; + } + error = 0; + break; + } + td = td->obj_next; + + /* this USB frame is complete */ + error = 0; + break; + + } while (0); + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + return (error ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); +} + +static void +atmegadci_standard_done(struct usb_xfer *xfer) +{ + usb_error_t err = 0; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = atmegadci_standard_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = atmegadci_standard_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = atmegadci_standard_done_sub(xfer); + } +done: + atmegadci_device_done(xfer, err); +} + +/*------------------------------------------------------------------------* + * atmegadci_device_done + * + * NOTE: this function can be called more than one time on the + * same USB transfer! + *------------------------------------------------------------------------*/ +static void +atmegadci_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus); + uint8_t ep_no; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + DPRINTFN(9, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); + + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + ep_no = (xfer->endpointno & UE_ADDR); + + /* select endpoint number */ + ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no); + + /* disable endpoint interrupt */ + ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, 0); + + DPRINTFN(15, "disabled interrupts!\n"); + } + /* dequeue transfer and start next transfer */ + usbd_transfer_done(xfer, error); +} + +static void +atmegadci_set_stall(struct usb_device *udev, struct usb_xfer *xfer, + struct usb_endpoint *ep, uint8_t *did_stall) +{ + struct atmegadci_softc *sc; + uint8_t ep_no; + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + DPRINTFN(5, "endpoint=%p\n", ep); + + if (xfer) { + /* cancel any ongoing transfers */ + atmegadci_device_done(xfer, USB_ERR_STALLED); + } + sc = ATMEGA_BUS2SC(udev->bus); + /* get endpoint number */ + ep_no = (ep->edesc->bEndpointAddress & UE_ADDR); + /* select endpoint number */ + ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no); + /* set stall */ + ATMEGA_WRITE_1(sc, ATMEGA_UECONX, + ATMEGA_UECONX_EPEN | + ATMEGA_UECONX_STALLRQ); +} + +static void +atmegadci_clear_stall_sub(struct atmegadci_softc *sc, uint8_t ep_no, + uint8_t ep_type, uint8_t ep_dir) +{ + uint8_t temp; + + if (ep_type == UE_CONTROL) { + /* clearing stall is not needed */ + return; + } + /* select endpoint number */ + ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no); + + /* set endpoint reset */ + ATMEGA_WRITE_1(sc, ATMEGA_UERST, ATMEGA_UERST_MASK(ep_no)); + + /* clear endpoint reset */ + ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0); + + /* set stall */ + ATMEGA_WRITE_1(sc, ATMEGA_UECONX, + ATMEGA_UECONX_EPEN | + ATMEGA_UECONX_STALLRQ); + + /* reset data toggle */ + ATMEGA_WRITE_1(sc, ATMEGA_UECONX, + ATMEGA_UECONX_EPEN | + ATMEGA_UECONX_RSTDT); + + /* clear stall */ + ATMEGA_WRITE_1(sc, ATMEGA_UECONX, + ATMEGA_UECONX_EPEN | + ATMEGA_UECONX_STALLRQC); + + do { + if (ep_type == UE_BULK) { + temp = ATMEGA_UECFG0X_EPTYPE2; + } else if (ep_type == UE_INTERRUPT) { + temp = ATMEGA_UECFG0X_EPTYPE3; + } else { + temp = ATMEGA_UECFG0X_EPTYPE1; + } + if (ep_dir & UE_DIR_IN) { + temp |= ATMEGA_UECFG0X_EPDIR; + } + /* two banks, 64-bytes wMaxPacket */ + ATMEGA_WRITE_1(sc, ATMEGA_UECFG0X, temp); + ATMEGA_WRITE_1(sc, ATMEGA_UECFG1X, + ATMEGA_UECFG1X_ALLOC | + ATMEGA_UECFG1X_EPBK0 | /* one bank */ + ATMEGA_UECFG1X_EPSIZE(3)); + + temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X); + if (!(temp & ATMEGA_UESTA0X_CFGOK)) { + device_printf(sc->sc_bus.bdev, + "Chip rejected configuration\n"); + } + } while (0); +} + +static void +atmegadci_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) +{ + struct atmegadci_softc *sc; + struct usb_endpoint_descriptor *ed; + + DPRINTFN(5, "endpoint=%p\n", ep); + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + /* check mode */ + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + /* get softc */ + sc = ATMEGA_BUS2SC(udev->bus); + + /* get endpoint descriptor */ + ed = ep->edesc; + + /* reset endpoint */ + atmegadci_clear_stall_sub(sc, + (ed->bEndpointAddress & UE_ADDR), + (ed->bmAttributes & UE_XFERTYPE), + (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); +} + +usb_error_t +atmegadci_init(struct atmegadci_softc *sc) +{ + uint8_t n; + + DPRINTF("start\n"); + + /* set up the bus structure */ + sc->sc_bus.usbrev = USB_REV_1_1; + sc->sc_bus.methods = &atmegadci_bus_methods; + + USB_BUS_LOCK(&sc->sc_bus); + + /* make sure USB is enabled */ + ATMEGA_WRITE_1(sc, ATMEGA_USBCON, + ATMEGA_USBCON_USBE | + ATMEGA_USBCON_FRZCLK); + + /* enable USB PAD regulator */ + ATMEGA_WRITE_1(sc, ATMEGA_UHWCON, + ATMEGA_UHWCON_UVREGE | + ATMEGA_UHWCON_UIMOD); + + /* the following register sets up the USB PLL, assuming 16MHz X-tal */ + ATMEGA_WRITE_1(sc, 0x49 /* PLLCSR */, 0x14 | 0x02); + + /* wait for PLL to lock */ + for (n = 0; n != 20; n++) { + if (ATMEGA_READ_1(sc, 0x49) & 0x01) + break; + /* wait a little bit for PLL to start */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); + } + + /* make sure USB is enabled */ + ATMEGA_WRITE_1(sc, ATMEGA_USBCON, + ATMEGA_USBCON_USBE | + ATMEGA_USBCON_OTGPADE | + ATMEGA_USBCON_VBUSTE); + + /* turn on clocks */ + (sc->sc_clocks_on) (&sc->sc_bus); + + /* make sure device is re-enumerated */ + ATMEGA_WRITE_1(sc, ATMEGA_UDCON, ATMEGA_UDCON_DETACH); + + /* wait a little for things to stabilise */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 20); + + /* enable interrupts */ + ATMEGA_WRITE_1(sc, ATMEGA_UDIEN, + ATMEGA_UDINT_SUSPE | + ATMEGA_UDINT_EORSTE); + + /* reset all endpoints */ + ATMEGA_WRITE_1(sc, ATMEGA_UERST, + (1 << ATMEGA_EP_MAX) - 1); + + /* disable reset */ + ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0); + + /* disable all endpoints */ + for (n = 0; n != ATMEGA_EP_MAX; n++) { + + /* select endpoint */ + ATMEGA_WRITE_1(sc, ATMEGA_UENUM, n); + + /* disable endpoint interrupt */ + ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, 0); + + /* disable endpoint */ + ATMEGA_WRITE_1(sc, ATMEGA_UECONX, 0); + } + + /* turn off clocks */ + + atmegadci_clocks_off(sc); + + /* read initial VBUS state */ + + n = ATMEGA_READ_1(sc, ATMEGA_USBSTA); + atmegadci_vbus_interrupt(sc, n & ATMEGA_USBSTA_VBUS); + + USB_BUS_UNLOCK(&sc->sc_bus); + + /* catch any lost interrupts */ + + atmegadci_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +atmegadci_uninit(struct atmegadci_softc *sc) +{ + USB_BUS_LOCK(&sc->sc_bus); + + /* turn on clocks */ + (sc->sc_clocks_on) (&sc->sc_bus); + + /* disable interrupts */ + ATMEGA_WRITE_1(sc, ATMEGA_UDIEN, 0); + + /* reset all endpoints */ + ATMEGA_WRITE_1(sc, ATMEGA_UERST, + (1 << ATMEGA_EP_MAX) - 1); + + /* disable reset */ + ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0); + + sc->sc_flags.port_powered = 0; + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + atmegadci_pull_down(sc); + atmegadci_clocks_off(sc); + + /* disable USB PAD regulator */ + ATMEGA_WRITE_1(sc, ATMEGA_UHWCON, 0); + + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +atmegadci_suspend(struct atmegadci_softc *sc) +{ + /* TODO */ +} + +static void +atmegadci_resume(struct atmegadci_softc *sc) +{ + /* TODO */ +} + +static void +atmegadci_do_poll(struct usb_bus *bus) +{ + struct atmegadci_softc *sc = ATMEGA_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + atmegadci_interrupt_poll(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/*------------------------------------------------------------------------* + * at91dci bulk support + * at91dci control support + * at91dci interrupt support + *------------------------------------------------------------------------*/ +static void +atmegadci_device_non_isoc_open(struct usb_xfer *xfer) +{ + return; +} + +static void +atmegadci_device_non_isoc_close(struct usb_xfer *xfer) +{ + atmegadci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +atmegadci_device_non_isoc_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +atmegadci_device_non_isoc_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + atmegadci_setup_standard_chain(xfer); + atmegadci_start_standard_chain(xfer); +} + +struct usb_pipe_methods atmegadci_device_non_isoc_methods = +{ + .open = atmegadci_device_non_isoc_open, + .close = atmegadci_device_non_isoc_close, + .enter = atmegadci_device_non_isoc_enter, + .start = atmegadci_device_non_isoc_start, +}; + +/*------------------------------------------------------------------------* + * at91dci full speed isochronous support + *------------------------------------------------------------------------*/ +static void +atmegadci_device_isoc_fs_open(struct usb_xfer *xfer) +{ + return; +} + +static void +atmegadci_device_isoc_fs_close(struct usb_xfer *xfer) +{ + atmegadci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +atmegadci_device_isoc_fs_enter(struct usb_xfer *xfer) +{ + struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus); + uint32_t temp; + uint32_t nframes; + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes); + + /* get the current frame index */ + + nframes = + (ATMEGA_READ_1(sc, ATMEGA_UDFNUMH) << 8) | + (ATMEGA_READ_1(sc, ATMEGA_UDFNUML)); + + nframes &= ATMEGA_FRAME_MASK; + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + temp = (nframes - xfer->endpoint->isoc_next) & ATMEGA_FRAME_MASK; + + if ((xfer->endpoint->is_synced == 0) || + (temp < xfer->nframes)) { + /* + * If there is data underflow or the pipe queue is + * empty we schedule the transfer a few frames ahead + * of the current frame position. Else two isochronous + * transfers might overlap. + */ + xfer->endpoint->isoc_next = (nframes + 3) & ATMEGA_FRAME_MASK; + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + temp = (xfer->endpoint->isoc_next - nframes) & ATMEGA_FRAME_MASK; + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + + xfer->nframes; + + /* compute frame number for next insertion */ + xfer->endpoint->isoc_next += xfer->nframes; + + /* setup TDs */ + atmegadci_setup_standard_chain(xfer); +} + +static void +atmegadci_device_isoc_fs_start(struct usb_xfer *xfer) +{ + /* start TD chain */ + atmegadci_start_standard_chain(xfer); +} + +struct usb_pipe_methods atmegadci_device_isoc_fs_methods = +{ + .open = atmegadci_device_isoc_fs_open, + .close = atmegadci_device_isoc_fs_close, + .enter = atmegadci_device_isoc_fs_enter, + .start = atmegadci_device_isoc_fs_start, +}; + +/*------------------------------------------------------------------------* + * at91dci root control support + *------------------------------------------------------------------------* + * Simulate a hardware HUB by handling all the necessary requests. + *------------------------------------------------------------------------*/ + +static const struct usb_device_descriptor atmegadci_devd = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = UDESC_DEVICE, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_FSHUB, + .bMaxPacketSize = 64, + .bcdDevice = {0x00, 0x01}, + .iManufacturer = 1, + .iProduct = 2, + .bNumConfigurations = 1, +}; + +static const struct atmegadci_config_desc atmegadci_confd = { + .confd = { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(atmegadci_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0, + }, + .ifcd = { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = 0, + }, + .endpd = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = (UE_DIR_IN | ATMEGA_INTR_ENDPT), + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, + .bInterval = 255, + }, +}; + +static const struct usb_hub_descriptor_min atmegadci_hubd = { + .bDescLength = sizeof(atmegadci_hubd), + .bDescriptorType = UDESC_HUB, + .bNbrPorts = 1, + .wHubCharacteristics[0] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF, + .wHubCharacteristics[1] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 8, + .bPwrOn2PwrGood = 50, + .bHubContrCurrent = 0, + .DeviceRemovable = {0}, /* port is removable */ +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_VENDOR \ + 'A', 0, 'T', 0, 'M', 0, 'E', 0, 'G', 0, 'A', 0 + +#define STRING_PRODUCT \ + 'D', 0, 'C', 0, 'I', 0, ' ', 0, 'R', 0, \ + 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \ + 'U', 0, 'B', 0, + +USB_MAKE_STRING_DESC(STRING_LANG, atmegadci_langtab); +USB_MAKE_STRING_DESC(STRING_VENDOR, atmegadci_vendor); +USB_MAKE_STRING_DESC(STRING_PRODUCT, atmegadci_product); + +static usb_error_t +atmegadci_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + struct atmegadci_softc *sc = ATMEGA_BUS2SC(udev->bus); + const void *ptr; + uint16_t len; + uint16_t value; + uint16_t index; + uint8_t temp; + usb_error_t err; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* buffer reset */ + ptr = (const void *)&sc->sc_hub_temp; + len = 0; + err = 0; + + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + /* demultiplex the control request */ + + switch (req->bmRequestType) { + case UT_READ_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (req->bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_DESCRIPTOR: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SYNCH_FRAME: + goto tr_valid; /* nop */ + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (req->bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_INTERFACE: + switch (req->bRequest) { + case UR_SET_INTERFACE: + goto tr_handle_set_interface; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (req->bRequest) { + case UR_GET_INTERFACE: + goto tr_handle_get_interface; + case UR_GET_STATUS: + goto tr_handle_get_iface_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_INTERFACE: + case UT_WRITE_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_READ_CLASS_INTERFACE: + case UT_READ_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_WRITE_CLASS_DEVICE: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_valid; + case UR_SET_DESCRIPTOR: + case UR_SET_FEATURE: + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_OTHER: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_handle_clear_port_feature; + case UR_SET_FEATURE: + goto tr_handle_set_port_feature; + case UR_CLEAR_TT_BUFFER: + case UR_RESET_TT: + case UR_STOP_TT: + goto tr_valid; + + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_OTHER: + switch (req->bRequest) { + case UR_GET_TT_STATE: + goto tr_handle_get_tt_state; + case UR_GET_STATUS: + goto tr_handle_get_port_status; + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_class_descriptor; + case UR_GET_STATUS: + goto tr_handle_get_class_status; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + goto tr_valid; + +tr_handle_get_descriptor: + switch (value >> 8) { + case UDESC_DEVICE: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(atmegadci_devd); + ptr = (const void *)&atmegadci_devd; + goto tr_valid; + case UDESC_CONFIG: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(atmegadci_confd); + ptr = (const void *)&atmegadci_confd; + goto tr_valid; + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + len = sizeof(atmegadci_langtab); + ptr = (const void *)&atmegadci_langtab; + goto tr_valid; + + case 1: /* Vendor */ + len = sizeof(atmegadci_vendor); + ptr = (const void *)&atmegadci_vendor; + goto tr_valid; + + case 2: /* Product */ + len = sizeof(atmegadci_product); + ptr = (const void *)&atmegadci_product; + goto tr_valid; + default: + break; + } + break; + default: + goto tr_stalled; + } + goto tr_stalled; + +tr_handle_get_config: + len = 1; + sc->sc_hub_temp.wValue[0] = sc->sc_conf; + goto tr_valid; + +tr_handle_get_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); + goto tr_valid; + +tr_handle_set_address: + if (value & 0xFF00) { + goto tr_stalled; + } + sc->sc_rt_addr = value; + goto tr_valid; + +tr_handle_set_config: + if (value >= 2) { + goto tr_stalled; + } + sc->sc_conf = value; + goto tr_valid; + +tr_handle_get_interface: + len = 1; + sc->sc_hub_temp.wValue[0] = 0; + goto tr_valid; + +tr_handle_get_tt_state: +tr_handle_get_class_status: +tr_handle_get_iface_status: +tr_handle_get_ep_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, 0); + goto tr_valid; + +tr_handle_set_halt: +tr_handle_set_interface: +tr_handle_set_wakeup: +tr_handle_clear_wakeup: +tr_handle_clear_halt: + goto tr_valid; + +tr_handle_clear_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index); + + switch (value) { + case UHF_PORT_SUSPEND: + atmegadci_wakeup_peer(sc); + break; + + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 0; + break; + + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 0; + atmegadci_pull_down(sc); + atmegadci_clocks_off(sc); + break; + case UHF_C_PORT_CONNECTION: + /* clear connect change flag */ + sc->sc_flags.change_connect = 0; + + if (!sc->sc_flags.status_bus_reset) { + /* we are not connected */ + break; + } + + /* configure the control endpoint */ + + /* select endpoint number */ + ATMEGA_WRITE_1(sc, ATMEGA_UENUM, 0); + + /* set endpoint reset */ + ATMEGA_WRITE_1(sc, ATMEGA_UERST, ATMEGA_UERST_MASK(0)); + + /* clear endpoint reset */ + ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0); + + /* enable and stall endpoint */ + ATMEGA_WRITE_1(sc, ATMEGA_UECONX, + ATMEGA_UECONX_EPEN | + ATMEGA_UECONX_STALLRQ); + + /* one bank, 64-bytes wMaxPacket */ + ATMEGA_WRITE_1(sc, ATMEGA_UECFG0X, + ATMEGA_UECFG0X_EPTYPE0); + ATMEGA_WRITE_1(sc, ATMEGA_UECFG1X, + ATMEGA_UECFG1X_ALLOC | + ATMEGA_UECFG1X_EPBK0 | + ATMEGA_UECFG1X_EPSIZE(3)); + + /* check valid config */ + temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X); + if (!(temp & ATMEGA_UESTA0X_CFGOK)) { + device_printf(sc->sc_bus.bdev, + "Chip rejected EP0 configuration\n"); + } + break; + case UHF_C_PORT_SUSPEND: + sc->sc_flags.change_suspend = 0; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_set_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_SET_PORT_FEATURE\n"); + + switch (value) { + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 1; + break; + case UHF_PORT_SUSPEND: + case UHF_PORT_RESET: + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 1; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_get_port_status: + + DPRINTFN(9, "UR_GET_PORT_STATUS\n"); + + if (index != 1) { + goto tr_stalled; + } + if (sc->sc_flags.status_vbus) { + atmegadci_clocks_on(sc); + atmegadci_pull_up(sc); + } else { + atmegadci_pull_down(sc); + atmegadci_clocks_off(sc); + } + + /* Select FULL-speed and Device Side Mode */ + + value = UPS_PORT_MODE_DEVICE; + + if (sc->sc_flags.port_powered) { + value |= UPS_PORT_POWER; + } + if (sc->sc_flags.port_enabled) { + value |= UPS_PORT_ENABLED; + } + if (sc->sc_flags.status_vbus && + sc->sc_flags.status_bus_reset) { + value |= UPS_CURRENT_CONNECT_STATUS; + } + if (sc->sc_flags.status_suspend) { + value |= UPS_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortStatus, value); + + value = 0; + + if (sc->sc_flags.change_connect) { + value |= UPS_C_CONNECT_STATUS; + } + if (sc->sc_flags.change_suspend) { + value |= UPS_C_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortChange, value); + len = sizeof(sc->sc_hub_temp.ps); + goto tr_valid; + +tr_handle_get_class_descriptor: + if (value & 0xFF) { + goto tr_stalled; + } + ptr = (const void *)&atmegadci_hubd; + len = sizeof(atmegadci_hubd); + goto tr_valid; + +tr_stalled: + err = USB_ERR_STALLED; +tr_valid: +done: + *plength = len; + *pptr = ptr; + return (err); +} + +static void +atmegadci_xfer_setup(struct usb_setup_params *parm) +{ + const struct usb_hw_ep_profile *pf; + struct atmegadci_softc *sc; + struct usb_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t n; + uint8_t ep_no; + + sc = ATMEGA_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + /* + * NOTE: This driver does not use any of the parameters that + * are computed from the following values. Just set some + * reasonable dummies: + */ + parm->hc_max_packet_size = 0x500; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x500; + + usbd_transfer_setup_sub(parm); + + /* + * compute maximum number of TDs + */ + if ((xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) { + + ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */ + + 1 /* SYNC 2 */ ; + } else { + + ntd = xfer->nframes + 1 /* SYNC */ ; + } + + /* + * check if "usbd_transfer_setup_sub" set an error + */ + if (parm->err) + return; + + /* + * allocate transfer descriptors + */ + last_obj = NULL; + + /* + * get profile stuff + */ + ep_no = xfer->endpointno & UE_ADDR; + atmegadci_get_hw_ep_profile(parm->udev, &pf, ep_no); + + if (pf == NULL) { + /* should not happen */ + parm->err = USB_ERR_INVAL; + return; + } + + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + for (n = 0; n != ntd; n++) { + + struct atmegadci_td *td; + + if (parm->buf) { + + td = USB_ADD_BYTES(parm->buf, parm->size[0]); + + /* init TD */ + td->max_packet_size = xfer->max_packet_size; + td->ep_no = ep_no; + if (pf->support_multi_buffer) { + td->support_multi_buffer = 1; + } + td->obj_next = last_obj; + + last_obj = td; + } + parm->size[0] += sizeof(*td); + } + + xfer->td_start[0] = last_obj; +} + +static void +atmegadci_xfer_unsetup(struct usb_xfer *xfer) +{ + return; +} + +static void +atmegadci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *ep) +{ + struct atmegadci_softc *sc = ATMEGA_BUS2SC(udev->bus); + + DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d,%d)\n", + ep, udev->address, + edesc->bEndpointAddress, udev->flags.usb_mode, + sc->sc_rt_addr, udev->device_index); + + if (udev->device_index != sc->sc_rt_addr) { + + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + if (udev->speed != USB_SPEED_FULL) { + /* not supported */ + return; + } + if ((edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS) + ep->methods = &atmegadci_device_isoc_fs_methods; + else + ep->methods = &atmegadci_device_non_isoc_methods; + } +} + +static void +atmegadci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) +{ + struct atmegadci_softc *sc = ATMEGA_BUS2SC(bus); + + switch (state) { + case USB_HW_POWER_SUSPEND: + atmegadci_suspend(sc); + break; + case USB_HW_POWER_SHUTDOWN: + atmegadci_uninit(sc); + break; + case USB_HW_POWER_RESUME: + atmegadci_resume(sc); + break; + default: + break; + } +} + +struct usb_bus_methods atmegadci_bus_methods = +{ + .endpoint_init = &atmegadci_ep_init, + .xfer_setup = &atmegadci_xfer_setup, + .xfer_unsetup = &atmegadci_xfer_unsetup, + .get_hw_ep_profile = &atmegadci_get_hw_ep_profile, + .set_stall = &atmegadci_set_stall, + .clear_stall = &atmegadci_clear_stall, + .roothub_exec = &atmegadci_roothub_exec, + .xfer_poll = &atmegadci_do_poll, + .set_hw_power_sleep = &atmegadci_set_hw_power_sleep, +}; diff --git a/sys/bus/u4b/controller/atmegadci.h b/sys/bus/u4b/controller/atmegadci.h new file mode 100644 index 0000000000..91ba030661 --- /dev/null +++ b/sys/bus/u4b/controller/atmegadci.h @@ -0,0 +1,283 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2009 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * USB Device Port register definitions, copied from ATMEGA documentation + * provided by ATMEL. + */ + +#ifndef _ATMEGADCI_H_ +#define _ATMEGADCI_H_ + +#define ATMEGA_MAX_DEVICES (USB_MIN_DEVICES + 1) + +#define ATMEGA_OTGTCON 0xF9 +#define ATMEGA_OTGTCON_VALUE(x) ((x) << 0) +#define ATMEGA_OTGTCON_PAGE(x) ((x) << 5) + +#define ATMEGA_UEINT 0xF4 +#define ATMEGA_UEINT_MASK(n) (1 << (n)) /* endpoint interrupt mask */ + +#define ATMEGA_UEBCHX 0xF3 /* FIFO byte count high */ +#define ATMEGA_UEBCLX 0xF2 /* FIFO byte count low */ +#define ATMEGA_UEDATX 0xF1 /* FIFO data */ + +#define ATMEGA_UEIENX 0xF0 /* interrupt enable register */ +#define ATMEGA_UEIENX_TXINE (1 << 0) +#define ATMEGA_UEIENX_STALLEDE (1 << 1) +#define ATMEGA_UEIENX_RXOUTE (1 << 2) +#define ATMEGA_UEIENX_RXSTPE (1 << 3) /* received SETUP packet */ +#define ATMEGA_UEIENX_NAKOUTE (1 << 4) +#define ATMEGA_UEIENX_NAKINE (1 << 6) +#define ATMEGA_UEIENX_FLERRE (1 << 7) + +#define ATMEGA_UESTA1X 0xEF +#define ATMEGA_UESTA1X_CURRBK (3 << 0) /* current bank */ +#define ATMEGA_UESTA1X_CTRLDIR (1 << 2) /* control endpoint direction */ + +#define ATMEGA_UESTA0X 0xEE +#define ATMEGA_UESTA0X_NBUSYBK (3 << 0) +#define ATMEGA_UESTA0X_DTSEQ (3 << 2) +#define ATMEGA_UESTA0X_UNDERFI (1 << 5) /* underflow */ +#define ATMEGA_UESTA0X_OVERFI (1 << 6) /* overflow */ +#define ATMEGA_UESTA0X_CFGOK (1 << 7) + +#define ATMEGA_UECFG1X 0xED /* endpoint config register */ +#define ATMEGA_UECFG1X_ALLOC (1 << 1) +#define ATMEGA_UECFG1X_EPBK0 (0 << 2) +#define ATMEGA_UECFG1X_EPBK1 (1 << 2) +#define ATMEGA_UECFG1X_EPBK2 (2 << 2) +#define ATMEGA_UECFG1X_EPBK3 (3 << 2) +#define ATMEGA_UECFG1X_EPSIZE(n) ((n) << 4) + +#define ATMEGA_UECFG0X 0xEC +#define ATMEGA_UECFG0X_EPDIR (1 << 0) /* endpoint direction */ +#define ATMEGA_UECFG0X_EPTYPE0 (0 << 6) +#define ATMEGA_UECFG0X_EPTYPE1 (1 << 6) +#define ATMEGA_UECFG0X_EPTYPE2 (2 << 6) +#define ATMEGA_UECFG0X_EPTYPE3 (3 << 6) + +#define ATMEGA_UECONX 0xEB +#define ATMEGA_UECONX_EPEN (1 << 0) +#define ATMEGA_UECONX_RSTDT (1 << 3) +#define ATMEGA_UECONX_STALLRQC (1 << 4) /* stall request clear */ +#define ATMEGA_UECONX_STALLRQ (1 << 5) /* stall request set */ + +#define ATMEGA_UERST 0xEA /* endpoint reset register */ +#define ATMEGA_UERST_MASK(n) (1 << (n)) + +#define ATMEGA_UENUM 0xE9 /* endpoint number */ + +#define ATMEGA_UEINTX 0xE8 /* interrupt register */ +#define ATMEGA_UEINTX_TXINI (1 << 0) +#define ATMEGA_UEINTX_STALLEDI (1 << 1) +#define ATMEGA_UEINTX_RXOUTI (1 << 2) +#define ATMEGA_UEINTX_RXSTPI (1 << 3) /* received setup packet */ +#define ATMEGA_UEINTX_NAKOUTI (1 << 4) +#define ATMEGA_UEINTX_RWAL (1 << 5) +#define ATMEGA_UEINTX_NAKINI (1 << 6) +#define ATMEGA_UEINTX_FIFOCON (1 << 7) + +#define ATMEGA_UDMFN 0xE6 +#define ATMEGA_UDMFN_FNCERR (1 << 4) + +#define ATMEGA_UDFNUMH 0xE5 /* frame number high */ +#define ATMEGA_UDFNUMH_MASK 7 + +#define ATMEGA_UDFNUML 0xE4 /* frame number low */ +#define ATMEGA_UDFNUML_MASK 0xFF + +#define ATMEGA_FRAME_MASK 0x7FF + +#define ATMEGA_UDADDR 0xE3 /* USB address */ +#define ATMEGA_UDADDR_MASK 0x7F +#define ATMEGA_UDADDR_ADDEN (1 << 7) + +#define ATMEGA_UDIEN 0xE2 /* USB device interrupt enable */ +#define ATMEGA_UDINT_SUSPE (1 << 0) +#define ATMEGA_UDINT_MSOFE (1 << 1) +#define ATMEGA_UDINT_SOFE (1 << 2) +#define ATMEGA_UDINT_EORSTE (1 << 3) +#define ATMEGA_UDINT_WAKEUPE (1 << 4) +#define ATMEGA_UDINT_EORSME (1 << 5) +#define ATMEGA_UDINT_UPRSME (1 << 6) + +#define ATMEGA_UDINT 0xE1 /* USB device interrupt status */ +#define ATMEGA_UDINT_SUSPI (1 << 0) +#define ATMEGA_UDINT_MSOFI (1 << 1) +#define ATMEGA_UDINT_SOFI (1 << 2) +#define ATMEGA_UDINT_EORSTI (1 << 3) +#define ATMEGA_UDINT_WAKEUPI (1 << 4) +#define ATMEGA_UDINT_EORSMI (1 << 5) +#define ATMEGA_UDINT_UPRSMI (1 << 6) + +#define ATMEGA_UDCON 0xE0 /* USB device connection register */ +#define ATMEGA_UDCON_DETACH (1 << 0) +#define ATMEGA_UDCON_RMWKUP (1 << 1) +#define ATMEGA_UDCON_LSM (1 << 2) +#define ATMEGA_UDCON_RSTCPU (1 << 3) + +#define ATMEGA_OTGINT 0xDF + +#define ATMEGA_OTGCON 0xDD +#define ATMEGA_OTGCON_VBUSRQC (1 << 0) +#define ATMEGA_OTGCON_VBUSREQ (1 << 1) +#define ATMEGA_OTGCON_VBUSHWC (1 << 2) +#define ATMEGA_OTGCON_SRPSEL (1 << 3) +#define ATMEGA_OTGCON_SRPREQ (1 << 4) +#define ATMEGA_OTGCON_HNPREQ (1 << 5) + +#define ATMEGA_USBINT 0xDA +#define ATMEGA_USBINT_VBUSTI (1 << 0) /* USB VBUS interrupt */ +#define ATMEGA_USBINT_IDI (1 << 1) /* USB ID interrupt */ + +#define ATMEGA_USBSTA 0xD9 +#define ATMEGA_USBSTA_VBUS (1 << 0) +#define ATMEGA_USBSTA_ID (1 << 1) + +#define ATMEGA_USBCON 0xD8 +#define ATMEGA_USBCON_VBUSTE (1 << 0) +#define ATMEGA_USBCON_IDE (1 << 1) +#define ATMEGA_USBCON_OTGPADE (1 << 4) +#define ATMEGA_USBCON_FRZCLK (1 << 5) +#define ATMEGA_USBCON_USBE (1 << 7) + +#define ATMEGA_UHWCON 0xD7 +#define ATMEGA_UHWCON_UVREGE (1 << 0) +#define ATMEGA_UHWCON_UVCONE (1 << 4) +#define ATMEGA_UHWCON_UIDE (1 << 6) +#define ATMEGA_UHWCON_UIMOD (1 << 7) + +#define ATMEGA_READ_1(sc, reg) \ + bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) + +#define ATMEGA_WRITE_1(sc, reg, data) \ + bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data) + +#define ATMEGA_WRITE_MULTI_1(sc, reg, ptr, len) \ + bus_space_write_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, ptr, len) + +#define ATMEGA_READ_MULTI_1(sc, reg, ptr, len) \ + bus_space_read_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, ptr, len) + +/* + * Maximum number of endpoints supported: + */ +#define ATMEGA_EP_MAX 7 + +struct atmegadci_td; + +typedef uint8_t (atmegadci_cmd_t)(struct atmegadci_td *td); +typedef void (atmegadci_clocks_t)(struct usb_bus *); + +struct atmegadci_td { + struct atmegadci_td *obj_next; + atmegadci_cmd_t *func; + struct usb_page_cache *pc; + uint32_t offset; + uint32_t remainder; + uint16_t max_packet_size; + uint8_t error:1; + uint8_t alt_next:1; + uint8_t short_pkt:1; + uint8_t support_multi_buffer:1; + uint8_t did_stall:1; + uint8_t ep_no:3; +}; + +struct atmegadci_std_temp { + atmegadci_cmd_t *func; + struct usb_page_cache *pc; + struct atmegadci_td *td; + struct atmegadci_td *td_next; + uint32_t len; + uint32_t offset; + uint16_t max_frame_size; + uint8_t short_pkt; + /* + * short_pkt = 0: transfer should be short terminated + * short_pkt = 1: transfer should not be short terminated + */ + uint8_t setup_alt_next; + uint8_t did_stall; +}; + +struct atmegadci_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union atmegadci_hub_temp { + uWord wValue; + struct usb_port_status ps; +}; + +struct atmegadci_flags { + uint8_t change_connect:1; + uint8_t change_suspend:1; + uint8_t status_suspend:1; /* set if suspended */ + uint8_t status_vbus:1; /* set if present */ + uint8_t status_bus_reset:1; /* set if reset complete */ + uint8_t remote_wakeup:1; + uint8_t self_powered:1; + uint8_t clocks_off:1; + uint8_t port_powered:1; + uint8_t port_enabled:1; + uint8_t d_pulled_up:1; +}; + +struct atmegadci_softc { + struct usb_bus sc_bus; + union atmegadci_hub_temp sc_hub_temp; + + /* must be set by by the bus interface layer */ + atmegadci_clocks_t *sc_clocks_on; + atmegadci_clocks_t *sc_clocks_off; + + struct usb_device *sc_devices[ATMEGA_MAX_DEVICES]; + struct resource *sc_irq_res; + void *sc_intr_hdl; + struct resource *sc_io_res; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + uint8_t sc_rt_addr; /* root hub address */ + uint8_t sc_dv_addr; /* device address */ + uint8_t sc_conf; /* root hub config */ + + uint8_t sc_hub_idata[1]; + + struct atmegadci_flags sc_flags; +}; + +/* prototypes */ + +usb_error_t atmegadci_init(struct atmegadci_softc *sc); +void atmegadci_uninit(struct atmegadci_softc *sc); +void atmegadci_interrupt(struct atmegadci_softc *sc); + +#endif /* _ATMEGADCI_H_ */ diff --git a/sys/bus/u4b/controller/atmegadci_atmelarm.c b/sys/bus/u4b/controller/atmegadci_atmelarm.c new file mode 100644 index 0000000000..6c380b6076 --- /dev/null +++ b/sys/bus/u4b/controller/atmegadci_atmelarm.c @@ -0,0 +1,216 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2009 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +static device_probe_t atmegadci_probe; +static device_attach_t atmegadci_attach; +static device_detach_t atmegadci_detach; + +struct atmegadci_super_softc { + struct atmegadci_softc sc_otg; /* must be first */ +}; + +static void +atmegadci_clocks_on(struct usb_bus *bus) +{ + /* TODO */ +} + +static void +atmegadci_clocks_off(struct usb_bus *bus) +{ + /* TODO */ +} + +static int +atmegadci_probe(device_t dev) +{ + device_set_desc(dev, "ATMEL OTG integrated USB controller"); + return (0); +} + +static int +atmegadci_attach(device_t dev) +{ + struct atmegadci_super_softc *sc = device_get_softc(dev); + int err; + int rid; + + /* setup MUSB OTG USB controller interface softc */ + sc->sc_otg.sc_clocks_on = &atmegadci_clocks_on; + sc->sc_otg.sc_clocks_off = &atmegadci_clocks_off; + + /* initialise some bus fields */ + sc->sc_otg.sc_bus.parent = dev; + sc->sc_otg.sc_bus.devices = sc->sc_otg.sc_devices; + sc->sc_otg.sc_bus.devices_max = ATMEGA_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_otg.sc_bus, + USB_GET_DMA_TAG(dev), NULL)) { + return (ENOMEM); + } + rid = 0; + sc->sc_otg.sc_io_res = + bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); + + if (!(sc->sc_otg.sc_io_res)) { + err = ENOMEM; + goto error; + } + sc->sc_otg.sc_io_tag = rman_get_bustag(sc->sc_otg.sc_io_res); + sc->sc_otg.sc_io_hdl = rman_get_bushandle(sc->sc_otg.sc_io_res); + + rid = 0; + sc->sc_otg.sc_irq_res = + bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); + if (!(sc->sc_otg.sc_irq_res)) { + goto error; + } + sc->sc_otg.sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (!(sc->sc_otg.sc_bus.bdev)) { + goto error; + } + device_set_ivars(sc->sc_otg.sc_bus.bdev, &sc->sc_otg.sc_bus); + + err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)atmegadci_interrupt, sc, &sc->sc_otg.sc_intr_hdl); + if (err) { + sc->sc_otg.sc_intr_hdl = NULL; + goto error; + } + err = atmegadci_init(&sc->sc_otg); + if (!err) { + err = device_probe_and_attach(sc->sc_otg.sc_bus.bdev); + } + if (err) { + goto error; + } + return (0); + +error: + atmegadci_detach(dev); + return (ENXIO); +} + +static int +atmegadci_detach(device_t dev) +{ + struct atmegadci_super_softc *sc = device_get_softc(dev); + device_t bdev; + int err; + + if (sc->sc_otg.sc_bus.bdev) { + bdev = sc->sc_otg.sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(dev); + + if (sc->sc_otg.sc_irq_res && sc->sc_otg.sc_intr_hdl) { + /* + * only call atmegadci_uninit() after atmegadci_init() + */ + atmegadci_uninit(&sc->sc_otg); + + err = bus_teardown_intr(dev, sc->sc_otg.sc_irq_res, + sc->sc_otg.sc_intr_hdl); + sc->sc_otg.sc_intr_hdl = NULL; + } + /* free IRQ channel, if any */ + if (sc->sc_otg.sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 0, + sc->sc_otg.sc_irq_res); + sc->sc_otg.sc_irq_res = NULL; + } + /* free memory resource, if any */ + if (sc->sc_otg.sc_io_res) { + bus_release_resource(dev, SYS_RES_MEMORY, 0, + sc->sc_otg.sc_io_res); + sc->sc_otg.sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_otg.sc_bus, NULL); + + return (0); +} + +static device_method_t atmegadci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, atmegadci_probe), + DEVMETHOD(device_attach, atmegadci_attach), + DEVMETHOD(device_detach, atmegadci_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD_END +}; + +static driver_t atmegadci_driver = { + .name = "atmegadci", + .methods = atmegadci_methods, + .size = sizeof(struct atmegadci_super_softc), +}; + +static devclass_t atmegadci_devclass; + +DRIVER_MODULE(atmegadci, atmelarm, atmegadci_driver, atmegadci_devclass, 0, 0); +MODULE_DEPEND(atmegadci, usb, 1, 1, 1); diff --git a/sys/bus/u4b/controller/avr32dci.c b/sys/bus/u4b/controller/avr32dci.c new file mode 100644 index 0000000000..9494c30164 --- /dev/null +++ b/sys/bus/u4b/controller/avr32dci.c @@ -0,0 +1,2105 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2009 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains the driver for the AVR32 series USB Device + * Controller + */ + +/* + * NOTE: When the chip detects BUS-reset it will also reset the + * endpoints, Function-address and more. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define USB_DEBUG_VAR avr32dci_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define AVR32_BUS2SC(bus) \ + ((struct avr32dci_softc *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((struct avr32dci_softc *)0)->sc_bus)))) + +#define AVR32_PC2SC(pc) \ + AVR32_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus) + +#ifdef USB_DEBUG +static int avr32dci_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, avr32dci, CTLFLAG_RW, 0, "USB AVR32 DCI"); +SYSCTL_INT(_hw_usb_avr32dci, OID_AUTO, debug, CTLFLAG_RW, + &avr32dci_debug, 0, "AVR32 DCI debug level"); +#endif + +#define AVR32_INTR_ENDPT 1 + +/* prototypes */ + +struct usb_bus_methods avr32dci_bus_methods; +struct usb_pipe_methods avr32dci_device_non_isoc_methods; +struct usb_pipe_methods avr32dci_device_isoc_fs_methods; + +static avr32dci_cmd_t avr32dci_setup_rx; +static avr32dci_cmd_t avr32dci_data_rx; +static avr32dci_cmd_t avr32dci_data_tx; +static avr32dci_cmd_t avr32dci_data_tx_sync; +static void avr32dci_device_done(struct usb_xfer *, usb_error_t); +static void avr32dci_do_poll(struct usb_bus *); +static void avr32dci_standard_done(struct usb_xfer *); +static void avr32dci_root_intr(struct avr32dci_softc *sc); + +/* + * Here is a list of what the chip supports: + */ +static const struct usb_hw_ep_profile + avr32dci_ep_profile[4] = { + + [0] = { + .max_in_frame_size = 64, + .max_out_frame_size = 64, + .is_simplex = 1, + .support_control = 1, + }, + + [1] = { + .max_in_frame_size = 512, + .max_out_frame_size = 512, + .is_simplex = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, + + [2] = { + .max_in_frame_size = 64, + .max_out_frame_size = 64, + .is_simplex = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_in = 1, + .support_out = 1, + }, + + [3] = { + .max_in_frame_size = 1024, + .max_out_frame_size = 1024, + .is_simplex = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, +}; + +static void +avr32dci_get_hw_ep_profile(struct usb_device *udev, + const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) +{ + if (ep_addr == 0) + *ppf = avr32dci_ep_profile; + else if (ep_addr < 3) + *ppf = avr32dci_ep_profile + 1; + else if (ep_addr < 5) + *ppf = avr32dci_ep_profile + 2; + else if (ep_addr < 7) + *ppf = avr32dci_ep_profile + 3; + else + *ppf = NULL; +} + +static void +avr32dci_mod_ctrl(struct avr32dci_softc *sc, uint32_t set, uint32_t clear) +{ + uint32_t temp; + + temp = AVR32_READ_4(sc, AVR32_CTRL); + temp |= set; + temp &= ~clear; + AVR32_WRITE_4(sc, AVR32_CTRL, temp); +} + +static void +avr32dci_mod_ien(struct avr32dci_softc *sc, uint32_t set, uint32_t clear) +{ + uint32_t temp; + + temp = AVR32_READ_4(sc, AVR32_IEN); + temp |= set; + temp &= ~clear; + AVR32_WRITE_4(sc, AVR32_IEN, temp); +} + +static void +avr32dci_clocks_on(struct avr32dci_softc *sc) +{ + if (sc->sc_flags.clocks_off && + sc->sc_flags.port_powered) { + + DPRINTFN(5, "\n"); + + /* turn on clocks */ + (sc->sc_clocks_on) (&sc->sc_bus); + + avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_EN_USBA, 0); + + sc->sc_flags.clocks_off = 0; + } +} + +static void +avr32dci_clocks_off(struct avr32dci_softc *sc) +{ + if (!sc->sc_flags.clocks_off) { + + DPRINTFN(5, "\n"); + + avr32dci_mod_ctrl(sc, 0, AVR32_CTRL_DEV_EN_USBA); + + /* turn clocks off */ + (sc->sc_clocks_off) (&sc->sc_bus); + + sc->sc_flags.clocks_off = 1; + } +} + +static void +avr32dci_pull_up(struct avr32dci_softc *sc) +{ + /* pullup D+, if possible */ + + if (!sc->sc_flags.d_pulled_up && + sc->sc_flags.port_powered) { + sc->sc_flags.d_pulled_up = 1; + avr32dci_mod_ctrl(sc, 0, AVR32_CTRL_DEV_DETACH); + } +} + +static void +avr32dci_pull_down(struct avr32dci_softc *sc) +{ + /* pulldown D+, if possible */ + + if (sc->sc_flags.d_pulled_up) { + sc->sc_flags.d_pulled_up = 0; + avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_DETACH, 0); + } +} + +static void +avr32dci_wakeup_peer(struct avr32dci_softc *sc) +{ + if (!sc->sc_flags.status_suspend) { + return; + } + avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_REWAKEUP, 0); + + /* wait 8 milliseconds */ + /* Wait for reset to complete. */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125); + + /* hardware should have cleared RMWKUP bit */ +} + +static void +avr32dci_set_address(struct avr32dci_softc *sc, uint8_t addr) +{ + DPRINTFN(5, "addr=%d\n", addr); + + avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_FADDR_EN | addr, 0); +} + +static uint8_t +avr32dci_setup_rx(struct avr32dci_td *td) +{ + struct avr32dci_softc *sc; + struct usb_device_request req; + uint16_t count; + uint32_t temp; + + /* get pointer to softc */ + sc = AVR32_PC2SC(td->pc); + + /* check endpoint status */ + temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no)); + + DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp); + + if (!(temp & AVR32_EPTSTA_RX_SETUP)) { + goto not_complete; + } + /* clear did stall */ + td->did_stall = 0; + /* get the packet byte count */ + count = AVR32_EPTSTA_BYTE_COUNT(temp); + + /* verify data length */ + if (count != td->remainder) { + DPRINTFN(0, "Invalid SETUP packet " + "length, %d bytes\n", count); + goto not_complete; + } + if (count != sizeof(req)) { + DPRINTFN(0, "Unsupported SETUP packet " + "length, %d bytes\n", count); + goto not_complete; + } + /* receive data */ + memcpy(&req, sc->physdata, sizeof(req)); + + /* copy data into real buffer */ + usbd_copy_in(td->pc, 0, &req, sizeof(req)); + + td->offset = sizeof(req); + td->remainder = 0; + + /* sneak peek the set address */ + if ((req.bmRequestType == UT_WRITE_DEVICE) && + (req.bRequest == UR_SET_ADDRESS)) { + sc->sc_dv_addr = req.wValue[0] & 0x7F; + /* must write address before ZLP */ + avr32dci_mod_ctrl(sc, 0, AVR32_CTRL_DEV_FADDR_EN | + AVR32_CTRL_DEV_ADDR); + avr32dci_mod_ctrl(sc, sc->sc_dv_addr, 0); + } else { + sc->sc_dv_addr = 0xFF; + } + + /* clear SETUP packet interrupt */ + AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(td->ep_no), AVR32_EPTSTA_RX_SETUP); + return (0); /* complete */ + +not_complete: + if (temp & AVR32_EPTSTA_RX_SETUP) { + /* clear SETUP packet interrupt */ + AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(td->ep_no), AVR32_EPTSTA_RX_SETUP); + } + /* abort any ongoing transfer */ + if (!td->did_stall) { + DPRINTFN(5, "stalling\n"); + AVR32_WRITE_4(sc, AVR32_EPTSETSTA(td->ep_no), + AVR32_EPTSTA_FRCESTALL); + td->did_stall = 1; + } + return (1); /* not complete */ +} + +static uint8_t +avr32dci_data_rx(struct avr32dci_td *td) +{ + struct avr32dci_softc *sc; + struct usb_page_search buf_res; + uint16_t count; + uint32_t temp; + uint8_t to; + uint8_t got_short; + + to = 4; /* don't loop forever! */ + got_short = 0; + + /* get pointer to softc */ + sc = AVR32_PC2SC(td->pc); + +repeat: + /* check if any of the FIFO banks have data */ + /* check endpoint status */ + temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no)); + + DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp); + + if (temp & AVR32_EPTSTA_RX_SETUP) { + if (td->remainder == 0) { + /* + * We are actually complete and have + * received the next SETUP + */ + DPRINTFN(5, "faking complete\n"); + return (0); /* complete */ + } + /* + * USB Host Aborted the transfer. + */ + td->error = 1; + return (0); /* complete */ + } + /* check status */ + if (!(temp & AVR32_EPTSTA_RX_BK_RDY)) { + /* no data */ + goto not_complete; + } + /* get the packet byte count */ + count = AVR32_EPTSTA_BYTE_COUNT(temp); + + /* verify the packet byte count */ + if (count != td->max_packet_size) { + if (count < td->max_packet_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + } + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + while (count > 0) { + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* receive data */ + memcpy(buf_res.buffer, sc->physdata + + (AVR32_EPTSTA_CURRENT_BANK(temp) << td->bank_shift) + + (td->ep_no << 16) + (td->offset % td->max_packet_size), buf_res.length); + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* clear OUT packet interrupt */ + AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(td->ep_no), AVR32_EPTSTA_RX_BK_RDY); + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) { + /* we are complete */ + return (0); + } + /* else need to receive a zero length packet */ + } + if (--to) { + goto repeat; + } +not_complete: + return (1); /* not complete */ +} + +static uint8_t +avr32dci_data_tx(struct avr32dci_td *td) +{ + struct avr32dci_softc *sc; + struct usb_page_search buf_res; + uint16_t count; + uint8_t to; + uint32_t temp; + + to = 4; /* don't loop forever! */ + + /* get pointer to softc */ + sc = AVR32_PC2SC(td->pc); + +repeat: + + /* check endpoint status */ + temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no)); + + DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp); + + if (temp & AVR32_EPTSTA_RX_SETUP) { + /* + * The current transfer was aborted + * by the USB Host + */ + td->error = 1; + return (0); /* complete */ + } + if (temp & AVR32_EPTSTA_TX_PK_RDY) { + /* cannot write any data - all banks are busy */ + goto not_complete; + } + count = td->max_packet_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + while (count > 0) { + + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* transmit data */ + memcpy(sc->physdata + + (AVR32_EPTSTA_CURRENT_BANK(temp) << td->bank_shift) + + (td->ep_no << 16) + (td->offset % td->max_packet_size), + buf_res.buffer, buf_res.length); + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* allocate FIFO bank */ + AVR32_WRITE_4(sc, AVR32_EPTCTL(td->ep_no), AVR32_EPTCTL_TX_PK_RDY); + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) { + return (0); /* complete */ + } + /* else we need to transmit a short packet */ + } + if (--to) { + goto repeat; + } +not_complete: + return (1); /* not complete */ +} + +static uint8_t +avr32dci_data_tx_sync(struct avr32dci_td *td) +{ + struct avr32dci_softc *sc; + uint32_t temp; + + /* get pointer to softc */ + sc = AVR32_PC2SC(td->pc); + + /* check endpoint status */ + temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no)); + + DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp); + + if (temp & AVR32_EPTSTA_RX_SETUP) { + DPRINTFN(5, "faking complete\n"); + /* Race condition */ + return (0); /* complete */ + } + /* + * The control endpoint has only got one bank, so if that bank + * is free the packet has been transferred! + */ + if (AVR32_EPTSTA_BUSY_BANK_STA(temp) != 0) { + /* cannot write any data - a bank is busy */ + goto not_complete; + } + if (sc->sc_dv_addr != 0xFF) { + /* set new address */ + avr32dci_set_address(sc, sc->sc_dv_addr); + } + return (0); /* complete */ + +not_complete: + return (1); /* not complete */ +} + +static uint8_t +avr32dci_xfer_do_fifo(struct usb_xfer *xfer) +{ + struct avr32dci_td *td; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + while (1) { + if ((td->func) (td)) { + /* operation in progress */ + break; + } + if (((void *)td) == xfer->td_transfer_last) { + goto done; + } + if (td->error) { + goto done; + } else if (td->remainder > 0) { + /* + * We had a short transfer. If there is no alternate + * next, stop processing ! + */ + if (!td->alt_next) { + goto done; + } + } + /* + * Fetch the next transfer descriptor and transfer + * some flags to the next transfer descriptor + */ + td = td->obj_next; + xfer->td_transfer_cache = td; + } + return (1); /* not complete */ + +done: + /* compute all actual lengths */ + + avr32dci_standard_done(xfer); + return (0); /* complete */ +} + +static void +avr32dci_interrupt_poll(struct avr32dci_softc *sc) +{ + struct usb_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + if (!avr32dci_xfer_do_fifo(xfer)) { + /* queue has been modified */ + goto repeat; + } + } +} + +void +avr32dci_vbus_interrupt(struct avr32dci_softc *sc, uint8_t is_on) +{ + DPRINTFN(5, "vbus = %u\n", is_on); + + if (is_on) { + if (!sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 1; + + /* complete root HUB interrupt endpoint */ + + avr32dci_root_intr(sc); + } + } else { + if (sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* complete root HUB interrupt endpoint */ + + avr32dci_root_intr(sc); + } + } +} + +void +avr32dci_interrupt(struct avr32dci_softc *sc) +{ + uint32_t status; + + USB_BUS_LOCK(&sc->sc_bus); + + /* read interrupt status */ + status = AVR32_READ_4(sc, AVR32_INTSTA); + + /* clear all set interrupts */ + AVR32_WRITE_4(sc, AVR32_CLRINT, status); + + DPRINTFN(14, "INTSTA=0x%08x\n", status); + + /* check for any bus state change interrupts */ + if (status & AVR32_INT_ENDRESET) { + + DPRINTFN(5, "end of reset\n"); + + /* set correct state */ + sc->sc_flags.status_bus_reset = 1; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* disable resume interrupt */ + avr32dci_mod_ien(sc, AVR32_INT_DET_SUSPD | + AVR32_INT_ENDRESET, AVR32_INT_WAKE_UP); + + /* complete root HUB interrupt endpoint */ + avr32dci_root_intr(sc); + } + /* + * If resume and suspend is set at the same time we interpret + * that like RESUME. Resume is set when there is at least 3 + * milliseconds of inactivity on the USB BUS. + */ + if (status & AVR32_INT_WAKE_UP) { + + DPRINTFN(5, "resume interrupt\n"); + + if (sc->sc_flags.status_suspend) { + /* update status bits */ + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 1; + + /* disable resume interrupt */ + avr32dci_mod_ien(sc, AVR32_INT_DET_SUSPD | + AVR32_INT_ENDRESET, AVR32_INT_WAKE_UP); + + /* complete root HUB interrupt endpoint */ + avr32dci_root_intr(sc); + } + } else if (status & AVR32_INT_DET_SUSPD) { + + DPRINTFN(5, "suspend interrupt\n"); + + if (!sc->sc_flags.status_suspend) { + /* update status bits */ + sc->sc_flags.status_suspend = 1; + sc->sc_flags.change_suspend = 1; + + /* disable suspend interrupt */ + avr32dci_mod_ien(sc, AVR32_INT_WAKE_UP | + AVR32_INT_ENDRESET, AVR32_INT_DET_SUSPD); + + /* complete root HUB interrupt endpoint */ + avr32dci_root_intr(sc); + } + } + /* check for any endpoint interrupts */ + if (status & -AVR32_INT_EPT_INT(0)) { + + DPRINTFN(5, "real endpoint interrupt\n"); + + avr32dci_interrupt_poll(sc); + } + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +avr32dci_setup_standard_chain_sub(struct avr32dci_std_temp *temp) +{ + struct avr32dci_td *td; + + /* get current Transfer Descriptor */ + td = temp->td_next; + temp->td = td; + + /* prepare for next TD */ + temp->td_next = td->obj_next; + + /* fill out the Transfer Descriptor */ + td->func = temp->func; + td->pc = temp->pc; + td->offset = temp->offset; + td->remainder = temp->len; + td->error = 0; + td->did_stall = temp->did_stall; + td->short_pkt = temp->short_pkt; + td->alt_next = temp->setup_alt_next; +} + +static void +avr32dci_setup_standard_chain(struct usb_xfer *xfer) +{ + struct avr32dci_std_temp temp; + struct avr32dci_softc *sc; + struct avr32dci_td *td; + uint32_t x; + uint8_t ep_no; + uint8_t need_sync; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpointno), + xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); + + temp.max_frame_size = xfer->max_frame_size; + + td = xfer->td_start[0]; + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + /* setup temp */ + + temp.pc = NULL; + temp.td = NULL; + temp.td_next = xfer->td_start[0]; + temp.offset = 0; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.did_stall = !xfer->flags_int.control_stall; + + sc = AVR32_BUS2SC(xfer->xroot->bus); + ep_no = (xfer->endpointno & UE_ADDR); + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.func = &avr32dci_setup_rx; + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.short_pkt = temp.len ? 1 : 0; + /* check for last frame */ + if (xfer->nframes == 1) { + /* no STATUS stage yet, SETUP is last */ + if (xfer->flags_int.control_act) + temp.setup_alt_next = 0; + } + avr32dci_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + if (x != xfer->nframes) { + if (xfer->endpointno & UE_DIR_IN) { + temp.func = &avr32dci_data_tx; + need_sync = 1; + } else { + temp.func = &avr32dci_data_rx; + need_sync = 0; + } + + /* setup "pc" pointer */ + temp.pc = xfer->frbuffers + x; + } else { + need_sync = 0; + } + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + + x++; + + if (x == xfer->nframes) { + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_act) { + temp.setup_alt_next = 0; + } + } else { + temp.setup_alt_next = 0; + } + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.short_pkt = 0; + + } else { + + /* regular data transfer */ + + temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + avr32dci_setup_standard_chain_sub(&temp); + + if (xfer->flags_int.isochronous_xfr) { + temp.offset += temp.len; + } else { + /* get next Page Cache pointer */ + temp.pc = xfer->frbuffers + x; + } + } + + if (xfer->flags_int.control_xfr) { + + /* always setup a valid "pc" pointer for status and sync */ + temp.pc = xfer->frbuffers + 0; + temp.len = 0; + temp.short_pkt = 0; + temp.setup_alt_next = 0; + + /* check if we need to sync */ + if (need_sync) { + /* we need a SYNC point after TX */ + temp.func = &avr32dci_data_tx_sync; + avr32dci_setup_standard_chain_sub(&temp); + } + /* check if we should append a status stage */ + if (!xfer->flags_int.control_act) { + + /* + * Send a DATA1 message and invert the current + * endpoint direction. + */ + if (xfer->endpointno & UE_DIR_IN) { + temp.func = &avr32dci_data_rx; + need_sync = 0; + } else { + temp.func = &avr32dci_data_tx; + need_sync = 1; + } + + avr32dci_setup_standard_chain_sub(&temp); + if (need_sync) { + /* we need a SYNC point after TX */ + temp.func = &avr32dci_data_tx_sync; + avr32dci_setup_standard_chain_sub(&temp); + } + } + } + /* must have at least one frame! */ + td = temp.td; + xfer->td_transfer_last = td; +} + +static void +avr32dci_timeout(void *arg) +{ + struct usb_xfer *xfer = arg; + + DPRINTF("xfer=%p\n", xfer); + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* transfer is transferred */ + avr32dci_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +avr32dci_start_standard_chain(struct usb_xfer *xfer) +{ + DPRINTFN(9, "\n"); + + /* poll one time - will turn on interrupts */ + if (avr32dci_xfer_do_fifo(xfer)) { + uint8_t ep_no = xfer->endpointno & UE_ADDR; + struct avr32dci_softc *sc = AVR32_BUS2SC(xfer->xroot->bus); + + avr32dci_mod_ien(sc, AVR32_INT_EPT_INT(ep_no), 0); + + /* put transfer on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usbd_transfer_timeout_ms(xfer, + &avr32dci_timeout, xfer->timeout); + } + } +} + +static void +avr32dci_root_intr(struct avr32dci_softc *sc) +{ + DPRINTFN(9, "\n"); + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* set port bit */ + sc->sc_hub_idata[0] = 0x02; /* we only have one port */ + + uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, + sizeof(sc->sc_hub_idata)); +} + +static usb_error_t +avr32dci_standard_done_sub(struct usb_xfer *xfer) +{ + struct avr32dci_td *td; + uint32_t len; + uint8_t error; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + + do { + len = td->remainder; + + if (xfer->aframes != xfer->nframes) { + /* + * Verify the length and subtract + * the remainder from "frlengths[]": + */ + if (len > xfer->frlengths[xfer->aframes]) { + td->error = 1; + } else { + xfer->frlengths[xfer->aframes] -= len; + } + } + /* Check for transfer error */ + if (td->error) { + /* the transfer is finished */ + error = 1; + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + td = td->obj_next; + } else { + td = NULL; + } + } else { + /* the transfer is finished */ + td = NULL; + } + error = 0; + break; + } + td = td->obj_next; + + /* this USB frame is complete */ + error = 0; + break; + + } while (0); + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + return (error ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); +} + +static void +avr32dci_standard_done(struct usb_xfer *xfer) +{ + usb_error_t err = 0; + + DPRINTFN(13, "xfer=%p pipe=%p transfer done\n", + xfer, xfer->endpoint); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = avr32dci_standard_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = avr32dci_standard_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = avr32dci_standard_done_sub(xfer); + } +done: + avr32dci_device_done(xfer, err); +} + +/*------------------------------------------------------------------------* + * avr32dci_device_done + * + * NOTE: this function can be called more than one time on the + * same USB transfer! + *------------------------------------------------------------------------*/ +static void +avr32dci_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + struct avr32dci_softc *sc = AVR32_BUS2SC(xfer->xroot->bus); + uint8_t ep_no; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + DPRINTFN(9, "xfer=%p, pipe=%p, error=%d\n", + xfer, xfer->endpoint, error); + + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + ep_no = (xfer->endpointno & UE_ADDR); + + /* disable endpoint interrupt */ + avr32dci_mod_ien(sc, 0, AVR32_INT_EPT_INT(ep_no)); + + DPRINTFN(15, "disabled interrupts!\n"); + } + /* dequeue transfer and start next transfer */ + usbd_transfer_done(xfer, error); +} + +static void +avr32dci_set_stall(struct usb_device *udev, struct usb_xfer *xfer, + struct usb_endpoint *pipe, uint8_t *did_stall) +{ + struct avr32dci_softc *sc; + uint8_t ep_no; + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + DPRINTFN(5, "pipe=%p\n", pipe); + + if (xfer) { + /* cancel any ongoing transfers */ + avr32dci_device_done(xfer, USB_ERR_STALLED); + } + sc = AVR32_BUS2SC(udev->bus); + /* get endpoint number */ + ep_no = (pipe->edesc->bEndpointAddress & UE_ADDR); + /* set stall */ + AVR32_WRITE_4(sc, AVR32_EPTSETSTA(ep_no), AVR32_EPTSTA_FRCESTALL); +} + +static void +avr32dci_clear_stall_sub(struct avr32dci_softc *sc, uint8_t ep_no, + uint8_t ep_type, uint8_t ep_dir) +{ + const struct usb_hw_ep_profile *pf; + uint32_t temp; + uint32_t epsize; + uint8_t n; + + if (ep_type == UE_CONTROL) { + /* clearing stall is not needed */ + return; + } + /* set endpoint reset */ + AVR32_WRITE_4(sc, AVR32_EPTRST, AVR32_EPTRST_MASK(ep_no)); + + /* set stall */ + AVR32_WRITE_4(sc, AVR32_EPTSETSTA(ep_no), AVR32_EPTSTA_FRCESTALL); + + /* reset data toggle */ + AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(ep_no), AVR32_EPTSTA_TOGGLESQ); + + /* clear stall */ + AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(ep_no), AVR32_EPTSTA_FRCESTALL); + + if (ep_type == UE_BULK) { + temp = AVR32_EPTCFG_TYPE_BULK; + } else if (ep_type == UE_INTERRUPT) { + temp = AVR32_EPTCFG_TYPE_INTR; + } else { + temp = AVR32_EPTCFG_TYPE_ISOC | + AVR32_EPTCFG_NB_TRANS(1); + } + if (ep_dir & UE_DIR_IN) { + temp |= AVR32_EPTCFG_EPDIR_IN; + } + avr32dci_get_hw_ep_profile(NULL, &pf, ep_no); + + /* compute endpoint size (use maximum) */ + epsize = pf->max_in_frame_size | pf->max_out_frame_size; + n = 0; + while ((epsize /= 2)) + n++; + temp |= AVR32_EPTCFG_EPSIZE(n); + + /* use the maximum number of banks supported */ + if (ep_no < 1) + temp |= AVR32_EPTCFG_NBANK(1); + else if (ep_no < 3) + temp |= AVR32_EPTCFG_NBANK(2); + else + temp |= AVR32_EPTCFG_NBANK(3); + + AVR32_WRITE_4(sc, AVR32_EPTCFG(ep_no), temp); + + temp = AVR32_READ_4(sc, AVR32_EPTCFG(ep_no)); + + if (!(temp & AVR32_EPTCFG_EPT_MAPD)) { + device_printf(sc->sc_bus.bdev, "Chip rejected configuration\n"); + } else { + AVR32_WRITE_4(sc, AVR32_EPTCTLENB(ep_no), + AVR32_EPTCTL_EPT_ENABL); + } +} + +static void +avr32dci_clear_stall(struct usb_device *udev, struct usb_endpoint *pipe) +{ + struct avr32dci_softc *sc; + struct usb_endpoint_descriptor *ed; + + DPRINTFN(5, "pipe=%p\n", pipe); + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + /* check mode */ + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + /* get softc */ + sc = AVR32_BUS2SC(udev->bus); + + /* get endpoint descriptor */ + ed = pipe->edesc; + + /* reset endpoint */ + avr32dci_clear_stall_sub(sc, + (ed->bEndpointAddress & UE_ADDR), + (ed->bmAttributes & UE_XFERTYPE), + (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); +} + +usb_error_t +avr32dci_init(struct avr32dci_softc *sc) +{ + uint8_t n; + + DPRINTF("start\n"); + + /* set up the bus structure */ + sc->sc_bus.usbrev = USB_REV_1_1; + sc->sc_bus.methods = &avr32dci_bus_methods; + + USB_BUS_LOCK(&sc->sc_bus); + + /* make sure USB is enabled */ + avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_EN_USBA, 0); + + /* turn on clocks */ + (sc->sc_clocks_on) (&sc->sc_bus); + + /* make sure device is re-enumerated */ + avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_DETACH, 0); + + /* wait a little for things to stabilise */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 20); + + /* disable interrupts */ + avr32dci_mod_ien(sc, 0, 0xFFFFFFFF); + + /* enable interrupts */ + avr32dci_mod_ien(sc, AVR32_INT_DET_SUSPD | + AVR32_INT_ENDRESET, 0); + + /* reset all endpoints */ + AVR32_WRITE_4(sc, AVR32_EPTRST, (1 << AVR32_EP_MAX) - 1); + + /* disable all endpoints */ + for (n = 0; n != AVR32_EP_MAX; n++) { + /* disable endpoint */ + AVR32_WRITE_4(sc, AVR32_EPTCTLDIS(n), AVR32_EPTCTL_EPT_ENABL); + } + + /* turn off clocks */ + + avr32dci_clocks_off(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); + + /* catch any lost interrupts */ + + avr32dci_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +avr32dci_uninit(struct avr32dci_softc *sc) +{ + uint8_t n; + + USB_BUS_LOCK(&sc->sc_bus); + + /* turn on clocks */ + (sc->sc_clocks_on) (&sc->sc_bus); + + /* disable interrupts */ + avr32dci_mod_ien(sc, 0, 0xFFFFFFFF); + + /* reset all endpoints */ + AVR32_WRITE_4(sc, AVR32_EPTRST, (1 << AVR32_EP_MAX) - 1); + + /* disable all endpoints */ + for (n = 0; n != AVR32_EP_MAX; n++) { + /* disable endpoint */ + AVR32_WRITE_4(sc, AVR32_EPTCTLDIS(n), AVR32_EPTCTL_EPT_ENABL); + } + + sc->sc_flags.port_powered = 0; + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + avr32dci_pull_down(sc); + avr32dci_clocks_off(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +avr32dci_suspend(struct avr32dci_softc *sc) +{ + /* TODO */ +} + +static void +avr32dci_resume(struct avr32dci_softc *sc) +{ + /* TODO */ +} + +static void +avr32dci_do_poll(struct usb_bus *bus) +{ + struct avr32dci_softc *sc = AVR32_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + avr32dci_interrupt_poll(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/*------------------------------------------------------------------------* + * at91dci bulk support + * at91dci control support + * at91dci interrupt support + *------------------------------------------------------------------------*/ +static void +avr32dci_device_non_isoc_open(struct usb_xfer *xfer) +{ + return; +} + +static void +avr32dci_device_non_isoc_close(struct usb_xfer *xfer) +{ + avr32dci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +avr32dci_device_non_isoc_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +avr32dci_device_non_isoc_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + avr32dci_setup_standard_chain(xfer); + avr32dci_start_standard_chain(xfer); +} + +struct usb_pipe_methods avr32dci_device_non_isoc_methods = +{ + .open = avr32dci_device_non_isoc_open, + .close = avr32dci_device_non_isoc_close, + .enter = avr32dci_device_non_isoc_enter, + .start = avr32dci_device_non_isoc_start, +}; + +/*------------------------------------------------------------------------* + * at91dci full speed isochronous support + *------------------------------------------------------------------------*/ +static void +avr32dci_device_isoc_fs_open(struct usb_xfer *xfer) +{ + return; +} + +static void +avr32dci_device_isoc_fs_close(struct usb_xfer *xfer) +{ + avr32dci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +avr32dci_device_isoc_fs_enter(struct usb_xfer *xfer) +{ + struct avr32dci_softc *sc = AVR32_BUS2SC(xfer->xroot->bus); + uint32_t temp; + uint32_t nframes; + uint8_t ep_no; + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes); + + /* get the current frame index */ + ep_no = xfer->endpointno & UE_ADDR; + nframes = (AVR32_READ_4(sc, AVR32_FNUM) / 8); + + nframes &= AVR32_FRAME_MASK; + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + temp = (nframes - xfer->endpoint->isoc_next) & AVR32_FRAME_MASK; + + if ((xfer->endpoint->is_synced == 0) || + (temp < xfer->nframes)) { + /* + * If there is data underflow or the pipe queue is + * empty we schedule the transfer a few frames ahead + * of the current frame position. Else two isochronous + * transfers might overlap. + */ + xfer->endpoint->isoc_next = (nframes + 3) & AVR32_FRAME_MASK; + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + temp = (xfer->endpoint->isoc_next - nframes) & AVR32_FRAME_MASK; + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + + xfer->nframes; + + /* compute frame number for next insertion */ + xfer->endpoint->isoc_next += xfer->nframes; + + /* setup TDs */ + avr32dci_setup_standard_chain(xfer); +} + +static void +avr32dci_device_isoc_fs_start(struct usb_xfer *xfer) +{ + /* start TD chain */ + avr32dci_start_standard_chain(xfer); +} + +struct usb_pipe_methods avr32dci_device_isoc_fs_methods = +{ + .open = avr32dci_device_isoc_fs_open, + .close = avr32dci_device_isoc_fs_close, + .enter = avr32dci_device_isoc_fs_enter, + .start = avr32dci_device_isoc_fs_start, +}; + +/*------------------------------------------------------------------------* + * at91dci root control support + *------------------------------------------------------------------------* + * Simulate a hardware HUB by handling all the necessary requests. + *------------------------------------------------------------------------*/ + +static const struct usb_device_descriptor avr32dci_devd = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = UDESC_DEVICE, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_HSHUBSTT, + .bMaxPacketSize = 64, + .bcdDevice = {0x00, 0x01}, + .iManufacturer = 1, + .iProduct = 2, + .bNumConfigurations = 1, +}; + +static const struct usb_device_qualifier avr32dci_odevd = { + .bLength = sizeof(struct usb_device_qualifier), + .bDescriptorType = UDESC_DEVICE_QUALIFIER, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_FSHUB, + .bMaxPacketSize0 = 0, + .bNumConfigurations = 0, +}; + +static const struct avr32dci_config_desc avr32dci_confd = { + .confd = { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(avr32dci_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0, + }, + .ifcd = { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = 0, + }, + .endpd = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = (UE_DIR_IN | AVR32_INTR_ENDPT), + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, + .bInterval = 255, + }, +}; + +static const struct usb_hub_descriptor_min avr32dci_hubd = { + .bDescLength = sizeof(avr32dci_hubd), + .bDescriptorType = UDESC_HUB, + .bNbrPorts = 1, + .wHubCharacteristics[0] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF, + .wHubCharacteristics[1] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 8, + .bPwrOn2PwrGood = 50, + .bHubContrCurrent = 0, + .DeviceRemovable = {0}, /* port is removable */ +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_VENDOR \ + 'A', 0, 'V', 0, 'R', 0, '3', 0, '2', 0 + +#define STRING_PRODUCT \ + 'D', 0, 'C', 0, 'I', 0, ' ', 0, 'R', 0, \ + 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \ + 'U', 0, 'B', 0, + +USB_MAKE_STRING_DESC(STRING_LANG, avr32dci_langtab); +USB_MAKE_STRING_DESC(STRING_VENDOR, avr32dci_vendor); +USB_MAKE_STRING_DESC(STRING_PRODUCT, avr32dci_product); + +static usb_error_t +avr32dci_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + struct avr32dci_softc *sc = AVR32_BUS2SC(udev->bus); + const void *ptr; + uint16_t len; + uint16_t value; + uint16_t index; + uint32_t temp; + usb_error_t err; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* buffer reset */ + ptr = (const void *)&sc->sc_hub_temp; + len = 0; + err = 0; + + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + /* demultiplex the control request */ + + switch (req->bmRequestType) { + case UT_READ_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (req->bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_DESCRIPTOR: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SYNCH_FRAME: + goto tr_valid; /* nop */ + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (req->bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_INTERFACE: + switch (req->bRequest) { + case UR_SET_INTERFACE: + goto tr_handle_set_interface; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (req->bRequest) { + case UR_GET_INTERFACE: + goto tr_handle_get_interface; + case UR_GET_STATUS: + goto tr_handle_get_iface_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_INTERFACE: + case UT_WRITE_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_READ_CLASS_INTERFACE: + case UT_READ_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_WRITE_CLASS_DEVICE: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_valid; + case UR_SET_DESCRIPTOR: + case UR_SET_FEATURE: + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_OTHER: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_handle_clear_port_feature; + case UR_SET_FEATURE: + goto tr_handle_set_port_feature; + case UR_CLEAR_TT_BUFFER: + case UR_RESET_TT: + case UR_STOP_TT: + goto tr_valid; + + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_OTHER: + switch (req->bRequest) { + case UR_GET_TT_STATE: + goto tr_handle_get_tt_state; + case UR_GET_STATUS: + goto tr_handle_get_port_status; + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_class_descriptor; + case UR_GET_STATUS: + goto tr_handle_get_class_status; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + goto tr_valid; + +tr_handle_get_descriptor: + switch (value >> 8) { + case UDESC_DEVICE: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(avr32dci_devd); + ptr = (const void *)&avr32dci_devd; + goto tr_valid; + case UDESC_CONFIG: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(avr32dci_confd); + ptr = (const void *)&avr32dci_confd; + goto tr_valid; + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + len = sizeof(avr32dci_langtab); + ptr = (const void *)&avr32dci_langtab; + goto tr_valid; + + case 1: /* Vendor */ + len = sizeof(avr32dci_vendor); + ptr = (const void *)&avr32dci_vendor; + goto tr_valid; + + case 2: /* Product */ + len = sizeof(avr32dci_product); + ptr = (const void *)&avr32dci_product; + goto tr_valid; + default: + break; + } + break; + default: + goto tr_stalled; + } + goto tr_stalled; + +tr_handle_get_config: + len = 1; + sc->sc_hub_temp.wValue[0] = sc->sc_conf; + goto tr_valid; + +tr_handle_get_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); + goto tr_valid; + +tr_handle_set_address: + if (value & 0xFF00) { + goto tr_stalled; + } + sc->sc_rt_addr = value; + goto tr_valid; + +tr_handle_set_config: + if (value >= 2) { + goto tr_stalled; + } + sc->sc_conf = value; + goto tr_valid; + +tr_handle_get_interface: + len = 1; + sc->sc_hub_temp.wValue[0] = 0; + goto tr_valid; + +tr_handle_get_tt_state: +tr_handle_get_class_status: +tr_handle_get_iface_status: +tr_handle_get_ep_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, 0); + goto tr_valid; + +tr_handle_set_halt: +tr_handle_set_interface: +tr_handle_set_wakeup: +tr_handle_clear_wakeup: +tr_handle_clear_halt: + goto tr_valid; + +tr_handle_clear_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index); + + switch (value) { + case UHF_PORT_SUSPEND: + avr32dci_wakeup_peer(sc); + break; + + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 0; + break; + + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 0; + avr32dci_pull_down(sc); + avr32dci_clocks_off(sc); + break; + case UHF_C_PORT_CONNECTION: + /* clear connect change flag */ + sc->sc_flags.change_connect = 0; + + if (!sc->sc_flags.status_bus_reset) { + /* we are not connected */ + break; + } + /* configure the control endpoint */ + /* set endpoint reset */ + AVR32_WRITE_4(sc, AVR32_EPTRST, AVR32_EPTRST_MASK(0)); + + /* set stall */ + AVR32_WRITE_4(sc, AVR32_EPTSETSTA(0), AVR32_EPTSTA_FRCESTALL); + + /* reset data toggle */ + AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(0), AVR32_EPTSTA_TOGGLESQ); + + /* clear stall */ + AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(0), AVR32_EPTSTA_FRCESTALL); + + /* configure */ + AVR32_WRITE_4(sc, AVR32_EPTCFG(0), AVR32_EPTCFG_TYPE_CTRL | + AVR32_EPTCFG_NBANK(1) | AVR32_EPTCFG_EPSIZE(6)); + + temp = AVR32_READ_4(sc, AVR32_EPTCFG(0)); + + if (!(temp & AVR32_EPTCFG_EPT_MAPD)) { + device_printf(sc->sc_bus.bdev, + "Chip rejected configuration\n"); + } else { + AVR32_WRITE_4(sc, AVR32_EPTCTLENB(0), + AVR32_EPTCTL_EPT_ENABL); + } + break; + case UHF_C_PORT_SUSPEND: + sc->sc_flags.change_suspend = 0; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_set_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_SET_PORT_FEATURE\n"); + + switch (value) { + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 1; + break; + case UHF_PORT_SUSPEND: + case UHF_PORT_RESET: + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 1; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_get_port_status: + + DPRINTFN(9, "UR_GET_PORT_STATUS\n"); + + if (index != 1) { + goto tr_stalled; + } + if (sc->sc_flags.status_vbus) { + avr32dci_clocks_on(sc); + avr32dci_pull_up(sc); + } else { + avr32dci_pull_down(sc); + avr32dci_clocks_off(sc); + } + + /* Select Device Side Mode */ + + value = UPS_PORT_MODE_DEVICE; + + /* Check for High Speed */ + if (AVR32_READ_4(sc, AVR32_INTSTA) & AVR32_INT_SPEED) + value |= UPS_HIGH_SPEED; + + if (sc->sc_flags.port_powered) { + value |= UPS_PORT_POWER; + } + if (sc->sc_flags.port_enabled) { + value |= UPS_PORT_ENABLED; + } + if (sc->sc_flags.status_vbus && + sc->sc_flags.status_bus_reset) { + value |= UPS_CURRENT_CONNECT_STATUS; + } + if (sc->sc_flags.status_suspend) { + value |= UPS_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortStatus, value); + + value = 0; + + if (sc->sc_flags.change_connect) { + value |= UPS_C_CONNECT_STATUS; + } + if (sc->sc_flags.change_suspend) { + value |= UPS_C_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortChange, value); + len = sizeof(sc->sc_hub_temp.ps); + goto tr_valid; + +tr_handle_get_class_descriptor: + if (value & 0xFF) { + goto tr_stalled; + } + ptr = (const void *)&avr32dci_hubd; + len = sizeof(avr32dci_hubd); + goto tr_valid; + +tr_stalled: + err = USB_ERR_STALLED; +tr_valid: +done: + *plength = len; + *pptr = ptr; + return (err); +} + +static void +avr32dci_xfer_setup(struct usb_setup_params *parm) +{ + const struct usb_hw_ep_profile *pf; + struct avr32dci_softc *sc; + struct usb_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t n; + uint8_t ep_no; + + sc = AVR32_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + /* + * NOTE: This driver does not use any of the parameters that + * are computed from the following values. Just set some + * reasonable dummies: + */ + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x400; + + usbd_transfer_setup_sub(parm); + + /* + * compute maximum number of TDs + */ + if ((xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) { + + ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */ + + 1 /* SYNC 2 */ ; + } else { + + ntd = xfer->nframes + 1 /* SYNC */ ; + } + + /* + * check if "usbd_transfer_setup_sub" set an error + */ + if (parm->err) + return; + + /* + * allocate transfer descriptors + */ + last_obj = NULL; + + /* + * get profile stuff + */ + ep_no = xfer->endpointno & UE_ADDR; + avr32dci_get_hw_ep_profile(parm->udev, &pf, ep_no); + + if (pf == NULL) { + /* should not happen */ + parm->err = USB_ERR_INVAL; + return; + } + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + for (n = 0; n != ntd; n++) { + + struct avr32dci_td *td; + + if (parm->buf) { + uint32_t temp; + + td = USB_ADD_BYTES(parm->buf, parm->size[0]); + + /* init TD */ + td->max_packet_size = xfer->max_packet_size; + td->ep_no = ep_no; + temp = pf->max_in_frame_size | pf->max_out_frame_size; + td->bank_shift = 0; + while ((temp /= 2)) + td->bank_shift++; + if (pf->support_multi_buffer) { + td->support_multi_buffer = 1; + } + td->obj_next = last_obj; + + last_obj = td; + } + parm->size[0] += sizeof(*td); + } + + xfer->td_start[0] = last_obj; +} + +static void +avr32dci_xfer_unsetup(struct usb_xfer *xfer) +{ + return; +} + +static void +avr32dci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *pipe) +{ + struct avr32dci_softc *sc = AVR32_BUS2SC(udev->bus); + + DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d,%d)\n", + pipe, udev->address, + edesc->bEndpointAddress, udev->flags.usb_mode, + sc->sc_rt_addr, udev->device_index); + + if (udev->device_index != sc->sc_rt_addr) { + + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + if ((udev->speed != USB_SPEED_FULL) && + (udev->speed != USB_SPEED_HIGH)) { + /* not supported */ + return; + } + if ((edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS) + pipe->methods = &avr32dci_device_isoc_fs_methods; + else + pipe->methods = &avr32dci_device_non_isoc_methods; + } +} + +static void +avr32dci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) +{ + struct avr32dci_softc *sc = AVR32_BUS2SC(bus); + + switch (state) { + case USB_HW_POWER_SUSPEND: + avr32dci_suspend(sc); + break; + case USB_HW_POWER_SHUTDOWN: + avr32dci_uninit(sc); + break; + case USB_HW_POWER_RESUME: + avr32dci_resume(sc); + break; + default: + break; + } +} + +struct usb_bus_methods avr32dci_bus_methods = +{ + .endpoint_init = &avr32dci_ep_init, + .xfer_setup = &avr32dci_xfer_setup, + .xfer_unsetup = &avr32dci_xfer_unsetup, + .get_hw_ep_profile = &avr32dci_get_hw_ep_profile, + .set_stall = &avr32dci_set_stall, + .clear_stall = &avr32dci_clear_stall, + .roothub_exec = &avr32dci_roothub_exec, + .xfer_poll = &avr32dci_do_poll, + .set_hw_power_sleep = &avr32dci_set_hw_power_sleep, +}; diff --git a/sys/bus/u4b/controller/avr32dci.h b/sys/bus/u4b/controller/avr32dci.h new file mode 100644 index 0000000000..2d80344fd4 --- /dev/null +++ b/sys/bus/u4b/controller/avr32dci.h @@ -0,0 +1,253 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2009 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _AVR32DCI_H_ +#define _AVR32DCI_H_ + +#define AVR32_MAX_DEVICES (USB_MIN_DEVICES + 1) + +/* Register definitions */ + +#define AVR32_CTRL 0x00 /* Control */ +#define AVR32_CTRL_DEV_ADDR 0x7F +#define AVR32_CTRL_DEV_FADDR_EN 0x80 +#define AVR32_CTRL_DEV_EN_USBA 0x100 +#define AVR32_CTRL_DEV_DETACH 0x200 +#define AVR32_CTRL_DEV_REWAKEUP 0x400 + +#define AVR32_FNUM 0x04 /* Frame Number */ +#define AVR32_FNUM_MASK 0x3FFF +#define AVR32_FRAME_MASK 0x7FF + +/* 0x08 - 0x0C Reserved */ +#define AVR32_IEN 0x10 /* Interrupt Enable */ +#define AVR32_INTSTA 0x14 /* Interrupt Status */ +#define AVR32_CLRINT 0x18 /* Clear Interrupt */ + +#define AVR32_INT_SPEED 0x00000001 /* set if High Speed else Full Speed */ +#define AVR32_INT_DET_SUSPD 0x00000002 +#define AVR32_INT_MICRO_SOF 0x00000004 +#define AVR32_INT_INT_SOF 0x00000008 +#define AVR32_INT_ENDRESET 0x00000010 +#define AVR32_INT_WAKE_UP 0x00000020 +#define AVR32_INT_ENDOFRSM 0x00000040 +#define AVR32_INT_UPSTR_RES 0x00000080 +#define AVR32_INT_EPT_INT(n) (0x00000100 << (n)) +#define AVR32_INT_DMA_INT(n) (0x01000000 << (n)) + +#define AVR32_EPTRST 0x1C /* Endpoints Reset */ +#define AVR32_EPTRST_MASK(n) (0x00000001 << (n)) + +/* 0x20 - 0xCC Reserved */ +#define AVR32_TSTSOFCNT 0xD0 /* Test SOF Counter */ +#define AVR32_TSTCNTA 0xD4 /* Test A Counter */ +#define AVR32_TSTCNTB 0xD8 /* Test B Counter */ +#define AVR32_TSTMODEREG 0xDC /* Test Mode */ +#define AVR32_TST 0xE0 /* Test */ +#define AVR32_TST_NORMAL 0x00000000 +#define AVR32_TST_HS_ONLY 0x00000002 +#define AVR32_TST_FS_ONLY 0x00000003 + +/* 0xE4 - 0xE8 Reserved */ +#define AVR32_IPPADDRSIZE 0xEC /* PADDRSIZE */ +#define AVR32_IPNAME1 0xF0 /* Name1 */ +#define AVR32_IPNAME2 0xF4 /* Name2 */ +#define AVR32_IPFEATURES 0xF8 /* Features */ +#define AVR32_IPFEATURES_NEP(x) (((x) & 0xF) ? ((x) & 0xF) : 0x10) + +#define AVR32_IPVERSION 0xFC /* IP Version */ + +#define _A(base,n) ((base) + (0x20*(n))) +#define AVR32_EPTCFG(n) _A(0x100, n) /* Endpoint Configuration */ +#define AVR32_EPTCFG_EPSIZE(n) ((n)-3) /* power of two */ +#define AVR32_EPTCFG_EPDIR_OUT 0x00000000 +#define AVR32_EPTCFG_EPDIR_IN 0x00000008 +#define AVR32_EPTCFG_TYPE_CTRL 0x00000000 +#define AVR32_EPTCFG_TYPE_ISOC 0x00000100 +#define AVR32_EPTCFG_TYPE_BULK 0x00000200 +#define AVR32_EPTCFG_TYPE_INTR 0x00000300 +#define AVR32_EPTCFG_NBANK(n) (0x00000400*(n)) +#define AVR32_EPTCFG_NB_TRANS(n) (0x00001000*(n)) +#define AVR32_EPTCFG_EPT_MAPD 0x80000000 + +#define AVR32_EPTCTLENB(n) _A(0x104, n) /* Endpoint Control Enable */ +#define AVR32_EPTCTLDIS(n) _A(0x108, n) /* Endpoint Control Disable */ +#define AVR32_EPTCTL(n) _A(0x10C, n) /* Endpoint Control */ +#define AVR32_EPTCTL_EPT_ENABL 0x00000001 +#define AVR32_EPTCTL_AUTO_VALID 0x00000002 +#define AVR32_EPTCTL_INTDIS_DMA 0x00000008 +#define AVR32_EPTCTL_NYET_DIS 0x00000010 +#define AVR32_EPTCTL_DATAX_RX 0x00000040 +#define AVR32_EPTCTL_MDATA_RX 0x00000080 +#define AVR32_EPTCTL_ERR_OVFLW 0x00000100 +#define AVR32_EPTCTL_RX_BK_RDY 0x00000200 +#define AVR32_EPTCTL_TX_COMPLT 0x00000400 +#define AVR32_EPTCTL_TX_PK_RDY 0x00000800 +#define AVR32_EPTCTL_RX_SETUP 0x00001000 +#define AVR32_EPTCTL_STALL_SNT 0x00002000 +#define AVR32_EPTCTL_NAK_IN 0x00004000 +#define AVR32_EPTCTL_NAK_OUT 0x00008000 +#define AVR32_EPTCTL_BUSY_BANK 0x00040000 +#define AVR32_EPTCTL_SHORT_PCKT 0x80000000 + +/* 0x110 Reserved */ +#define AVR32_EPTSETSTA(n) _A(0x114, n) /* Endpoint Set Status */ +#define AVR32_EPTCLRSTA(n) _A(0x118, n) /* Endpoint Clear Status */ +#define AVR32_EPTSTA(n) _A(0x11C, n) /* Endpoint Status */ +#define AVR32_EPTSTA_FRCESTALL 0x00000020 +#define AVR32_EPTSTA_TOGGLESQ_STA(x) (((x) & 0xC0) >> 6) +#define AVR32_EPTSTA_TOGGLESQ 0x00000040 +#define AVR32_EPTSTA_ERR_OVFLW 0x00000100 +#define AVR32_EPTSTA_RX_BK_RDY 0x00000200 +#define AVR32_EPTSTA_TX_COMPLT 0x00000400 +#define AVR32_EPTSTA_TX_PK_RDY 0x00000800 +#define AVR32_EPTSTA_RX_SETUP 0x00001000 +#define AVR32_EPTSTA_STALL_SNT 0x00002000 +#define AVR32_EPTSTA_NAK_IN 0x00004000 +#define AVR32_EPTSTA_NAK_OUT 0x00008000 +#define AVR32_EPTSTA_CURRENT_BANK(x) (((x) & 0x00030000) >> 16) +#define AVR32_EPTSTA_BUSY_BANK_STA(x) (((x) & 0x000C0000) >> 18) +#define AVR32_EPTSTA_BYTE_COUNT(x) (((x) & 0x7FF00000) >> 20) +#define AVR32_EPTSTA_SHRT_PCKT 0x80000000 + +/* 0x300 - 0x30C Reserved */ +#define AVR32_DMANXTDSC 0x310 /* DMA Next Descriptor Address */ +#define AVR32_DMAADDRESS 0x314 /* DMA Channel Address */ + +#define AVR32_READ_4(sc, reg) \ + bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) + +#define AVR32_WRITE_4(sc, reg, data) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data) + +#define AVR32_WRITE_MULTI_4(sc, reg, ptr, len) \ + bus_space_write_multi_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, ptr, len) + +#define AVR32_READ_MULTI_4(sc, reg, ptr, len) \ + bus_space_read_multi_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, ptr, len) + +/* + * Maximum number of endpoints supported: + */ +#define AVR32_EP_MAX 7 + +struct avr32dci_td; + +typedef uint8_t (avr32dci_cmd_t)(struct avr32dci_td *td); +typedef void (avr32dci_clocks_t)(struct usb_bus *); + +struct avr32dci_td { + struct avr32dci_td *obj_next; + avr32dci_cmd_t *func; + struct usb_page_cache *pc; + uint32_t offset; + uint32_t remainder; + uint16_t max_packet_size; + uint8_t bank_shift; + uint8_t error:1; + uint8_t alt_next:1; + uint8_t short_pkt:1; + uint8_t support_multi_buffer:1; + uint8_t did_stall:1; + uint8_t ep_no:3; +}; + +struct avr32dci_std_temp { + avr32dci_cmd_t *func; + struct usb_page_cache *pc; + struct avr32dci_td *td; + struct avr32dci_td *td_next; + uint32_t len; + uint32_t offset; + uint16_t max_frame_size; + uint8_t bank_shift; + uint8_t short_pkt; + /* + * short_pkt = 0: transfer should be short terminated + * short_pkt = 1: transfer should not be short terminated + */ + uint8_t setup_alt_next; + uint8_t did_stall; +}; + +struct avr32dci_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union avr32dci_hub_temp { + uWord wValue; + struct usb_port_status ps; +}; + +struct avr32dci_flags { + uint8_t change_connect:1; + uint8_t change_suspend:1; + uint8_t status_suspend:1; /* set if suspended */ + uint8_t status_vbus:1; /* set if present */ + uint8_t status_bus_reset:1; /* set if reset complete */ + uint8_t remote_wakeup:1; + uint8_t self_powered:1; + uint8_t clocks_off:1; + uint8_t port_powered:1; + uint8_t port_enabled:1; + uint8_t d_pulled_up:1; +}; + +struct avr32dci_softc { + struct usb_bus sc_bus; + union avr32dci_hub_temp sc_hub_temp; + + /* must be set by by the bus interface layer */ + avr32dci_clocks_t *sc_clocks_on; + avr32dci_clocks_t *sc_clocks_off; + + struct usb_device *sc_devices[AVR32_MAX_DEVICES]; + struct resource *sc_irq_res; + void *sc_intr_hdl; + struct resource *sc_io_res; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + uint8_t *physdata; + + uint8_t sc_rt_addr; /* root hub address */ + uint8_t sc_dv_addr; /* device address */ + uint8_t sc_conf; /* root hub config */ + + uint8_t sc_hub_idata[1]; + + struct avr32dci_flags sc_flags; +}; + +/* prototypes */ + +usb_error_t avr32dci_init(struct avr32dci_softc *sc); +void avr32dci_uninit(struct avr32dci_softc *sc); +void avr32dci_interrupt(struct avr32dci_softc *sc); +void avr32dci_vbus_interrupt(struct avr32dci_softc *sc, uint8_t is_on); + +#endif /* _AVR32DCI_H_ */ diff --git a/sys/bus/u4b/controller/dwc_otg.c b/sys/bus/u4b/controller/dwc_otg.c new file mode 100644 index 0000000000..c679ccc491 --- /dev/null +++ b/sys/bus/u4b/controller/dwc_otg.c @@ -0,0 +1,2612 @@ +/*- + * Copyright (c) 2012 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains the driver for the DesignWare series USB 2.0 OTG + * Controller. This driver currently only supports the device mode of + * the USB hardware. + */ + +/* + * LIMITATION: Drivers must be bound to all OUT endpoints in the + * active configuration for this driver to work properly. Blocking any + * OUT endpoint will block all OUT endpoints including the control + * endpoint. Usually this is not a problem. + */ + +/* + * NOTE: Writing to non-existing registers appears to cause an + * internal reset. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define USB_DEBUG_VAR dwc_otg_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define DWC_OTG_BUS2SC(bus) \ + ((struct dwc_otg_softc *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((struct dwc_otg_softc *)0)->sc_bus)))) + +#define DWC_OTG_PC2SC(pc) \ + DWC_OTG_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus) + +#define DWC_OTG_MSK_GINT_ENABLED \ + (DWC_OTG_MSK_GINT_ENUM_DONE | \ + DWC_OTG_MSK_GINT_USB_SUSPEND | \ + DWC_OTG_MSK_GINT_INEP | \ + DWC_OTG_MSK_GINT_RXFLVL | \ + DWC_OTG_MSK_GINT_SESSREQINT) + +#define DWC_OTG_USE_HSIC 0 + +#ifdef USB_DEBUG +static int dwc_otg_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, dwc_otg, CTLFLAG_RW, 0, "USB DWC OTG"); +SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, debug, CTLFLAG_RW, + &dwc_otg_debug, 0, "DWC OTG debug level"); +#endif + +#define DWC_OTG_INTR_ENDPT 1 + +/* prototypes */ + +struct usb_bus_methods dwc_otg_bus_methods; +struct usb_pipe_methods dwc_otg_device_non_isoc_methods; +struct usb_pipe_methods dwc_otg_device_isoc_fs_methods; + +static dwc_otg_cmd_t dwc_otg_setup_rx; +static dwc_otg_cmd_t dwc_otg_data_rx; +static dwc_otg_cmd_t dwc_otg_data_tx; +static dwc_otg_cmd_t dwc_otg_data_tx_sync; +static void dwc_otg_device_done(struct usb_xfer *, usb_error_t); +static void dwc_otg_do_poll(struct usb_bus *); +static void dwc_otg_standard_done(struct usb_xfer *); +static void dwc_otg_root_intr(struct dwc_otg_softc *sc); + +/* + * Here is a configuration that the chip supports. + */ +static const struct usb_hw_ep_profile dwc_otg_ep_profile[1] = { + + [0] = { + .max_in_frame_size = 64,/* fixed */ + .max_out_frame_size = 64, /* fixed */ + .is_simplex = 1, + .support_control = 1, + } +}; + +static void +dwc_otg_get_hw_ep_profile(struct usb_device *udev, + const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) +{ + struct dwc_otg_softc *sc; + + sc = DWC_OTG_BUS2SC(udev->bus); + + if (ep_addr < sc->sc_dev_ep_max) + *ppf = &sc->sc_hw_ep_profile[ep_addr].usb; + else + *ppf = NULL; +} + +static int +dwc_otg_init_fifo(struct dwc_otg_softc *sc) +{ + struct dwc_otg_profile *pf; + uint32_t fifo_size; + uint32_t fifo_regs; + uint32_t tx_start; + uint8_t x; + + fifo_size = sc->sc_fifo_size; + + fifo_regs = 4 * (sc->sc_dev_ep_max + sc->sc_dev_in_ep_max); + + if (fifo_size >= fifo_regs) + fifo_size -= fifo_regs; + else + fifo_size = 0; + + /* split equally for IN and OUT */ + fifo_size /= 2; + + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GRXFSIZ, fifo_size / 4); + + /* align to 4-bytes */ + fifo_size &= ~3; + + tx_start = fifo_size; + + if (fifo_size < 0x40) { + DPRINTFN(-1, "Not enough data space for EP0 FIFO.\n"); + USB_BUS_UNLOCK(&sc->sc_bus); + return (EINVAL); + } + + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GNPTXFSIZ, (0x10 << 16) | (tx_start / 4)); + fifo_size -= 0x40; + tx_start += 0x40; + + /* setup control endpoint profile */ + sc->sc_hw_ep_profile[0].usb = dwc_otg_ep_profile[0]; + + for (x = 1; x != sc->sc_dev_ep_max; x++) { + + pf = sc->sc_hw_ep_profile + x; + + pf->usb.max_out_frame_size = 1024 * 3; + pf->usb.is_simplex = 0; /* assume duplex */ + pf->usb.support_bulk = 1; + pf->usb.support_interrupt = 1; + pf->usb.support_isochronous = 1; + pf->usb.support_out = 1; + + if (x < sc->sc_dev_in_ep_max) { + uint32_t limit; + + limit = (x == 1) ? DWC_OTG_MAX_TXN : + (DWC_OTG_MAX_TXN / 2); + + if (fifo_size >= limit) { + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPTXF(x), + ((limit / 4) << 16) | + (tx_start / 4)); + tx_start += limit; + fifo_size -= limit; + pf->usb.max_in_frame_size = 0x200; + pf->usb.support_in = 1; + pf->max_buffer = limit; + + } else if (fifo_size >= 0x80) { + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPTXF(x), + ((0x80 / 4) << 16) | (tx_start / 4)); + tx_start += 0x80; + fifo_size -= 0x80; + pf->usb.max_in_frame_size = 0x40; + pf->usb.support_in = 1; + + } else { + pf->usb.is_simplex = 1; + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPTXF(x), + (0x0 << 16) | (tx_start / 4)); + } + } else { + pf->usb.is_simplex = 1; + } + + DPRINTF("FIFO%d = IN:%d / OUT:%d\n", x, + pf->usb.max_in_frame_size, + pf->usb.max_out_frame_size); + } + + /* reset RX FIFO */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GRSTCTL, + DWC_OTG_MSK_GRSTCTL_RXFFLUSH); + + /* reset all TX FIFOs */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GRSTCTL, + DWC_OTG_MSK_GRSTCTL_TXFIFO(0x10) | + DWC_OTG_MSK_GRSTCTL_TXFFLUSH); + + return (0); +} + +static void +dwc_otg_clocks_on(struct dwc_otg_softc *sc) +{ + if (sc->sc_flags.clocks_off && + sc->sc_flags.port_powered) { + + DPRINTFN(5, "\n"); + + /* TODO - platform specific */ + + sc->sc_flags.clocks_off = 0; + } +} + +static void +dwc_otg_clocks_off(struct dwc_otg_softc *sc) +{ + if (!sc->sc_flags.clocks_off) { + + DPRINTFN(5, "\n"); + + /* TODO - platform specific */ + + sc->sc_flags.clocks_off = 1; + } +} + +static void +dwc_otg_pull_up(struct dwc_otg_softc *sc) +{ + uint32_t temp; + + /* pullup D+, if possible */ + + if (!sc->sc_flags.d_pulled_up && + sc->sc_flags.port_powered) { + sc->sc_flags.d_pulled_up = 1; + + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DCTL); + temp &= ~DWC_OTG_MSK_DCTL_SOFT_DISC; + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DCTL, temp); + } +} + +static void +dwc_otg_pull_down(struct dwc_otg_softc *sc) +{ + uint32_t temp; + + /* pulldown D+, if possible */ + + if (sc->sc_flags.d_pulled_up) { + sc->sc_flags.d_pulled_up = 0; + + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DCTL); + temp |= DWC_OTG_MSK_DCTL_SOFT_DISC; + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DCTL, temp); + } +} + +static void +dwc_otg_resume_irq(struct dwc_otg_softc *sc) +{ + if (sc->sc_flags.status_suspend) { + /* update status bits */ + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 1; + + /* + * Disable resume interrupt and enable suspend + * interrupt: + */ + sc->sc_irq_mask &= ~DWC_OTG_MSK_GINT_WKUPINT; + sc->sc_irq_mask |= DWC_OTG_MSK_GINT_USB_SUSPEND; + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GINTMSK, sc->sc_irq_mask); + + /* complete root HUB interrupt endpoint */ + dwc_otg_root_intr(sc); + } +} + +static void +dwc_otg_wakeup_peer(struct dwc_otg_softc *sc) +{ + uint32_t temp; + + if (!sc->sc_flags.status_suspend) + return; + + DPRINTFN(5, "Remote wakeup\n"); + + /* enable remote wakeup signalling */ + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DCTL); + temp |= DWC_OTG_MSK_DCTL_REMOTE_WAKEUP; + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DCTL, temp); + + /* Wait 8ms for remote wakeup to complete. */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125); + + temp &= ~DWC_OTG_MSK_DCTL_REMOTE_WAKEUP; + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DCTL, temp); + + /* need to fake resume IRQ */ + dwc_otg_resume_irq(sc); +} + +static void +dwc_otg_set_address(struct dwc_otg_softc *sc, uint8_t addr) +{ + uint32_t temp; + + DPRINTFN(5, "addr=%d\n", addr); + + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DCFG); + temp &= ~DWC_OTG_MSK_DCFG_SET_DEV_ADDR(0x7F); + temp |= DWC_OTG_MSK_DCFG_SET_DEV_ADDR(addr); + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DCFG, temp); +} + +static void +dwc_otg_common_rx_ack(struct dwc_otg_softc *sc) +{ + DPRINTFN(5, "RX status clear\n"); + + /* enable RX FIFO level interrupt */ + sc->sc_irq_mask |= DWC_OTG_MSK_GINT_RXFLVL; + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GINTMSK, sc->sc_irq_mask); + + /* clear cached status */ + sc->sc_last_rx_status = 0; +} + +static uint8_t +dwc_otg_setup_rx(struct dwc_otg_td *td) +{ + struct dwc_otg_softc *sc; + struct usb_device_request req __aligned(4); + uint32_t temp; + uint16_t count; + + /* get pointer to softc */ + sc = DWC_OTG_PC2SC(td->pc); + + /* check endpoint status */ + + if (sc->sc_last_rx_status == 0) + goto not_complete; + + if (DWC_OTG_MSK_GRXSTS_GET_CHANNEL(sc->sc_last_rx_status) != 0) + goto not_complete; + + if ((sc->sc_last_rx_status & DWC_OTG_MSK_GRXSTS_PID) != + DWC_OTG_MSK_GRXSTS_PID_DATA0) { + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + goto not_complete; + } + + if ((sc->sc_last_rx_status & DWC_OTG_MSK_GRXSTS_PACKET_STS) != + DWC_OTG_MSK_GRXSTS_DEV_STP_DATA) { + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + goto not_complete; + } + + DPRINTFN(5, "GRXSTSR=0x%08x\n", sc->sc_last_rx_status); + + /* clear did stall */ + td->did_stall = 0; + + /* get the packet byte count */ + count = DWC_OTG_MSK_GRXSTS_GET_BYTE_CNT(sc->sc_last_rx_status); + + /* verify data length */ + if (count != td->remainder) { + DPRINTFN(0, "Invalid SETUP packet " + "length, %d bytes\n", count); + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + goto not_complete; + } + if (count != sizeof(req)) { + DPRINTFN(0, "Unsupported SETUP packet " + "length, %d bytes\n", count); + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + goto not_complete; + } + + /* copy in control request */ + memcpy(&req, sc->sc_rx_bounce_buffer, sizeof(req)); + + /* copy data into real buffer */ + usbd_copy_in(td->pc, 0, &req, sizeof(req)); + + td->offset = sizeof(req); + td->remainder = 0; + + /* sneak peek the set address */ + if ((req.bmRequestType == UT_WRITE_DEVICE) && + (req.bRequest == UR_SET_ADDRESS)) { + /* must write address before ZLP */ + dwc_otg_set_address(sc, req.wValue[0] & 0x7F); + } + + /* don't send any data by default */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPTSIZ(0), + DWC_OTG_MSK_DXEPTSIZ_SET_NPKT(0) | + DWC_OTG_MSK_DXEPTSIZ_SET_NBYTES(0)); + + temp = sc->sc_in_ctl[0]; + + /* enable IN endpoint */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPCTL(0), + temp | DWC_OTG_MSK_DIEPCTL_ENABLE); + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPCTL(0), + temp | DWC_OTG_MSK_DIEPCTL_SET_NAK); + + /* reset IN endpoint buffer */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GRSTCTL, + DWC_OTG_MSK_GRSTCTL_TXFIFO(0) | + DWC_OTG_MSK_GRSTCTL_TXFFLUSH); + + /* acknowledge RX status */ + dwc_otg_common_rx_ack(sc); + return (0); /* complete */ + +not_complete: + /* abort any ongoing transfer, before enabling again */ + + temp = sc->sc_out_ctl[0]; + + temp |= DWC_OTG_MSK_DOEPCTL_ENABLE | + DWC_OTG_MSK_DOEPCTL_SET_NAK; + + /* enable OUT endpoint */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPCTL(0), temp); + + if (!td->did_stall) { + td->did_stall = 1; + + DPRINTFN(5, "stalling IN and OUT direction\n"); + + /* set stall after enabling endpoint */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPCTL(0), + temp | DWC_OTG_MSK_DOEPCTL_STALL); + + temp = sc->sc_in_ctl[0]; + + /* set stall assuming endpoint is enabled */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPCTL(0), + temp | DWC_OTG_MSK_DIEPCTL_STALL); + } + + /* setup number of buffers to receive */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPTSIZ(0), + DWC_OTG_MSK_DXEPTSIZ_SET_MULTI(3) | + DWC_OTG_MSK_DXEPTSIZ_SET_NPKT(1) | + DWC_OTG_MSK_DXEPTSIZ_SET_NBYTES(sizeof(req))); + + return (1); /* not complete */ +} + +static uint8_t +dwc_otg_data_rx(struct dwc_otg_td *td) +{ + struct dwc_otg_softc *sc; + uint32_t temp; + uint16_t count; + uint8_t got_short; + + got_short = 0; + + /* get pointer to softc */ + sc = DWC_OTG_PC2SC(td->pc); + + /* check endpoint status */ + if (sc->sc_last_rx_status == 0) + goto not_complete; + + if (DWC_OTG_MSK_GRXSTS_GET_CHANNEL(sc->sc_last_rx_status) != td->ep_no) + goto not_complete; + + /* check for SETUP packet */ + if ((sc->sc_last_rx_status & DWC_OTG_MSK_GRXSTS_PACKET_STS) == + DWC_OTG_MSK_GRXSTS_DEV_STP_DATA) { + if (td->remainder == 0) { + /* + * We are actually complete and have + * received the next SETUP + */ + DPRINTFN(5, "faking complete\n"); + return (0); /* complete */ + } + /* + * USB Host Aborted the transfer. + */ + td->error = 1; + return (0); /* complete */ + } + + if ((sc->sc_last_rx_status & DWC_OTG_MSK_GRXSTS_PACKET_STS) != + DWC_OTG_MSK_GRXSTS_DEV_OUT_DATA) { + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + goto not_complete; + } + + /* get the packet byte count */ + count = DWC_OTG_MSK_GRXSTS_GET_BYTE_CNT(sc->sc_last_rx_status); + + /* verify the packet byte count */ + if (count != td->max_packet_size) { + if (count < td->max_packet_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error = 1; + + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + return (0); /* we are complete */ + } + } + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error = 1; + + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + return (0); /* we are complete */ + } + + usbd_copy_in(td->pc, td->offset, sc->sc_rx_bounce_buffer, count); + td->remainder -= count; + td->offset += count; + + /* release FIFO */ + dwc_otg_common_rx_ack(sc); + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) { + /* we are complete */ + return (0); + } + /* else need to receive a zero length packet */ + } + +not_complete: + + temp = sc->sc_out_ctl[td->ep_no]; + + temp |= DWC_OTG_MSK_DOEPCTL_ENABLE | + DWC_OTG_MSK_DOEPCTL_CLR_NAK; + + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPCTL(td->ep_no), temp); + + /* enable SETUP and transfer complete interrupt */ + if (td->ep_no == 0) { + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPTSIZ(0), + DWC_OTG_MSK_DXEPTSIZ_SET_NPKT(1) | + DWC_OTG_MSK_DXEPTSIZ_SET_NBYTES(td->max_packet_size)); + } else { + /* allow reception of multiple packets */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPTSIZ(td->ep_no), + DWC_OTG_MSK_DXEPTSIZ_SET_MULTI(1) | + DWC_OTG_MSK_DXEPTSIZ_SET_NPKT(4) | + DWC_OTG_MSK_DXEPTSIZ_SET_NBYTES(4 * + ((td->max_packet_size + 3) & ~3))); + } + return (1); /* not complete */ +} + +static uint8_t +dwc_otg_data_tx(struct dwc_otg_td *td) +{ + struct dwc_otg_softc *sc; + uint32_t max_buffer; + uint32_t count; + uint32_t fifo_left; + uint32_t mpkt; + uint32_t temp; + uint8_t to; + + to = 3; /* don't loop forever! */ + + /* get pointer to softc */ + sc = DWC_OTG_PC2SC(td->pc); + + max_buffer = sc->sc_hw_ep_profile[td->ep_no].max_buffer; + +repeat: + /* check for for endpoint 0 data */ + + temp = sc->sc_last_rx_status; + + if ((td->ep_no == 0) && (temp != 0) && + (DWC_OTG_MSK_GRXSTS_GET_CHANNEL(temp) == 0)) { + + if ((temp & DWC_OTG_MSK_GRXSTS_PACKET_STS) != + DWC_OTG_MSK_GRXSTS_DEV_STP_DATA) { + + /* dump data - wrong direction */ + dwc_otg_common_rx_ack(sc); + } else { + /* + * The current transfer was cancelled + * by the USB Host: + */ + td->error = 1; + return (0); /* complete */ + } + } + + /* fill in more TX data, if possible */ + if (td->tx_bytes != 0) { + + uint16_t cpkt; + + /* check if packets have been transferred */ + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DIEPTSIZ(td->ep_no)); + + /* get current packet number */ + cpkt = DWC_OTG_MSK_DXEPTSIZ_GET_NPKT(temp); + + if (cpkt >= td->npkt) { + fifo_left = 0; + } else { + if (max_buffer != 0) { + fifo_left = (td->npkt - cpkt) * + td->max_packet_size; + + if (fifo_left > max_buffer) + fifo_left = max_buffer; + } else { + fifo_left = td->max_packet_size; + } + } + + count = td->tx_bytes; + if (count > fifo_left) + count = fifo_left; + + if (count != 0) { + + /* clear topmost word before copy */ + sc->sc_tx_bounce_buffer[(count - 1) / 4] = 0; + + /* copy out data */ + usbd_copy_out(td->pc, td->offset, + sc->sc_tx_bounce_buffer, count); + + /* transfer data into FIFO */ + bus_space_write_region_4(sc->sc_io_tag, sc->sc_io_hdl, + DWC_OTG_REG_DFIFO(td->ep_no), + sc->sc_tx_bounce_buffer, (count + 3) / 4); + + td->tx_bytes -= count; + td->remainder -= count; + td->offset += count; + td->npkt = cpkt; + } + if (td->tx_bytes != 0) + goto not_complete; + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) + return (0); /* complete */ + + /* else we need to transmit a short packet */ + } + } + + /* check if no packets have been transferred */ + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DIEPTSIZ(td->ep_no)); + + if (DWC_OTG_MSK_DXEPTSIZ_GET_NPKT(temp) != 0) { + + DPRINTFN(5, "busy ep=%d npkt=%d DIEPTSIZ=0x%08x " + "DIEPCTL=0x%08x\n", td->ep_no, + DWC_OTG_MSK_DXEPTSIZ_GET_NPKT(temp), + temp, DWC_OTG_READ_4(sc, DWC_OTG_REG_DIEPCTL(td->ep_no))); + + goto not_complete; + } + + DPRINTFN(5, "rem=%u ep=%d\n", td->remainder, td->ep_no); + + /* try to optimise by sending more data */ + if ((max_buffer != 0) && ((td->max_packet_size & 3) == 0)) { + + /* send multiple packets at the same time */ + mpkt = max_buffer / td->max_packet_size; + + if (mpkt > 0x3FE) + mpkt = 0x3FE; + + count = td->remainder; + if (count > 0x7FFFFF) + count = 0x7FFFFF - (0x7FFFFF % td->max_packet_size); + + td->npkt = count / td->max_packet_size; + + /* + * NOTE: We could use 0x3FE instead of "mpkt" in the + * check below to get more throughput, but then we + * have a dependency towards non-generic chip features + * to disable the TX-FIFO-EMPTY interrupts on a per + * endpoint basis. Increase the maximum buffer size of + * the IN endpoint to increase the performance. + */ + if (td->npkt > mpkt) { + td->npkt = mpkt; + count = td->max_packet_size * mpkt; + } else if ((count == 0) || (count % td->max_packet_size)) { + /* we are transmitting a short packet */ + td->npkt++; + td->short_pkt = 1; + } + } else { + /* send one packet at a time */ + mpkt = 1; + count = td->max_packet_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + td->npkt = 1; + } + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPTSIZ(td->ep_no), + DWC_OTG_MSK_DXEPTSIZ_SET_MULTI(1) | + DWC_OTG_MSK_DXEPTSIZ_SET_NPKT(td->npkt) | + DWC_OTG_MSK_DXEPTSIZ_SET_NBYTES(count)); + + /* make room for buffering */ + td->npkt += mpkt; + + temp = sc->sc_in_ctl[td->ep_no]; + + /* must enable before writing data to FIFO */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPCTL(td->ep_no), temp | + DWC_OTG_MSK_DIEPCTL_ENABLE | + DWC_OTG_MSK_DIEPCTL_CLR_NAK); + + td->tx_bytes = count; + + /* check remainder */ + if (td->tx_bytes == 0 && + td->remainder == 0) { + if (td->short_pkt) + return (0); /* complete */ + + /* else we need to transmit a short packet */ + } + + if (--to) + goto repeat; + +not_complete: + return (1); /* not complete */ +} + +static uint8_t +dwc_otg_data_tx_sync(struct dwc_otg_td *td) +{ + struct dwc_otg_softc *sc; + uint32_t temp; + + /* get pointer to softc */ + sc = DWC_OTG_PC2SC(td->pc); + + /* + * If all packets are transferred we are complete: + */ + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DIEPTSIZ(td->ep_no)); + + /* check that all packets have been transferred */ + if (DWC_OTG_MSK_DXEPTSIZ_GET_NPKT(temp) != 0) { + DPRINTFN(5, "busy ep=%d\n", td->ep_no); + goto not_complete; + } + return (0); + +not_complete: + + /* we only want to know if there is a SETUP packet or free IN packet */ + + temp = sc->sc_last_rx_status; + + if ((td->ep_no == 0) && (temp != 0) && + (DWC_OTG_MSK_GRXSTS_GET_CHANNEL(temp) == 0)) { + + if ((temp & DWC_OTG_MSK_GRXSTS_PACKET_STS) == + DWC_OTG_MSK_GRXSTS_DEV_STP_DATA) { + DPRINTFN(5, "faking complete\n"); + /* + * Race condition: We are complete! + */ + return (0); + } else { + /* dump data - wrong direction */ + dwc_otg_common_rx_ack(sc); + } + } + return (1); /* not complete */ +} + +static uint8_t +dwc_otg_xfer_do_fifo(struct usb_xfer *xfer) +{ + struct dwc_otg_td *td; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + while (1) { + if ((td->func) (td)) { + /* operation in progress */ + break; + } + if (((void *)td) == xfer->td_transfer_last) { + goto done; + } + if (td->error) { + goto done; + } else if (td->remainder > 0) { + /* + * We had a short transfer. If there is no alternate + * next, stop processing ! + */ + if (!td->alt_next) + goto done; + } + + /* + * Fetch the next transfer descriptor and transfer + * some flags to the next transfer descriptor + */ + td = td->obj_next; + xfer->td_transfer_cache = td; + } + return (1); /* not complete */ + +done: + /* compute all actual lengths */ + + dwc_otg_standard_done(xfer); + return (0); /* complete */ +} + +static void +dwc_otg_interrupt_poll(struct dwc_otg_softc *sc) +{ + struct usb_xfer *xfer; + uint32_t temp; + uint8_t got_rx_status; + +repeat: + if (sc->sc_last_rx_status == 0) { + + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_GINTSTS); + if (temp & DWC_OTG_MSK_GINT_RXFLVL) { + /* pop current status */ + sc->sc_last_rx_status = + DWC_OTG_READ_4(sc, DWC_OTG_REG_GRXSTSP); + } + + if (sc->sc_last_rx_status != 0) { + + uint32_t temp; + uint8_t ep_no; + + temp = DWC_OTG_MSK_GRXSTS_GET_BYTE_CNT( + sc->sc_last_rx_status); + ep_no = DWC_OTG_MSK_GRXSTS_GET_CHANNEL( + sc->sc_last_rx_status); + + /* receive data, if any */ + if (temp != 0) { + DPRINTF("Reading %d bytes from ep %d\n", temp, ep_no); + bus_space_read_region_4(sc->sc_io_tag, sc->sc_io_hdl, + DWC_OTG_REG_DFIFO(ep_no), + sc->sc_rx_bounce_buffer, (temp + 3) / 4); + } + + temp = sc->sc_last_rx_status & + DWC_OTG_MSK_GRXSTS_PACKET_STS; + + /* non-data messages we simply skip */ + if (temp != DWC_OTG_MSK_GRXSTS_DEV_STP_DATA && + temp != DWC_OTG_MSK_GRXSTS_DEV_OUT_DATA) { + dwc_otg_common_rx_ack(sc); + goto repeat; + } + + /* check if we should dump the data */ + if (!(sc->sc_active_out_ep & (1U << ep_no))) { + dwc_otg_common_rx_ack(sc); + goto repeat; + } + + got_rx_status = 1; + + DPRINTFN(5, "RX status = 0x%08x: ch=%d pid=%d bytes=%d sts=%d\n", + sc->sc_last_rx_status, ep_no, + (sc->sc_last_rx_status >> 15) & 3, + DWC_OTG_MSK_GRXSTS_GET_BYTE_CNT(sc->sc_last_rx_status), + (sc->sc_last_rx_status >> 17) & 15); + } else { + got_rx_status = 0; + } + } else { + uint8_t ep_no; + + ep_no = DWC_OTG_MSK_GRXSTS_GET_CHANNEL( + sc->sc_last_rx_status); + + /* check if we should dump the data */ + if (!(sc->sc_active_out_ep & (1U << ep_no))) { + dwc_otg_common_rx_ack(sc); + goto repeat; + } + + got_rx_status = 1; + } + + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + if (!dwc_otg_xfer_do_fifo(xfer)) { + /* queue has been modified */ + goto repeat; + } + } + + if (got_rx_status) { + if (sc->sc_last_rx_status == 0) + goto repeat; + + /* disable RX FIFO level interrupt */ + sc->sc_irq_mask &= ~DWC_OTG_MSK_GINT_RXFLVL; + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GINTMSK, sc->sc_irq_mask); + } +} + +static void +dwc_otg_vbus_interrupt(struct dwc_otg_softc *sc, uint8_t is_on) +{ + DPRINTFN(5, "vbus = %u\n", is_on); + + if (is_on) { + if (!sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 1; + + /* complete root HUB interrupt endpoint */ + + dwc_otg_root_intr(sc); + } + } else { + if (sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* complete root HUB interrupt endpoint */ + + dwc_otg_root_intr(sc); + } + } +} + +void +dwc_otg_interrupt(struct dwc_otg_softc *sc) +{ + uint32_t status; + + USB_BUS_LOCK(&sc->sc_bus); + + /* read and clear interrupt status */ + status = DWC_OTG_READ_4(sc, DWC_OTG_REG_GINTSTS); + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GINTSTS, status); + + DPRINTFN(14, "GINTSTS=0x%08x\n", status); + + /* check for any bus state change interrupts */ + if (status & DWC_OTG_MSK_GINT_ENUM_DONE) { + + uint32_t temp; + + DPRINTFN(5, "end of reset\n"); + + /* set correct state */ + sc->sc_flags.status_bus_reset = 1; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* reset FIFOs */ + dwc_otg_init_fifo(sc); + + /* reset function address */ + dwc_otg_set_address(sc, 0); + + /* reset active endpoints */ + sc->sc_active_out_ep = 1; + + /* figure out enumeration speed */ + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DSTS); + if (DWC_OTG_MSK_DSTS_GET_ENUM_SPEED(temp) == + DWC_OTG_MSK_DSTS_ENUM_SPEED_HI) + sc->sc_flags.status_high_speed = 1; + else + sc->sc_flags.status_high_speed = 0; + + /* disable resume interrupt and enable suspend interrupt */ + + sc->sc_irq_mask &= ~DWC_OTG_MSK_GINT_WKUPINT; + sc->sc_irq_mask |= DWC_OTG_MSK_GINT_USB_SUSPEND; + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GINTMSK, sc->sc_irq_mask); + + /* complete root HUB interrupt endpoint */ + dwc_otg_root_intr(sc); + } + /* + * If resume and suspend is set at the same time we interpret + * that like RESUME. Resume is set when there is at least 3 + * milliseconds of inactivity on the USB BUS. + */ + if (status & DWC_OTG_MSK_GINT_WKUPINT) { + + DPRINTFN(5, "resume interrupt\n"); + + dwc_otg_resume_irq(sc); + + } else if (status & DWC_OTG_MSK_GINT_USB_SUSPEND) { + + DPRINTFN(5, "suspend interrupt\n"); + + if (!sc->sc_flags.status_suspend) { + /* update status bits */ + sc->sc_flags.status_suspend = 1; + sc->sc_flags.change_suspend = 1; + + /* + * Disable suspend interrupt and enable resume + * interrupt: + */ + sc->sc_irq_mask &= ~DWC_OTG_MSK_GINT_USB_SUSPEND; + sc->sc_irq_mask |= DWC_OTG_MSK_GINT_WKUPINT; + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GINTMSK, sc->sc_irq_mask); + + /* complete root HUB interrupt endpoint */ + dwc_otg_root_intr(sc); + } + } + /* check VBUS */ + if (status & (DWC_OTG_MSK_GINT_USB_SUSPEND | + DWC_OTG_MSK_GINT_SESSREQINT)) { + uint32_t temp; + + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_GOTGCTL); + + DPRINTFN(5, "GOTGCTL=0x%08x\n", temp); + + dwc_otg_vbus_interrupt(sc, + (temp & DWC_OTG_MSK_GOTGCTL_BSESS_VALID) ? 1 : 0); + } + + /* clear all IN endpoint interrupts */ + if (status & DWC_OTG_MSK_GINT_INEP) { + uint32_t temp; + uint8_t x; + + for (x = 0; x != sc->sc_dev_in_ep_max; x++) { + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DIEPINT(x)); + if (temp == 0) + continue; + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPINT(x), temp); + } + } + +#if 0 + /* check if we should poll the FIFOs */ + if (status & (DWC_OTG_MSK_GINT_RXFLVL | DWC_OTG_MSK_GINT_INEP)) +#endif + /* poll FIFO(s) */ + dwc_otg_interrupt_poll(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +dwc_otg_setup_standard_chain_sub(struct dwc_otg_std_temp *temp) +{ + struct dwc_otg_td *td; + + /* get current Transfer Descriptor */ + td = temp->td_next; + temp->td = td; + + /* prepare for next TD */ + temp->td_next = td->obj_next; + + /* fill out the Transfer Descriptor */ + td->func = temp->func; + td->pc = temp->pc; + td->offset = temp->offset; + td->remainder = temp->len; + td->tx_bytes = 0; + td->error = 0; + td->npkt = 1; + td->did_stall = temp->did_stall; + td->short_pkt = temp->short_pkt; + td->alt_next = temp->setup_alt_next; +} + +static void +dwc_otg_setup_standard_chain(struct usb_xfer *xfer) +{ + struct dwc_otg_std_temp temp; + struct dwc_otg_td *td; + uint32_t x; + uint8_t need_sync; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpointno), + xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); + + temp.max_frame_size = xfer->max_frame_size; + + td = xfer->td_start[0]; + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + /* setup temp */ + + temp.pc = NULL; + temp.td = NULL; + temp.td_next = xfer->td_start[0]; + temp.offset = 0; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.did_stall = !xfer->flags_int.control_stall; + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.func = &dwc_otg_setup_rx; + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.short_pkt = temp.len ? 1 : 0; + + /* check for last frame */ + if (xfer->nframes == 1) { + /* no STATUS stage yet, SETUP is last */ + if (xfer->flags_int.control_act) + temp.setup_alt_next = 0; + } + + dwc_otg_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + if (x != xfer->nframes) { + if (xfer->endpointno & UE_DIR_IN) { + temp.func = &dwc_otg_data_tx; + need_sync = 1; + } else { + temp.func = &dwc_otg_data_rx; + need_sync = 0; + } + + /* setup "pc" pointer */ + temp.pc = xfer->frbuffers + x; + } else { + need_sync = 0; + } + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + + x++; + + if (x == xfer->nframes) { + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_act) { + temp.setup_alt_next = 0; + } + } else { + temp.setup_alt_next = 0; + } + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.short_pkt = 0; + + } else { + + /* regular data transfer */ + + temp.short_pkt = (xfer->flags.force_short_xfer ? 0 : 1); + } + + dwc_otg_setup_standard_chain_sub(&temp); + + if (xfer->flags_int.isochronous_xfr) { + temp.offset += temp.len; + } else { + /* get next Page Cache pointer */ + temp.pc = xfer->frbuffers + x; + } + } + + if (xfer->flags_int.control_xfr) { + + /* always setup a valid "pc" pointer for status and sync */ + temp.pc = xfer->frbuffers + 0; + temp.len = 0; + temp.short_pkt = 0; + temp.setup_alt_next = 0; + + /* check if we need to sync */ + if (need_sync) { + /* we need a SYNC point after TX */ + temp.func = &dwc_otg_data_tx_sync; + dwc_otg_setup_standard_chain_sub(&temp); + } + + /* check if we should append a status stage */ + if (!xfer->flags_int.control_act) { + + /* + * Send a DATA1 message and invert the current + * endpoint direction. + */ + if (xfer->endpointno & UE_DIR_IN) { + temp.func = &dwc_otg_data_rx; + need_sync = 0; + } else { + temp.func = &dwc_otg_data_tx; + need_sync = 1; + } + + dwc_otg_setup_standard_chain_sub(&temp); + if (need_sync) { + /* we need a SYNC point after TX */ + temp.func = &dwc_otg_data_tx_sync; + dwc_otg_setup_standard_chain_sub(&temp); + } + } + } else { + /* check if we need to sync */ + if (need_sync) { + + temp.pc = xfer->frbuffers + 0; + temp.len = 0; + temp.short_pkt = 0; + temp.setup_alt_next = 0; + + /* we need a SYNC point after TX */ + temp.func = &dwc_otg_data_tx_sync; + dwc_otg_setup_standard_chain_sub(&temp); + } + } + + /* must have at least one frame! */ + td = temp.td; + xfer->td_transfer_last = td; +} + +static void +dwc_otg_timeout(void *arg) +{ + struct usb_xfer *xfer = arg; + + DPRINTF("xfer=%p\n", xfer); + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* transfer is transferred */ + dwc_otg_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +dwc_otg_start_standard_chain(struct usb_xfer *xfer) +{ + DPRINTFN(9, "\n"); + + /* poll one time - will turn on interrupts */ + if (dwc_otg_xfer_do_fifo(xfer)) { + + /* put transfer on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usbd_transfer_timeout_ms(xfer, + &dwc_otg_timeout, xfer->timeout); + } + } +} + +static void +dwc_otg_root_intr(struct dwc_otg_softc *sc) +{ + DPRINTFN(9, "\n"); + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* set port bit */ + sc->sc_hub_idata[0] = 0x02; /* we only have one port */ + + uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, + sizeof(sc->sc_hub_idata)); +} + +static usb_error_t +dwc_otg_standard_done_sub(struct usb_xfer *xfer) +{ + struct dwc_otg_td *td; + uint32_t len; + uint8_t error; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + + do { + len = td->remainder; + + if (xfer->aframes != xfer->nframes) { + /* + * Verify the length and subtract + * the remainder from "frlengths[]": + */ + if (len > xfer->frlengths[xfer->aframes]) { + td->error = 1; + } else { + xfer->frlengths[xfer->aframes] -= len; + } + } + /* Check for transfer error */ + if (td->error) { + /* the transfer is finished */ + error = 1; + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + td = td->obj_next; + } else { + td = NULL; + } + } else { + /* the transfer is finished */ + td = NULL; + } + error = 0; + break; + } + td = td->obj_next; + + /* this USB frame is complete */ + error = 0; + break; + + } while (0); + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + return (error ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); +} + +static void +dwc_otg_standard_done(struct usb_xfer *xfer) +{ + usb_error_t err = 0; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = dwc_otg_standard_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = dwc_otg_standard_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = dwc_otg_standard_done_sub(xfer); + } +done: + dwc_otg_device_done(xfer, err); +} + +/*------------------------------------------------------------------------* + * dwc_otg_device_done + * + * NOTE: this function can be called more than one time on the + * same USB transfer! + *------------------------------------------------------------------------*/ +static void +dwc_otg_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + DPRINTFN(9, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); + + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + DPRINTFN(15, "disabled interrupts!\n"); + } + /* dequeue transfer and start next transfer */ + usbd_transfer_done(xfer, error); +} + +static void +dwc_otg_set_stall(struct usb_device *udev, struct usb_xfer *xfer, + struct usb_endpoint *ep, uint8_t *did_stall) +{ + struct dwc_otg_softc *sc; + uint32_t temp; + uint32_t reg; + uint8_t ep_no; + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + if (xfer) { + /* cancel any ongoing transfers */ + dwc_otg_device_done(xfer, USB_ERR_STALLED); + } + sc = DWC_OTG_BUS2SC(udev->bus); + + /* get endpoint address */ + ep_no = ep->edesc->bEndpointAddress; + + DPRINTFN(5, "endpoint=0x%x\n", ep_no); + + if (ep_no & UE_DIR_IN) { + reg = DWC_OTG_REG_DIEPCTL(ep_no & UE_ADDR); + temp = sc->sc_in_ctl[ep_no & UE_ADDR]; + } else { + reg = DWC_OTG_REG_DOEPCTL(ep_no & UE_ADDR); + temp = sc->sc_out_ctl[ep_no & UE_ADDR]; + } + + /* disable and stall endpoint */ + DWC_OTG_WRITE_4(sc, reg, temp | DWC_OTG_MSK_DOEPCTL_DISABLE); + DWC_OTG_WRITE_4(sc, reg, temp | DWC_OTG_MSK_DOEPCTL_STALL); + + /* clear active OUT ep */ + if (!(ep_no & UE_DIR_IN)) { + + sc->sc_active_out_ep &= ~(1U << (ep_no & UE_ADDR)); + + if (sc->sc_last_rx_status != 0 && + (ep_no & UE_ADDR) == DWC_OTG_MSK_GRXSTS_GET_CHANNEL( + sc->sc_last_rx_status)) { + /* dump data */ + dwc_otg_common_rx_ack(sc); + /* poll interrupt */ + dwc_otg_interrupt_poll(sc); + } + } +} + +static void +dwc_otg_clear_stall_sub(struct dwc_otg_softc *sc, uint32_t mps, + uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) +{ + uint32_t reg; + uint32_t temp; + + if (ep_type == UE_CONTROL) { + /* clearing stall is not needed */ + return; + } + + if (ep_dir) { + reg = DWC_OTG_REG_DIEPCTL(ep_no); + } else { + reg = DWC_OTG_REG_DOEPCTL(ep_no); + sc->sc_active_out_ep |= (1U << ep_no); + } + + /* round up and mask away the multiplier count */ + mps = (mps + 3) & 0x7FC; + + if (ep_type == UE_BULK) { + temp = DWC_OTG_MSK_EP_SET_TYPE( + DWC_OTG_MSK_EP_TYPE_BULK) | + DWC_OTG_MSK_DIEPCTL_USB_AEP; + } else if (ep_type == UE_INTERRUPT) { + temp = DWC_OTG_MSK_EP_SET_TYPE( + DWC_OTG_MSK_EP_TYPE_INTERRUPT) | + DWC_OTG_MSK_DIEPCTL_USB_AEP; + } else { + temp = DWC_OTG_MSK_EP_SET_TYPE( + DWC_OTG_MSK_EP_TYPE_ISOC) | + DWC_OTG_MSK_DIEPCTL_USB_AEP; + } + + temp |= DWC_OTG_MSK_DIEPCTL_MPS(mps); + temp |= DWC_OTG_MSK_DIEPCTL_FNUM(ep_no); + + if (ep_dir) + sc->sc_in_ctl[ep_no] = temp; + else + sc->sc_out_ctl[ep_no] = temp; + + DWC_OTG_WRITE_4(sc, reg, temp | DWC_OTG_MSK_DOEPCTL_DISABLE); + DWC_OTG_WRITE_4(sc, reg, temp | DWC_OTG_MSK_DOEPCTL_SET_DATA0); + DWC_OTG_WRITE_4(sc, reg, temp | DWC_OTG_MSK_DIEPCTL_SET_NAK); + + /* we only reset the transmit FIFO */ + if (ep_dir) { + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GRSTCTL, + DWC_OTG_MSK_GRSTCTL_TXFIFO(ep_no) | + DWC_OTG_MSK_GRSTCTL_TXFFLUSH); + + DWC_OTG_WRITE_4(sc, + DWC_OTG_REG_DIEPTSIZ(ep_no), 0); + } + + /* poll interrupt */ + dwc_otg_interrupt_poll(sc); +} + +static void +dwc_otg_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) +{ + struct dwc_otg_softc *sc; + struct usb_endpoint_descriptor *ed; + + DPRINTFN(5, "endpoint=%p\n", ep); + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + /* check mode */ + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + /* get softc */ + sc = DWC_OTG_BUS2SC(udev->bus); + + /* get endpoint descriptor */ + ed = ep->edesc; + + /* reset endpoint */ + dwc_otg_clear_stall_sub(sc, + UGETW(ed->wMaxPacketSize), + (ed->bEndpointAddress & UE_ADDR), + (ed->bmAttributes & UE_XFERTYPE), + (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); +} + +static void +dwc_otg_device_state_change(struct usb_device *udev) +{ + struct dwc_otg_softc *sc; + uint8_t x; + + /* get softc */ + sc = DWC_OTG_BUS2SC(udev->bus); + + /* deactivate all other endpoint but the control endpoint */ + if (udev->state == USB_STATE_CONFIGURED || + udev->state == USB_STATE_ADDRESSED) { + + USB_BUS_LOCK(&sc->sc_bus); + + for (x = 1; x != sc->sc_dev_ep_max; x++) { + + if (x < sc->sc_dev_in_ep_max) { + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPCTL(x), + DWC_OTG_MSK_DIEPCTL_DISABLE); + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPCTL(x), 0); + } + + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPCTL(x), + DWC_OTG_MSK_DOEPCTL_DISABLE); + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPCTL(x), 0); + } + USB_BUS_UNLOCK(&sc->sc_bus); + } +} + +int +dwc_otg_init(struct dwc_otg_softc *sc) +{ + uint32_t temp; + + DPRINTF("start\n"); + + /* set up the bus structure */ + sc->sc_bus.usbrev = USB_REV_2_0; + sc->sc_bus.methods = &dwc_otg_bus_methods; + + /* reset active endpoints */ + sc->sc_active_out_ep = 1; + + USB_BUS_LOCK(&sc->sc_bus); + + /* turn on clocks */ + dwc_otg_clocks_on(sc); + + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_GSNPSID); + DPRINTF("Version = 0x%08x\n", temp); + + /* disconnect */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DCTL, + DWC_OTG_MSK_DCTL_SOFT_DISC); + + /* wait for host to detect disconnect */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 32); + + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GRSTCTL, + DWC_OTG_MSK_GRSTCTL_CSFTRST); + + /* wait a little bit for block to reset */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 128); + + /* select HSIC or non-HSIC mode */ + if (DWC_OTG_USE_HSIC) { + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GUSBCFG, + DWC_OTG_MSK_GUSBCFG_PHY_INTF | + DWC_OTG_MSK_GUSBCFG_TRD_TIM(5)); + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GOTGCTL, + 0x000000EC); + + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_GLPMCFG); + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GLPMCFG, + temp & ~DWC_OTG_MSK_GLPMCFG_HSIC_CONN); + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GLPMCFG, + temp | DWC_OTG_MSK_GLPMCFG_HSIC_CONN); + } else { + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GUSBCFG, + DWC_OTG_MSK_GUSBCFG_ULPI_UMTI_SEL | + DWC_OTG_MSK_GUSBCFG_TRD_TIM(5)); + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GOTGCTL, 0); + + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_GLPMCFG); + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GLPMCFG, + temp & ~DWC_OTG_MSK_GLPMCFG_HSIC_CONN); + } + + /* clear global nak */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DCTL, + DWC_OTG_MSK_DCTL_CGOUT_NAK | + DWC_OTG_MSK_DCTL_CGNPIN_NAK); + + /* enable USB port */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_PCGCCTL, 0); + + /* pull up D+ */ + dwc_otg_pull_up(sc); + + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_GHWCFG3); + + sc->sc_fifo_size = 4 * DWC_OTG_MSK_GHWCFG3_GET_DFIFO(temp); + + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_GHWCFG2); + + sc->sc_dev_ep_max = DWC_OTG_MSK_GHWCFG2_NUM_DEV_EP(temp); + + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_GHWCFG4); + + sc->sc_dev_in_ep_max = DWC_OTG_MSK_GHWCFG4_NUM_IN_EPS(temp); + + DPRINTF("Total FIFO size = %d bytes, Device EPs = %d/%d\n", + sc->sc_fifo_size, sc->sc_dev_ep_max, sc->sc_dev_in_ep_max); + + /* setup FIFO */ + if (dwc_otg_init_fifo(sc)) + return (EINVAL); + + /* enable interrupts */ + sc->sc_irq_mask = DWC_OTG_MSK_GINT_ENABLED; + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GINTMSK, sc->sc_irq_mask); + + /* + * Disable all endpoint interrupts, + * we use the SOF IRQ for transmit: + */ + + /* enable all endpoint interrupts */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DIEPMSK, + /* DWC_OTG_MSK_DIEP_FIFO_EMPTY | */ + DWC_OTG_MSK_DIEP_XFER_COMPLETE); + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DOEPMSK, 0); + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DAINTMSK, 0xFFFF); + + /* enable global IRQ */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GAHBCFG, + DWC_OTG_MSK_GAHBCFG_GLOBAL_IRQ); + + /* turn off clocks */ + dwc_otg_clocks_off(sc); + + /* read initial VBUS state */ + + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_GOTGCTL); + + DPRINTFN(5, "GOTCTL=0x%08x\n", temp); + + dwc_otg_vbus_interrupt(sc, + (temp & DWC_OTG_MSK_GOTGCTL_BSESS_VALID) ? 1 : 0); + + USB_BUS_UNLOCK(&sc->sc_bus); + + /* catch any lost interrupts */ + + dwc_otg_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +dwc_otg_uninit(struct dwc_otg_softc *sc) +{ + USB_BUS_LOCK(&sc->sc_bus); + + /* set disconnect */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_DCTL, + DWC_OTG_MSK_DCTL_SOFT_DISC); + + /* turn off global IRQ */ + DWC_OTG_WRITE_4(sc, DWC_OTG_REG_GAHBCFG, 0); + + sc->sc_flags.port_powered = 0; + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + dwc_otg_pull_down(sc); + dwc_otg_clocks_off(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +dwc_otg_suspend(struct dwc_otg_softc *sc) +{ + return; +} + +static void +dwc_otg_resume(struct dwc_otg_softc *sc) +{ + return; +} + +static void +dwc_otg_do_poll(struct usb_bus *bus) +{ + struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + dwc_otg_interrupt_poll(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/*------------------------------------------------------------------------* + * at91dci bulk support + * at91dci control support + * at91dci interrupt support + *------------------------------------------------------------------------*/ +static void +dwc_otg_device_non_isoc_open(struct usb_xfer *xfer) +{ + return; +} + +static void +dwc_otg_device_non_isoc_close(struct usb_xfer *xfer) +{ + dwc_otg_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +dwc_otg_device_non_isoc_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +dwc_otg_device_non_isoc_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + dwc_otg_setup_standard_chain(xfer); + dwc_otg_start_standard_chain(xfer); +} + +struct usb_pipe_methods dwc_otg_device_non_isoc_methods = +{ + .open = dwc_otg_device_non_isoc_open, + .close = dwc_otg_device_non_isoc_close, + .enter = dwc_otg_device_non_isoc_enter, + .start = dwc_otg_device_non_isoc_start, +}; + +/*------------------------------------------------------------------------* + * at91dci full speed isochronous support + *------------------------------------------------------------------------*/ +static void +dwc_otg_device_isoc_fs_open(struct usb_xfer *xfer) +{ + return; +} + +static void +dwc_otg_device_isoc_fs_close(struct usb_xfer *xfer) +{ + dwc_otg_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +dwc_otg_device_isoc_fs_enter(struct usb_xfer *xfer) +{ + struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(xfer->xroot->bus); + uint32_t temp; + uint32_t nframes; + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes); + + temp = DWC_OTG_READ_4(sc, DWC_OTG_REG_DSTS); + + /* get the current frame index */ + + nframes = DWC_OTG_MSK_DSTS_GET_FNUM(temp); + + if (sc->sc_flags.status_high_speed) + nframes /= 8; + + nframes &= DWC_OTG_FRAME_MASK; + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + temp = (nframes - xfer->endpoint->isoc_next) & DWC_OTG_FRAME_MASK; + + if ((xfer->endpoint->is_synced == 0) || + (temp < xfer->nframes)) { + /* + * If there is data underflow or the pipe queue is + * empty we schedule the transfer a few frames ahead + * of the current frame position. Else two isochronous + * transfers might overlap. + */ + xfer->endpoint->isoc_next = (nframes + 3) & DWC_OTG_FRAME_MASK; + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + temp = (xfer->endpoint->isoc_next - nframes) & DWC_OTG_FRAME_MASK; + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + + xfer->nframes; + + /* compute frame number for next insertion */ + xfer->endpoint->isoc_next += xfer->nframes; + + /* setup TDs */ + dwc_otg_setup_standard_chain(xfer); +} + +static void +dwc_otg_device_isoc_fs_start(struct usb_xfer *xfer) +{ + /* start TD chain */ + dwc_otg_start_standard_chain(xfer); +} + +struct usb_pipe_methods dwc_otg_device_isoc_fs_methods = +{ + .open = dwc_otg_device_isoc_fs_open, + .close = dwc_otg_device_isoc_fs_close, + .enter = dwc_otg_device_isoc_fs_enter, + .start = dwc_otg_device_isoc_fs_start, +}; + +/*------------------------------------------------------------------------* + * at91dci root control support + *------------------------------------------------------------------------* + * Simulate a hardware HUB by handling all the necessary requests. + *------------------------------------------------------------------------*/ + +static const struct usb_device_descriptor dwc_otg_devd = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = UDESC_DEVICE, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_HSHUBSTT, + .bMaxPacketSize = 64, + .bcdDevice = {0x00, 0x01}, + .iManufacturer = 1, + .iProduct = 2, + .bNumConfigurations = 1, +}; + +static const struct dwc_otg_config_desc dwc_otg_confd = { + .confd = { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(dwc_otg_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0, + }, + .ifcd = { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = 0, + }, + .endpd = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = (UE_DIR_IN | DWC_OTG_INTR_ENDPT), + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, + .bInterval = 255, + }, +}; + +static const struct usb_hub_descriptor_min dwc_otg_hubd = { + .bDescLength = sizeof(dwc_otg_hubd), + .bDescriptorType = UDESC_HUB, + .bNbrPorts = 1, + .wHubCharacteristics[0] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF, + .wHubCharacteristics[1] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 8, + .bPwrOn2PwrGood = 50, + .bHubContrCurrent = 0, + .DeviceRemovable = {0}, /* port is removable */ +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_VENDOR \ + 'D', 0, 'W', 0, 'C', 0, 'O', 0, 'T', 0, 'G', 0 + +#define STRING_PRODUCT \ + 'D', 0, 'C', 0, 'I', 0, ' ', 0, 'R', 0, \ + 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \ + 'U', 0, 'B', 0, + +USB_MAKE_STRING_DESC(STRING_LANG, dwc_otg_langtab); +USB_MAKE_STRING_DESC(STRING_VENDOR, dwc_otg_vendor); +USB_MAKE_STRING_DESC(STRING_PRODUCT, dwc_otg_product); + +static usb_error_t +dwc_otg_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(udev->bus); + const void *ptr; + uint16_t len; + uint16_t value; + uint16_t index; + usb_error_t err; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* buffer reset */ + ptr = (const void *)&sc->sc_hub_temp; + len = 0; + err = 0; + + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + /* demultiplex the control request */ + + switch (req->bmRequestType) { + case UT_READ_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (req->bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_DESCRIPTOR: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SYNCH_FRAME: + goto tr_valid; /* nop */ + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (req->bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_INTERFACE: + switch (req->bRequest) { + case UR_SET_INTERFACE: + goto tr_handle_set_interface; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (req->bRequest) { + case UR_GET_INTERFACE: + goto tr_handle_get_interface; + case UR_GET_STATUS: + goto tr_handle_get_iface_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_INTERFACE: + case UT_WRITE_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_READ_CLASS_INTERFACE: + case UT_READ_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_WRITE_CLASS_DEVICE: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_valid; + case UR_SET_DESCRIPTOR: + case UR_SET_FEATURE: + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_OTHER: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_handle_clear_port_feature; + case UR_SET_FEATURE: + goto tr_handle_set_port_feature; + case UR_CLEAR_TT_BUFFER: + case UR_RESET_TT: + case UR_STOP_TT: + goto tr_valid; + + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_OTHER: + switch (req->bRequest) { + case UR_GET_TT_STATE: + goto tr_handle_get_tt_state; + case UR_GET_STATUS: + goto tr_handle_get_port_status; + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_class_descriptor; + case UR_GET_STATUS: + goto tr_handle_get_class_status; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + goto tr_valid; + +tr_handle_get_descriptor: + switch (value >> 8) { + case UDESC_DEVICE: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(dwc_otg_devd); + ptr = (const void *)&dwc_otg_devd; + goto tr_valid; + case UDESC_CONFIG: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(dwc_otg_confd); + ptr = (const void *)&dwc_otg_confd; + goto tr_valid; + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + len = sizeof(dwc_otg_langtab); + ptr = (const void *)&dwc_otg_langtab; + goto tr_valid; + + case 1: /* Vendor */ + len = sizeof(dwc_otg_vendor); + ptr = (const void *)&dwc_otg_vendor; + goto tr_valid; + + case 2: /* Product */ + len = sizeof(dwc_otg_product); + ptr = (const void *)&dwc_otg_product; + goto tr_valid; + default: + break; + } + break; + default: + goto tr_stalled; + } + goto tr_stalled; + +tr_handle_get_config: + len = 1; + sc->sc_hub_temp.wValue[0] = sc->sc_conf; + goto tr_valid; + +tr_handle_get_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); + goto tr_valid; + +tr_handle_set_address: + if (value & 0xFF00) { + goto tr_stalled; + } + sc->sc_rt_addr = value; + goto tr_valid; + +tr_handle_set_config: + if (value >= 2) { + goto tr_stalled; + } + sc->sc_conf = value; + goto tr_valid; + +tr_handle_get_interface: + len = 1; + sc->sc_hub_temp.wValue[0] = 0; + goto tr_valid; + +tr_handle_get_tt_state: +tr_handle_get_class_status: +tr_handle_get_iface_status: +tr_handle_get_ep_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, 0); + goto tr_valid; + +tr_handle_set_halt: +tr_handle_set_interface: +tr_handle_set_wakeup: +tr_handle_clear_wakeup: +tr_handle_clear_halt: + goto tr_valid; + +tr_handle_clear_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index); + + switch (value) { + case UHF_PORT_SUSPEND: + dwc_otg_wakeup_peer(sc); + break; + + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 0; + break; + + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 0; + dwc_otg_pull_down(sc); + dwc_otg_clocks_off(sc); + break; + case UHF_C_PORT_CONNECTION: + /* clear connect change flag */ + sc->sc_flags.change_connect = 0; + + if (!sc->sc_flags.status_bus_reset) { + /* we are not connected */ + break; + } + break; + case UHF_C_PORT_SUSPEND: + sc->sc_flags.change_suspend = 0; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_set_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_SET_PORT_FEATURE\n"); + + switch (value) { + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 1; + break; + case UHF_PORT_SUSPEND: + case UHF_PORT_RESET: + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 1; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_get_port_status: + + DPRINTFN(9, "UR_GET_PORT_STATUS\n"); + + if (index != 1) { + goto tr_stalled; + } + if (sc->sc_flags.status_vbus) { + dwc_otg_clocks_on(sc); + } else { + dwc_otg_clocks_off(sc); + } + + /* Select Device Side Mode */ + + value = UPS_PORT_MODE_DEVICE; + + if (sc->sc_flags.status_high_speed) { + value |= UPS_HIGH_SPEED; + } + if (sc->sc_flags.port_powered) { + value |= UPS_PORT_POWER; + } + if (sc->sc_flags.port_enabled) { + value |= UPS_PORT_ENABLED; + } + if (sc->sc_flags.status_vbus && + sc->sc_flags.status_bus_reset) { + value |= UPS_CURRENT_CONNECT_STATUS; + } + if (sc->sc_flags.status_suspend) { + value |= UPS_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortStatus, value); + + value = 0; + + if (sc->sc_flags.change_connect) { + value |= UPS_C_CONNECT_STATUS; + } + if (sc->sc_flags.change_suspend) { + value |= UPS_C_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortChange, value); + len = sizeof(sc->sc_hub_temp.ps); + goto tr_valid; + +tr_handle_get_class_descriptor: + if (value & 0xFF) { + goto tr_stalled; + } + ptr = (const void *)&dwc_otg_hubd; + len = sizeof(dwc_otg_hubd); + goto tr_valid; + +tr_stalled: + err = USB_ERR_STALLED; +tr_valid: +done: + *plength = len; + *pptr = ptr; + return (err); +} + +static void +dwc_otg_xfer_setup(struct usb_setup_params *parm) +{ + const struct usb_hw_ep_profile *pf; + struct usb_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t n; + uint8_t ep_no; + + xfer = parm->curr_xfer; + + /* + * NOTE: This driver does not use any of the parameters that + * are computed from the following values. Just set some + * reasonable dummies: + */ + parm->hc_max_packet_size = 0x500; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x500; + + usbd_transfer_setup_sub(parm); + + /* + * compute maximum number of TDs + */ + if ((xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) { + + ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */ + + 1 /* SYNC 2 */ ; + } else { + + ntd = xfer->nframes + 1 /* SYNC */ ; + } + + /* + * check if "usbd_transfer_setup_sub" set an error + */ + if (parm->err) + return; + + /* + * allocate transfer descriptors + */ + last_obj = NULL; + + /* + * get profile stuff + */ + ep_no = xfer->endpointno & UE_ADDR; + dwc_otg_get_hw_ep_profile(parm->udev, &pf, ep_no); + + if (pf == NULL) { + /* should not happen */ + parm->err = USB_ERR_INVAL; + return; + } + + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + for (n = 0; n != ntd; n++) { + + struct dwc_otg_td *td; + + if (parm->buf) { + + td = USB_ADD_BYTES(parm->buf, parm->size[0]); + + /* init TD */ + td->max_packet_size = xfer->max_packet_size; + td->ep_no = ep_no; + td->obj_next = last_obj; + + last_obj = td; + } + parm->size[0] += sizeof(*td); + } + + xfer->td_start[0] = last_obj; +} + +static void +dwc_otg_xfer_unsetup(struct usb_xfer *xfer) +{ + return; +} + +static void +dwc_otg_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *ep) +{ + struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(udev->bus); + + DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d,%d)\n", + ep, udev->address, + edesc->bEndpointAddress, udev->flags.usb_mode, + sc->sc_rt_addr, udev->device_index); + + if (udev->device_index != sc->sc_rt_addr) { + + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + if (udev->speed != USB_SPEED_FULL && + udev->speed != USB_SPEED_HIGH) { + /* not supported */ + return; + } + if ((edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS) + ep->methods = &dwc_otg_device_isoc_fs_methods; + else + ep->methods = &dwc_otg_device_non_isoc_methods; + } +} + +static void +dwc_otg_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) +{ + struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(bus); + + switch (state) { + case USB_HW_POWER_SUSPEND: + dwc_otg_suspend(sc); + break; + case USB_HW_POWER_SHUTDOWN: + dwc_otg_uninit(sc); + break; + case USB_HW_POWER_RESUME: + dwc_otg_resume(sc); + break; + default: + break; + } +} + +struct usb_bus_methods dwc_otg_bus_methods = +{ + .endpoint_init = &dwc_otg_ep_init, + .xfer_setup = &dwc_otg_xfer_setup, + .xfer_unsetup = &dwc_otg_xfer_unsetup, + .get_hw_ep_profile = &dwc_otg_get_hw_ep_profile, + .set_stall = &dwc_otg_set_stall, + .clear_stall = &dwc_otg_clear_stall, + .roothub_exec = &dwc_otg_roothub_exec, + .xfer_poll = &dwc_otg_do_poll, + .device_state_change = &dwc_otg_device_state_change, + .set_hw_power_sleep = &dwc_otg_set_hw_power_sleep, +}; diff --git a/sys/bus/u4b/controller/dwc_otg.h b/sys/bus/u4b/controller/dwc_otg.h new file mode 100644 index 0000000000..cf0ad2f390 --- /dev/null +++ b/sys/bus/u4b/controller/dwc_otg.h @@ -0,0 +1,437 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2012 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _DWC_OTG_H_ +#define _DWC_OTG_H_ + +#define DWC_OTG_MAX_DEVICES (USB_MIN_DEVICES + 1) +#define DWC_OTG_FRAME_MASK 0x7FF +#define DWC_OTG_MAX_TXP 4 +#define DWC_OTG_MAX_TXN (0x200 * DWC_OTG_MAX_TXP) + +/* Global CSR registers */ + +#define DWC_OTG_REG_GOTGCTL 0x0000 +#define DWC_OTG_MSK_GOTGCTL_CHIRP_ON (1U << 27) +#define DWC_OTG_MSK_GOTGCTL_BSESS_VALID (1U << 19) +#define DWC_OTG_MSK_GOTGCTL_ASESS_VALID (1U << 18) +#define DWC_OTG_MSK_GOTGCTL_CONN_ID_STATUS (1U << 16) +#define DWC_OTG_MSK_GOTGCTL_SESS_REQ (1U << 1) +#define DWC_OTG_MSK_GOTGCTL_SESS_VALID (1U << 0) + +#define DWC_OTG_REG_GOTGINT 0x0004 +#define DWC_OTG_REG_GAHBCFG 0x0008 +#define DWC_OTG_MSK_GAHBCFG_GLOBAL_IRQ (1U << 0) + +#define DWC_OTG_REG_GUSBCFG 0x000C +#define DWC_OTG_MSK_GUSBCFG_FORCE_DEVICE (1U << 30) +#define DWC_OTG_MSK_GUSBCFG_FORCE_HOST (1U << 29) +#define DWC_OTG_MSK_GUSBCFG_NO_PULLUP (1U << 27) +#define DWC_OTG_MSK_GUSBCFG_NO_PULLUP (1U << 27) +#define DWC_OTG_MSK_GUSBCFG_IC_USB_CAP (1U << 26) +#define DWC_OTG_MSK_GUSBCFG_ULPI_FS_LS (1U << 17) +#define DWC_OTG_MSK_GUSBCFG_TRD_TIM(x) (((x) & 15U) << 10) +#define DWC_OTG_MSK_GUSBCFG_HRP (1U << 9) +#define DWC_OTG_MSK_GUSBCFG_SRP (1U << 8) +#define DWC_OTG_MSK_GUSBCFG_HS_PHY (1U << 6) +#define DWC_OTG_MSK_GUSBCFG_FS_INTF (1U << 5) +#define DWC_OTG_MSK_GUSBCFG_ULPI_UMTI_SEL (1U << 4) +#define DWC_OTG_MSK_GUSBCFG_PHY_INTF (1U << 3) + +#define DWC_OTG_REG_GRSTCTL 0x0010 +#define DWC_OTG_MSK_GRSTCTL_TXFIFO(n) (((n) & 31U) << 6) +#define DWC_OTG_MSK_GRSTCTL_TXFFLUSH (1U << 5) +#define DWC_OTG_MSK_GRSTCTL_RXFFLUSH (1U << 4) +#define DWC_OTG_MSK_GRSTCTL_FRMCNTRRST (1U << 2) +#define DWC_OTG_MSK_GRSTCTL_CSFTRST (1U << 0) + +#define DWC_OTG_REG_GINTSTS 0x0014 +#define DWC_OTG_REG_GINTMSK 0x0018 +#define DWC_OTG_MSK_GINT_WKUPINT (1U << 31) +#define DWC_OTG_MSK_GINT_SESSREQINT (1U << 30) +#define DWC_OTG_MSK_GINT_DISCONNINT (1U << 29) +#define DWC_OTG_MSK_GINT_CONNIDSTSCHNG (1U << 28) +#define DWC_OTG_MSK_GINT_LPM (1U << 27) +#define DWC_OTG_MSK_GINT_PTXFEMP (1U << 26) +#define DWC_OTG_MSK_GINT_HCHINT (1U << 25) +#define DWC_OTG_MSK_GINT_PRTINT (1U << 24) +#define DWC_OTG_MSK_GINT_RESETDET (1U << 23) +#define DWC_OTG_MSK_GINT_FETSUSP (1U << 22) +#define DWC_OTG_MSK_GINT_INCOMPL_P (1U << 21) +#define DWC_OTG_MSK_GINT_INCOMPL_ISO_IN (1U << 20) +#define DWC_OTG_MSK_GINT_OUTEP (1U << 19) +#define DWC_OTG_MSK_GINT_INEP (1U << 18) +#define DWC_OTG_MSK_GINT_EP_MISMATCH (1U << 17) +#define DWC_OTG_MSK_GINT_RESTORE_DONE (1U << 16) +#define DWC_OTG_MSK_GINT_EOP_FRAME (1U << 15) +#define DWC_OTG_MSK_GINT_ISO_OUT_DROP (1U << 14) +#define DWC_OTG_MSK_GINT_ENUM_DONE (1U << 13) +#define DWC_OTG_MSK_GINT_USB_RESET (1U << 12) +#define DWC_OTG_MSK_GINT_USB_SUSPEND (1U << 11) +#define DWC_OTG_MSK_GINT_EARLY_SUSPEND (1U << 10) +#define DWC_OTG_MSK_GINT_I2C_INT (1U << 9) +#define DWC_OTG_MSK_GINT_ULPI_CARKIT (1U << 8) +#define DWC_OTG_MSK_GINT_GLOBAL_OUT_NAK (1U << 7) +#define DWC_OTG_MSK_GINT_GLOBAL_IN_NAK (1U << 6) +#define DWC_OTG_MSK_GINT_NPTXFEMP (1U << 5) +#define DWC_OTG_MSK_GINT_RXFLVL (1U << 4) +#define DWC_OTG_MSK_GINT_SOF (1U << 3) +#define DWC_OTG_MSK_GINT_OTG (1U << 2) +#define DWC_OTG_MSK_GINT_MODE_MISMATCH (1U << 1) +#define DWC_OTG_MSK_GINT_CUR_MODE (1U << 0) + +#define DWC_OTG_REG_GRXSTSR 0x001C +#define DWC_OTG_REG_GRXSTSP 0x0020 +#define DWC_OTG_MSK_GRXSTS_PACKET_STS (15U << 17) +#define DWC_OTG_MSK_GRXSTS_HST_IN_DATA (2U << 17) +#define DWC_OTG_MSK_GRXSTS_HST_IN_COMPLETE (3U << 17) +#define DWC_OTG_MSK_GRXSTS_HST_DT_ERROR (5U << 17) +#define DWC_OTG_MSK_GRXSTS_HST_HALTED (7U << 17) +#define DWC_OTG_MSK_GRXSTS_DEV_GLOB_OUT_NAK (1U << 17) +#define DWC_OTG_MSK_GRXSTS_DEV_OUT_DATA (2U << 17) +#define DWC_OTG_MSK_GRXSTS_DEV_OUT_COMPLETE (3U << 17) +#define DWC_OTG_MSK_GRXSTS_DEV_STP_COMPLETE (4U << 17) +#define DWC_OTG_MSK_GRXSTS_DEV_STP_DATA (6U << 17) +#define DWC_OTG_MSK_GRXSTS_PID (3U << 15) +#define DWC_OTG_MSK_GRXSTS_PID_DATA0 (0U << 15) +#define DWC_OTG_MSK_GRXSTS_PID_DATA1 (2U << 15) +#define DWC_OTG_MSK_GRXSTS_PID_DATA2 (1U << 15) +#define DWC_OTG_MSK_GRXSTS_PID_MDATA (3U << 15) +#define DWC_OTG_MSK_GRXSTS_GET_BYTE_CNT(x) (((x) >> 4) & 0x7FFU) +#define DWC_OTG_MSK_GRXSTS_GET_CHANNEL(x) ((x) & 15U) +#define DWC_OTG_MSK_GRXSTS_GET_FRNUM (x) (((x) >> 21) & 15U) + +#define DWC_OTG_REG_GRXFSIZ 0x0024 +#define DWC_OTG_REG_GNPTXFSIZ 0x0028 +#define DWC_OTG_REG_GNPTXSTS 0x002C +#define DWC_OTG_REG_GI2CCTL 0x0030 +#define DWC_OTG_REG_GPVNDCTL 0x0034 +#define DWC_OTG_REG_GGPIO 0x0038 +#define DWC_OTG_REG_GUID 0x003C +#define DWC_OTG_REG_GSNPSID 0x0040 +#define DWC_OTG_REG_GHWCFG1 0x0044 +#define DWC_OTG_MSK_GHWCFG1_GET_DIR(x, n) (((x) >> (2 * (n))) & 3U) +#define DWC_OTG_MSK_GHWCFG1_BIDIR (0U) +#define DWC_OTG_MSK_GHWCFG1_IN (1U) +#define DWC_OTG_MSK_GHWCFG1_OUT (2U) +#define DWC_OTG_REG_GHWCFG2 0x0048 +#define DWC_OTG_MSK_GHWCFG2_NUM_DEV_EP(x) ((((x) >> 10) & 15) + 1) +#define DWC_OTG_MSK_GHWCFG2_NUM_HOST_EP(x) ((((x) >> 14) & 15) + 1) +#define DWC_OTG_REG_GHWCFG3 0x004C +#define DWC_OTG_MSK_GHWCFG3_GET_DFIFO(x) ((x) >> 16) +#define DWC_OTG_MSK_GHWCFG3_PKT_SIZE (0x10U << (((x) >> 4) & 7)) +#define DWC_OTG_MSK_GHWCFG3_XFR_SIZE (0x400U << (((x) >> 0) & 15)) + +#define DWC_OTG_REG_GHWCFG4 0x0050 +#define DWC_OTG_MSK_GHWCFG4_NUM_IN_EPS(x) ((((x) >> 26) & 15U) + 1U) +#define DWC_OTG_MSK_GHWCFG4_NUM_CTRL_EPS(x) (((x) >> 16) & 15U) +#define DWC_OTG_MSK_GHWCFG4_NUM_IN_PERIODIC_EPS(x) (((x) >> 0) & 15U) + +#define DWC_OTG_REG_GLPMCFG 0x0054 +#define DWC_OTG_MSK_GLPMCFG_HSIC_CONN (1U << 30) +#define DWC_OTG_REG_GPWRDN 0x0058 +#define DWC_OTG_MSK_GPWRDN_BVALID (1U << 22) +#define DWC_OTG_MSK_GPWRDN_IDDIG (1U << 21) +#define DWC_OTG_MSK_GPWRDN_CONNDET_INT (1U << 14) +#define DWC_OTG_MSK_GPWRDN_CONNDET (1U << 13) +#define DWC_OTG_MSK_GPWRDN_DISCONN_INT (1U << 12) +#define DWC_OTG_MSK_GPWRDN_DISCONN (1U << 11) +#define DWC_OTG_MSK_GPWRDN_RESETDET_INT (1U << 10) +#define DWC_OTG_MSK_GPWRDN_RESETDET (1U << 9) +#define DWC_OTG_MSK_GPWRDN_LINESTATE_INT (1U << 8) +#define DWC_OTG_MSK_GPWRDN_LINESTATE (1U << 7) +#define DWC_OTG_MSK_GPWRDN_DISABLE_VBUS (1U << 6) +#define DWC_OTG_MSK_GPWRDN_POWER_DOWN (1U << 5) +#define DWC_OTG_MSK_GPWRDN_POWER_DOWN_RST (1U << 4) +#define DWC_OTG_MSK_GPWRDN_POWER_DOWN_CLAMP (1U << 3) +#define DWC_OTG_MSK_GPWRDN_RESTORE (1U << 2) +#define DWC_OTG_MSK_GPWRDN_PMU_ACTIVE (1U << 1) +#define DWC_OTG_MSK_GPWRDN_PMU_IRQ_SEL (1U << 0) + +#define DWC_OTG_REG_GDFIFOCFG 0x005C +#define DWC_OTG_REG_GADPCTL 0x0060 +#define DWC_OTG_REG_HPTXFSIZ 0x0100 +#define DWC_OTG_REG_DPTXFSIZ(n) (0x0100 + (4*(n))) +#define DWC_OTG_REG_DIEPTXF(n) (0x0100 + (4*(n))) + +/* Host Mode CSR registers */ + +#define DWC_OTG_REG_HCFG 0x0400 +#define DWC_OTG_REG_HFIR 0x0404 +#define DWC_OTG_REG_HFNUM 0x0408 +#define DWC_OTG_REG_HPTXSTS 0x0410 +#define DWC_OTG_REG_HAINT 0x0414 +#define DWC_OTG_REG_HAINTMSK 0x0418 +#define DWC_OTG_REG_HPRT 0x0440 +#define DWC_OTG_REG_HCCHAR(n) (0x0500 + (32*(n))) +#define DWC_OTG_REG_HCSPLT(n) (0x0504 + (32*(n))) +#define DWC_OTG_REG_HCINT(n) (0x0508 + (32*(n))) +#define DWC_OTG_REG_HCINTMSK(n) (0x050C + (32*(n))) +#define DWC_OTG_REG_HCTSIZ(n) (0x0510 + (32*(n))) +#define DWC_OTG_REG_HCDMA(n) (0x0514 + (32*(n))) +#define DWC_OTG_REG_HCDMAB(n) (0x051C + (32*(n))) + +/* Device Mode CSR registers */ + +#define DWC_OTG_REG_DCFG 0x0800 +#define DWC_OTG_MSK_DCFG_SET_DEV_ADDR(x) (((x) & 0x7FU) << 4) +#define DWC_OTG_MSK_DCFG_SET_DEV_SPEED(x) ((x) & 0x3U) +#define DWC_OTG_MSK_DCFG_DEV_SPEED_HI (0U) +#define DWC_OTG_MSK_DCFG_DEV_SPEED_FULL20 (1U) +#define DWC_OTG_MSK_DCFG_DEV_SPEED_FULL10 (3U) + +#define DWC_OTG_REG_DCTL 0x0804 +#define DWC_OTG_MSK_DCTL_PWRON_PROG_DONE (1U << 11) +#define DWC_OTG_MSK_DCTL_CGOUT_NAK (1U << 10) +#define DWC_OTG_MSK_DCTL_CGNPIN_NAK (1U << 8) +#define DWC_OTG_MSK_DCTL_SOFT_DISC (1U << 1) +#define DWC_OTG_MSK_DCTL_REMOTE_WAKEUP (1U << 0) + +#define DWC_OTG_REG_DSTS 0x0808 +#define DWC_OTG_MSK_DSTS_GET_FNUM(x) (((x) >> 8) & 0x3FFF) +#define DWC_OTG_MSK_DSTS_GET_ENUM_SPEED(x) (((x) >> 1) & 3U) +#define DWC_OTG_MSK_DSTS_ENUM_SPEED_HI (0U) +#define DWC_OTG_MSK_DSTS_ENUM_SPEED_FULL20 (1U) +#define DWC_OTG_MSK_DSTS_ENUM_SPEED_LOW10 (2U) +#define DWC_OTG_MSK_DSTS_ENUM_SPEED_FULL10 (3U) +#define DWC_OTG_MSK_DSTS_SUSPEND (1U << 0) + +#define DWC_OTG_REG_DIEPMSK 0x0810 +#define DWC_OTG_MSK_DIEP_FIFO_EMPTY (1U << 4) +#define DWC_OTG_MSK_DIEP_XFER_COMPLETE (1U << 0) + +#define DWC_OTG_REG_DOEPMSK 0x0814 +#define DWC_OTG_MSK_DOEP_FIFO_EMPTY (1U << 4) +#define DWC_OTG_MSK_DOEP_XFER_COMPLETE (1U << 0) + +#define DWC_OTG_REG_DAINT 0x0818 +#define DWC_OTG_REG_DAINTMSK 0x081C + +#define DWC_OTG_MSK_ENDPOINT(x,in) \ + ((in) ? (1U << ((x) & 15U)) : \ + (0x10000U << ((x) & 15U))) + +#define DWC_OTG_REG_DTKNQR1 0x0820 +#define DWC_OTG_REG_DTKNQR2 0x0824 +#define DWC_OTG_REG_DTKNQR3 0x0830 +#define DWC_OTG_REG_DTKNQR4 0x0834 +#define DWC_OTG_REG_DVBUSDIS 0x0828 +#define DWC_OTG_REG_DVBUSPULSE 0x082C +#define DWC_OTG_REG_DTHRCTL 0x0830 +#define DWC_OTG_REG_DIEPEMPMSK 0x0834 +#define DWC_OTG_REG_DEACHINT 0x0838 +#define DWC_OTG_REG_DEACHINTMSK 0x083C +#define DWC_OTG_REG_DIEPEACHMSK(n) (0x0840 + (4*(n))) +#define DWC_OTG_MSK_DIEPEACH_XFER_COMPLETE (1U << 0) + +#define DWC_OTG_REG_DOEPEACHMSK(n) (0x0880 + (4*(n))) +#define DWC_OTG_MSK_DOEPEACH_SETUP (1U << 3) +#define DWC_OTG_MSK_DOEPEACH_XFER_COMPLETE (1U << 0) + +#define DWC_OTG_REG_DIEPCTL(n) (0x0900 + (32*(n))) +#define DWC_OTG_MSK_DIEPCTL_ENABLE (1U << 31) +#define DWC_OTG_MSK_DIEPCTL_DISABLE (1U << 30) +#define DWC_OTG_MSK_DIEPCTL_SET_DATA1 (1U << 29) /* non-control */ +#define DWC_OTG_MSK_DIEPCTL_SET_DATA0 (1U << 28) /* non-control */ +#define DWC_OTG_MSK_DIEPCTL_SET_NAK (1U << 27) +#define DWC_OTG_MSK_DIEPCTL_CLR_NAK (1U << 26) +#define DWC_OTG_MSK_DIEPCTL_FNUM(n) (((n) & 15U) << 22) +#define DWC_OTG_MSK_DIEPCTL_STALL (1U << 21) +#define DWC_OTG_MSK_EP_SET_TYPE(n) (((n) & 3) << 18) +#define DWC_OTG_MSK_EP_TYPE_CONTROL (0U) +#define DWC_OTG_MSK_EP_TYPE_ISOC (1U) +#define DWC_OTG_MSK_EP_TYPE_BULK (2U) +#define DWC_OTG_MSK_EP_TYPE_INTERRUPT (3U) +#define DWC_OTG_MSK_DIEPCTL_USB_AEP (1U << 15) +#define DWC_OTG_MSK_DIEPCTL_MPS_64 (0U << 0) /* control-only */ +#define DWC_OTG_MSK_DIEPCTL_MPS_32 (1U << 0) /* control-only */ +#define DWC_OTG_MSK_DIEPCTL_MPS_16 (2U << 0) /* control-only */ +#define DWC_OTG_MSK_DIEPCTL_MPS_8 (3U << 0) /* control-only */ +#define DWC_OTG_MSK_DIEPCTL_MPS(n) ((n) & 0x7FF) /* non-control */ + +#define DWC_OTG_REG_DIEPINT(n) (0x0908 + (32*(n))) +#define DWC_OTG_MSK_DXEPINT_TXFEMP (1U << 7) +#define DWC_OTG_MSK_DXEPINT_SETUP (1U << 3) +#define DWC_OTG_MSK_DXEPINT_XFER_COMPL (1U << 0) + +#define DWC_OTG_REG_DIEPTSIZ(n) (0x0910 + (32*(n))) +#define DWC_OTG_MSK_DXEPTSIZ_SET_MULTI(n) (((n) & 3) << 29) +#define DWC_OTG_MSK_DXEPTSIZ_SET_NPKT(n) (((n) & 0x3FF) << 19) +#define DWC_OTG_MSK_DXEPTSIZ_GET_NPKT(n) (((n) >> 19) & 0x3FF) +#define DWC_OTG_MSK_DXEPTSIZ_SET_NBYTES(n) (((n) & 0x7FFFFF) << 0) +#define DWC_OTG_MSK_DXEPTSIZ_GET_NBYTES(n) (((n) >> 0) & 0x7FFFFF) + +#define DWC_OTG_REG_DIEPDMA(n) (0x0914 + (32*(n))) +#define DWC_OTG_REG_DTXFSTS(n) (0x0918 + (32*(n))) +#define DWC_OTG_REG_DIEPDMAB0 (0x091C + (32*(n))) + +#define DWC_OTG_REG_DOEPCTL(n) (0x0B00 + (32*(n))) +#define DWC_OTG_MSK_DOEPCTL_ENABLE (1U << 31) +#define DWC_OTG_MSK_DOEPCTL_DISABLE (1U << 30) +#define DWC_OTG_MSK_DOEPCTL_SET_DATA1 (1U << 29) /* non-control */ +#define DWC_OTG_MSK_DOEPCTL_SET_DATA0 (1U << 28) /* non-control */ +#define DWC_OTG_MSK_DOEPCTL_SET_NAK (1U << 27) +#define DWC_OTG_MSK_DOEPCTL_CLR_NAK (1U << 26) +#define DWC_OTG_MSK_DOEPCTL_FNUM(n) (((n) & 15U) << 22) +#define DWC_OTG_MSK_DOEPCTL_STALL (1U << 21) +#define DWC_OTG_MSK_DOEPCTL_EP_TYPE(n) (((n) & 3) << 18) +#define DWC_OTG_MSK_DOEPCTL_USB_AEP (1U << 15) +#define DWC_OTG_MSK_DOEPCTL_MPS_64 (0U << 0) /* control-only */ +#define DWC_OTG_MSK_DOEPCTL_MPS_32 (1U << 0) /* control-only */ +#define DWC_OTG_MSK_DOEPCTL_MPS_16 (2U << 0) /* control-only */ +#define DWC_OTG_MSK_DOEPCTL_MPS_8 (3U << 0) /* control-only */ +#define DWC_OTG_MSK_DOEPCTL_MPS(n) ((n) & 0x7FF) /* non-control */ + +#define DWC_OTG_REG_DOEPINT(n) (0x0B08 + (32*(n))) +#define DWC_OTG_REG_DOEPTSIZ(n) (0x0B10 + (32*(n))) +#define DWC_OTG_REG_DOEPDMA(n) (0x0B14 + (32*(n))) +#define DWC_OTG_REG_DOEPDMAB(n) (0x0B1C + (32*(n))) + +/* FIFO access registers */ + +#define DWC_OTG_REG_DFIFO(n) (0x1000 + (0x1000 * (n))) + +/* Power and clock gating CSR */ + +#define DWC_OTG_REG_PCGCCTL 0x0E00 + +#define DWC_OTG_READ_4(sc, reg) \ + bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) + +#define DWC_OTG_WRITE_4(sc, reg, data) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data) + +struct dwc_otg_td; +struct dwc_otg_softc; + +typedef uint8_t (dwc_otg_cmd_t)(struct dwc_otg_td *td); + +struct dwc_otg_td { + struct dwc_otg_td *obj_next; + dwc_otg_cmd_t *func; + struct usb_page_cache *pc; + uint32_t tx_bytes; + uint32_t offset; + uint32_t remainder; + uint16_t max_packet_size; /* packet_size */ + uint16_t npkt; + uint8_t ep_no; + uint8_t error:1; + uint8_t alt_next:1; + uint8_t short_pkt:1; + uint8_t did_stall:1; +}; + +struct dwc_otg_std_temp { + dwc_otg_cmd_t *func; + struct usb_page_cache *pc; + struct dwc_otg_td *td; + struct dwc_otg_td *td_next; + uint32_t len; + uint32_t offset; + uint16_t max_frame_size; + uint8_t short_pkt; + /* + * short_pkt = 0: transfer should be short terminated + * short_pkt = 1: transfer should not be short terminated + */ + uint8_t setup_alt_next; + uint8_t did_stall; + uint8_t bulk_or_control; +}; + +struct dwc_otg_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union dwc_otg_hub_temp { + uWord wValue; + struct usb_port_status ps; +}; + +struct dwc_otg_flags { + uint8_t change_connect:1; + uint8_t change_suspend:1; + uint8_t status_suspend:1; /* set if suspended */ + uint8_t status_vbus:1; /* set if present */ + uint8_t status_bus_reset:1; /* set if reset complete */ + uint8_t status_high_speed:1; /* set if High Speed is selected */ + uint8_t remote_wakeup:1; + uint8_t self_powered:1; + uint8_t clocks_off:1; + uint8_t port_powered:1; + uint8_t port_enabled:1; + uint8_t d_pulled_up:1; +}; + +struct dwc_otg_profile { + struct usb_hw_ep_profile usb; + uint16_t max_buffer; +}; + +struct dwc_otg_softc { + struct usb_bus sc_bus; + union dwc_otg_hub_temp sc_hub_temp; + struct dwc_otg_profile sc_hw_ep_profile[16]; + + struct usb_device *sc_devices[DWC_OTG_MAX_DEVICES]; + struct resource *sc_io_res; + struct resource *sc_irq_res; + void *sc_intr_hdl; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + uint32_t sc_rx_bounce_buffer[1024 / 4]; + uint32_t sc_tx_bounce_buffer[(512 * DWC_OTG_MAX_TXP) / 4]; + + uint32_t sc_fifo_size; + uint32_t sc_irq_mask; + uint32_t sc_last_rx_status; + uint32_t sc_out_ctl[16]; + uint32_t sc_in_ctl[16]; + + uint16_t sc_active_out_ep; + + uint8_t sc_dev_ep_max; + uint8_t sc_dev_in_ep_max; + uint8_t sc_rt_addr; /* root HUB address */ + uint8_t sc_conf; /* root HUB config */ + + uint8_t sc_hub_idata[1]; + + struct dwc_otg_flags sc_flags; +}; + +/* prototypes */ + +void dwc_otg_interrupt(struct dwc_otg_softc *); +int dwc_otg_init(struct dwc_otg_softc *); +void dwc_otg_uninit(struct dwc_otg_softc *); + +#endif /* _DWC_OTG_H_ */ diff --git a/sys/bus/u4b/controller/dwc_otg_atmelarm.c b/sys/bus/u4b/controller/dwc_otg_atmelarm.c new file mode 100644 index 0000000000..bf758448e6 --- /dev/null +++ b/sys/bus/u4b/controller/dwc_otg_atmelarm.c @@ -0,0 +1,199 @@ +/*- + * Copyright (c) 2012 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +static device_probe_t dwc_otg_probe; +static device_attach_t dwc_otg_attach; +static device_detach_t dwc_otg_detach; + +struct dwc_otg_super_softc { + struct dwc_otg_softc sc_otg; /* must be first */ +}; + +static int +dwc_otg_probe(device_t dev) +{ + device_set_desc(dev, "DWC OTG 2.0 integrated USB controller"); + return (0); +} + +static int +dwc_otg_attach(device_t dev) +{ + struct dwc_otg_super_softc *sc = device_get_softc(dev); + int err; + int rid; + + /* initialise some bus fields */ + sc->sc_otg.sc_bus.parent = dev; + sc->sc_otg.sc_bus.devices = sc->sc_otg.sc_devices; + sc->sc_otg.sc_bus.devices_max = DWC_OTG_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_otg.sc_bus, + USB_GET_DMA_TAG(dev), NULL)) { + return (ENOMEM); + } + rid = 0; + sc->sc_otg.sc_io_res = + bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); + + if (!(sc->sc_otg.sc_io_res)) { + err = ENOMEM; + goto error; + } + sc->sc_otg.sc_io_tag = rman_get_bustag(sc->sc_otg.sc_io_res); + sc->sc_otg.sc_io_hdl = rman_get_bushandle(sc->sc_otg.sc_io_res); + sc->sc_otg.sc_io_size = rman_get_size(sc->sc_otg.sc_io_res); + + rid = 0; + sc->sc_otg.sc_irq_res = + bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); + if (sc->sc_otg.sc_irq_res == NULL) + goto error; + + sc->sc_otg.sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (sc->sc_otg.sc_bus.bdev == NULL) + goto error; + + device_set_ivars(sc->sc_otg.sc_bus.bdev, &sc->sc_otg.sc_bus); + + err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)dwc_otg_interrupt, sc, &sc->sc_otg.sc_intr_hdl); + if (err) { + sc->sc_otg.sc_intr_hdl = NULL; + goto error; + } + err = dwc_otg_init(&sc->sc_otg); + if (err == 0) { + err = device_probe_and_attach(sc->sc_otg.sc_bus.bdev); + } + if (err) + goto error; + return (0); + +error: + dwc_otg_detach(dev); + return (ENXIO); +} + +static int +dwc_otg_detach(device_t dev) +{ + struct dwc_otg_super_softc *sc = device_get_softc(dev); + device_t bdev; + int err; + + if (sc->sc_otg.sc_bus.bdev) { + bdev = sc->sc_otg.sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(dev); + + if (sc->sc_otg.sc_irq_res && sc->sc_otg.sc_intr_hdl) { + /* + * only call dwc_otg_uninit() after dwc_otg_init() + */ + dwc_otg_uninit(&sc->sc_otg); + + err = bus_teardown_intr(dev, sc->sc_otg.sc_irq_res, + sc->sc_otg.sc_intr_hdl); + sc->sc_otg.sc_intr_hdl = NULL; + } + /* free IRQ channel, if any */ + if (sc->sc_otg.sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 0, + sc->sc_otg.sc_irq_res); + sc->sc_otg.sc_irq_res = NULL; + } + /* free memory resource, if any */ + if (sc->sc_otg.sc_io_res) { + bus_release_resource(dev, SYS_RES_MEMORY, 0, + sc->sc_otg.sc_io_res); + sc->sc_otg.sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_otg.sc_bus, NULL); + + return (0); +} + +static device_method_t dwc_otg_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, dwc_otg_probe), + DEVMETHOD(device_attach, dwc_otg_attach), + DEVMETHOD(device_detach, dwc_otg_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD_END +}; + +static driver_t dwc_otg_driver = { + .name = "dwc_otg", + .methods = dwc_otg_methods, + .size = sizeof(struct dwc_otg_super_softc), +}; + +static devclass_t dwc_otg_devclass; + +DRIVER_MODULE(dwcotg, atmelarm, dwc_otg_driver, dwc_otg_devclass, 0, 0); +MODULE_DEPEND(dwcotg, usb, 1, 1, 1); diff --git a/sys/bus/u4b/controller/ehci.c b/sys/bus/u4b/controller/ehci.c new file mode 100644 index 0000000000..f59c801dac --- /dev/null +++ b/sys/bus/u4b/controller/ehci.c @@ -0,0 +1,3880 @@ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2004 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 2004 Lennart Augustsson. All rights reserved. + * Copyright (c) 2004 Charles M. Hannum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * USB Enhanced Host Controller Driver, a.k.a. USB 2.0 controller. + * + * The EHCI 0.96 spec can be found at + * http://developer.intel.com/technology/usb/download/ehci-r096.pdf + * The EHCI 1.0 spec can be found at + * http://developer.intel.com/technology/usb/download/ehci-r10.pdf + * and the USB 2.0 spec at + * http://www.usb.org/developers/docs/usb_20.zip + * + */ + +/* + * TODO: + * 1) command failures are not recovered correctly + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define USB_DEBUG_VAR ehcidebug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define EHCI_BUS2SC(bus) \ + ((ehci_softc_t *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((ehci_softc_t *)0)->sc_bus)))) + +#ifdef USB_DEBUG +static int ehcidebug = 0; +static int ehcinohighspeed = 0; +static int ehciiaadbug = 0; +static int ehcilostintrbug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ehci, CTLFLAG_RW, 0, "USB ehci"); +SYSCTL_INT(_hw_usb_ehci, OID_AUTO, debug, CTLFLAG_RW, + &ehcidebug, 0, "Debug level"); +SYSCTL_INT(_hw_usb_ehci, OID_AUTO, no_hs, CTLFLAG_RW, + &ehcinohighspeed, 0, "Disable High Speed USB"); +SYSCTL_INT(_hw_usb_ehci, OID_AUTO, iaadbug, CTLFLAG_RW, + &ehciiaadbug, 0, "Enable doorbell bug workaround"); +SYSCTL_INT(_hw_usb_ehci, OID_AUTO, lostintrbug, CTLFLAG_RW, + &ehcilostintrbug, 0, "Enable lost interrupt bug workaround"); + +TUNABLE_INT("hw.usb.ehci.debug", &ehcidebug); +TUNABLE_INT("hw.usb.ehci.no_hs", &ehcinohighspeed); +TUNABLE_INT("hw.usb.ehci.iaadbug", &ehciiaadbug); +TUNABLE_INT("hw.usb.ehci.lostintrbug", &ehcilostintrbug); + +static void ehci_dump_regs(ehci_softc_t *sc); +static void ehci_dump_sqh(ehci_softc_t *sc, ehci_qh_t *sqh); + +#endif + +#define EHCI_INTR_ENDPT 1 + +extern struct usb_bus_methods ehci_bus_methods; +extern struct usb_pipe_methods ehci_device_bulk_methods; +extern struct usb_pipe_methods ehci_device_ctrl_methods; +extern struct usb_pipe_methods ehci_device_intr_methods; +extern struct usb_pipe_methods ehci_device_isoc_fs_methods; +extern struct usb_pipe_methods ehci_device_isoc_hs_methods; + +static void ehci_do_poll(struct usb_bus *); +static void ehci_device_done(struct usb_xfer *, usb_error_t); +static uint8_t ehci_check_transfer(struct usb_xfer *); +static void ehci_timeout(void *); +static void ehci_poll_timeout(void *); + +static void ehci_root_intr(ehci_softc_t *sc); + +struct ehci_std_temp { + ehci_softc_t *sc; + struct usb_page_cache *pc; + ehci_qtd_t *td; + ehci_qtd_t *td_next; + uint32_t average; + uint32_t qtd_status; + uint32_t len; + uint16_t max_frame_size; + uint8_t shortpkt; + uint8_t auto_data_toggle; + uint8_t setup_alt_next; + uint8_t last_frame; +}; + +void +ehci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb) +{ + ehci_softc_t *sc = EHCI_BUS2SC(bus); + uint32_t i; + + cb(bus, &sc->sc_hw.pframes_pc, &sc->sc_hw.pframes_pg, + sizeof(uint32_t) * EHCI_FRAMELIST_COUNT, EHCI_FRAMELIST_ALIGN); + + cb(bus, &sc->sc_hw.terminate_pc, &sc->sc_hw.terminate_pg, + sizeof(struct ehci_qh_sub), EHCI_QH_ALIGN); + + cb(bus, &sc->sc_hw.async_start_pc, &sc->sc_hw.async_start_pg, + sizeof(ehci_qh_t), EHCI_QH_ALIGN); + + for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + cb(bus, sc->sc_hw.intr_start_pc + i, + sc->sc_hw.intr_start_pg + i, + sizeof(ehci_qh_t), EHCI_QH_ALIGN); + } + + for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + cb(bus, sc->sc_hw.isoc_hs_start_pc + i, + sc->sc_hw.isoc_hs_start_pg + i, + sizeof(ehci_itd_t), EHCI_ITD_ALIGN); + } + + for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + cb(bus, sc->sc_hw.isoc_fs_start_pc + i, + sc->sc_hw.isoc_fs_start_pg + i, + sizeof(ehci_sitd_t), EHCI_SITD_ALIGN); + } +} + +usb_error_t +ehci_reset(ehci_softc_t *sc) +{ + uint32_t hcr; + int i; + + EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_HCRESET); + for (i = 0; i < 100; i++) { + usb_pause_mtx(NULL, hz / 128); + hcr = EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_HCRESET; + if (!hcr) { + if (sc->sc_flags & (EHCI_SCFLG_SETMODE | EHCI_SCFLG_BIGEMMIO)) { + /* + * Force USBMODE as requested. Controllers + * may have multiple operating modes. + */ + uint32_t usbmode = EOREAD4(sc, EHCI_USBMODE); + if (sc->sc_flags & EHCI_SCFLG_SETMODE) { + usbmode = (usbmode &~ EHCI_UM_CM) | EHCI_UM_CM_HOST; + device_printf(sc->sc_bus.bdev, + "set host controller mode\n"); + } + if (sc->sc_flags & EHCI_SCFLG_BIGEMMIO) { + usbmode = (usbmode &~ EHCI_UM_ES) | EHCI_UM_ES_BE; + device_printf(sc->sc_bus.bdev, + "set big-endian mode\n"); + } + EOWRITE4(sc, EHCI_USBMODE, usbmode); + } + return (0); + } + } + device_printf(sc->sc_bus.bdev, "Reset timeout\n"); + return (USB_ERR_IOERROR); +} + +static usb_error_t +ehci_hcreset(ehci_softc_t *sc) +{ + uint32_t hcr; + int i; + + EOWRITE4(sc, EHCI_USBCMD, 0); /* Halt controller */ + for (i = 0; i < 100; i++) { + usb_pause_mtx(NULL, hz / 128); + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (hcr) + break; + } + if (!hcr) + /* + * Fall through and try reset anyway even though + * Table 2-9 in the EHCI spec says this will result + * in undefined behavior. + */ + device_printf(sc->sc_bus.bdev, "stop timeout\n"); + + return (ehci_reset(sc)); +} + +static int +ehci_init_sub(struct ehci_softc *sc) +{ + struct usb_page_search buf_res; + uint32_t cparams; + uint32_t hcr; + uint8_t i; + + cparams = EREAD4(sc, EHCI_HCCPARAMS); + + DPRINTF("cparams=0x%x\n", cparams); + + if (EHCI_HCC_64BIT(cparams)) { + DPRINTF("HCC uses 64-bit structures\n"); + + /* MUST clear segment register if 64 bit capable */ + EWRITE4(sc, EHCI_CTRLDSSEGMENT, 0); + } + + usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); + EOWRITE4(sc, EHCI_PERIODICLISTBASE, buf_res.physaddr); + + usbd_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res); + EOWRITE4(sc, EHCI_ASYNCLISTADDR, buf_res.physaddr | EHCI_LINK_QH); + + /* enable interrupts */ + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + /* turn on controller */ + EOWRITE4(sc, EHCI_USBCMD, + EHCI_CMD_ITC_1 | /* 1 microframes interrupt delay */ + (EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_FLS_M) | + EHCI_CMD_ASE | + EHCI_CMD_PSE | + EHCI_CMD_RS); + + /* Take over port ownership */ + EOWRITE4(sc, EHCI_CONFIGFLAG, EHCI_CONF_CF); + + for (i = 0; i < 100; i++) { + usb_pause_mtx(NULL, hz / 128); + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (!hcr) { + break; + } + } + if (hcr) { + device_printf(sc->sc_bus.bdev, "Run timeout\n"); + return (USB_ERR_IOERROR); + } + return (USB_ERR_NORMAL_COMPLETION); +} + +usb_error_t +ehci_init(ehci_softc_t *sc) +{ + struct usb_page_search buf_res; + uint32_t version; + uint32_t sparams; + uint16_t i; + uint16_t x; + uint16_t y; + uint16_t bit; + usb_error_t err = 0; + + DPRINTF("start\n"); + + usb_callout_init_mtx(&sc->sc_tmo_pcd, &sc->sc_bus.bus_mtx, 0); + usb_callout_init_mtx(&sc->sc_tmo_poll, &sc->sc_bus.bus_mtx, 0); + + sc->sc_offs = EHCI_CAPLENGTH(EREAD4(sc, EHCI_CAPLEN_HCIVERSION)); + +#ifdef USB_DEBUG + if (ehciiaadbug) + sc->sc_flags |= EHCI_SCFLG_IAADBUG; + if (ehcilostintrbug) + sc->sc_flags |= EHCI_SCFLG_LOSTINTRBUG; + if (ehcidebug > 2) { + ehci_dump_regs(sc); + } +#endif + + version = EHCI_HCIVERSION(EREAD4(sc, EHCI_CAPLEN_HCIVERSION)); + device_printf(sc->sc_bus.bdev, "EHCI version %x.%x\n", + version >> 8, version & 0xff); + + sparams = EREAD4(sc, EHCI_HCSPARAMS); + DPRINTF("sparams=0x%x\n", sparams); + + sc->sc_noport = EHCI_HCS_N_PORTS(sparams); + sc->sc_bus.usbrev = USB_REV_2_0; + + /* Reset the controller */ + DPRINTF("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev)); + + err = ehci_hcreset(sc); + if (err) { + device_printf(sc->sc_bus.bdev, "reset timeout\n"); + return (err); + } + /* + * use current frame-list-size selection 0: 1024*4 bytes 1: 512*4 + * bytes 2: 256*4 bytes 3: unknown + */ + if (EHCI_CMD_FLS(EOREAD4(sc, EHCI_USBCMD)) == 3) { + device_printf(sc->sc_bus.bdev, "invalid frame-list-size\n"); + return (USB_ERR_IOERROR); + } + /* set up the bus struct */ + sc->sc_bus.methods = &ehci_bus_methods; + + sc->sc_eintrs = EHCI_NORMAL_INTRS; + + if (1) { + struct ehci_qh_sub *qh; + + usbd_get_page(&sc->sc_hw.terminate_pc, 0, &buf_res); + + qh = buf_res.buffer; + + sc->sc_terminate_self = htohc32(sc, buf_res.physaddr); + + /* init terminate TD */ + qh->qtd_next = + htohc32(sc, EHCI_LINK_TERMINATE); + qh->qtd_altnext = + htohc32(sc, EHCI_LINK_TERMINATE); + qh->qtd_status = + htohc32(sc, EHCI_QTD_HALTED); + } + + for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + ehci_qh_t *qh; + + usbd_get_page(sc->sc_hw.intr_start_pc + i, 0, &buf_res); + + qh = buf_res.buffer; + + /* initialize page cache pointer */ + + qh->page_cache = sc->sc_hw.intr_start_pc + i; + + /* store a pointer to queue head */ + + sc->sc_intr_p_last[i] = qh; + + qh->qh_self = + htohc32(sc, buf_res.physaddr) | + htohc32(sc, EHCI_LINK_QH); + + qh->qh_endp = + htohc32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH)); + qh->qh_endphub = + htohc32(sc, EHCI_QH_SET_MULT(1)); + qh->qh_curqtd = 0; + + qh->qh_qtd.qtd_next = + htohc32(sc, EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_altnext = + htohc32(sc, EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_status = + htohc32(sc, EHCI_QTD_HALTED); + } + + /* + * the QHs are arranged to give poll intervals that are + * powers of 2 times 1ms + */ + bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 2; + while (bit) { + x = bit; + while (x & bit) { + ehci_qh_t *qh_x; + ehci_qh_t *qh_y; + + y = (x ^ bit) | (bit / 2); + + qh_x = sc->sc_intr_p_last[x]; + qh_y = sc->sc_intr_p_last[y]; + + /* + * the next QH has half the poll interval + */ + qh_x->qh_link = qh_y->qh_self; + + x++; + } + bit >>= 1; + } + + if (1) { + ehci_qh_t *qh; + + qh = sc->sc_intr_p_last[0]; + + /* the last (1ms) QH terminates */ + qh->qh_link = htohc32(sc, EHCI_LINK_TERMINATE); + } + for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + ehci_sitd_t *sitd; + ehci_itd_t *itd; + + usbd_get_page(sc->sc_hw.isoc_fs_start_pc + i, 0, &buf_res); + + sitd = buf_res.buffer; + + /* initialize page cache pointer */ + + sitd->page_cache = sc->sc_hw.isoc_fs_start_pc + i; + + /* store a pointer to the transfer descriptor */ + + sc->sc_isoc_fs_p_last[i] = sitd; + + /* initialize full speed isochronous */ + + sitd->sitd_self = + htohc32(sc, buf_res.physaddr) | + htohc32(sc, EHCI_LINK_SITD); + + sitd->sitd_back = + htohc32(sc, EHCI_LINK_TERMINATE); + + sitd->sitd_next = + sc->sc_intr_p_last[i | (EHCI_VIRTUAL_FRAMELIST_COUNT / 2)]->qh_self; + + + usbd_get_page(sc->sc_hw.isoc_hs_start_pc + i, 0, &buf_res); + + itd = buf_res.buffer; + + /* initialize page cache pointer */ + + itd->page_cache = sc->sc_hw.isoc_hs_start_pc + i; + + /* store a pointer to the transfer descriptor */ + + sc->sc_isoc_hs_p_last[i] = itd; + + /* initialize high speed isochronous */ + + itd->itd_self = + htohc32(sc, buf_res.physaddr) | + htohc32(sc, EHCI_LINK_ITD); + + itd->itd_next = + sitd->sitd_self; + } + + usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); + + if (1) { + uint32_t *pframes; + + pframes = buf_res.buffer; + + /* + * execution order: + * pframes -> high speed isochronous -> + * full speed isochronous -> interrupt QH's + */ + for (i = 0; i < EHCI_FRAMELIST_COUNT; i++) { + pframes[i] = sc->sc_isoc_hs_p_last + [i & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1)]->itd_self; + } + } + usbd_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res); + + if (1) { + + ehci_qh_t *qh; + + qh = buf_res.buffer; + + /* initialize page cache pointer */ + + qh->page_cache = &sc->sc_hw.async_start_pc; + + /* store a pointer to the queue head */ + + sc->sc_async_p_last = qh; + + /* init dummy QH that starts the async list */ + + qh->qh_self = + htohc32(sc, buf_res.physaddr) | + htohc32(sc, EHCI_LINK_QH); + + /* fill the QH */ + qh->qh_endp = + htohc32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) | EHCI_QH_HRECL); + qh->qh_endphub = htohc32(sc, EHCI_QH_SET_MULT(1)); + qh->qh_link = qh->qh_self; + qh->qh_curqtd = 0; + + /* fill the overlay qTD */ + qh->qh_qtd.qtd_next = htohc32(sc, EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_altnext = htohc32(sc, EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_status = htohc32(sc, EHCI_QTD_HALTED); + } + /* flush all cache into memory */ + + usb_bus_mem_flush_all(&sc->sc_bus, &ehci_iterate_hw_softc); + +#ifdef USB_DEBUG + if (ehcidebug) { + ehci_dump_sqh(sc, sc->sc_async_p_last); + } +#endif + + /* finial setup */ + err = ehci_init_sub(sc); + + if (!err) { + /* catch any lost interrupts */ + ehci_do_poll(&sc->sc_bus); + } + return (err); +} + +/* + * shut down the controller when the system is going down + */ +void +ehci_detach(ehci_softc_t *sc) +{ + USB_BUS_LOCK(&sc->sc_bus); + + usb_callout_stop(&sc->sc_tmo_pcd); + usb_callout_stop(&sc->sc_tmo_poll); + + EOWRITE4(sc, EHCI_USBINTR, 0); + USB_BUS_UNLOCK(&sc->sc_bus); + + if (ehci_hcreset(sc)) { + DPRINTF("reset failed!\n"); + } + + /* XXX let stray task complete */ + usb_pause_mtx(NULL, hz / 20); + + usb_callout_drain(&sc->sc_tmo_pcd); + usb_callout_drain(&sc->sc_tmo_poll); +} + +static void +ehci_suspend(ehci_softc_t *sc) +{ + DPRINTF("stopping the HC\n"); + + /* reset HC */ + ehci_hcreset(sc); +} + +static void +ehci_resume(ehci_softc_t *sc) +{ + /* reset HC */ + ehci_hcreset(sc); + + /* setup HC */ + ehci_init_sub(sc); + + /* catch any lost interrupts */ + ehci_do_poll(&sc->sc_bus); +} + +#ifdef USB_DEBUG +static void +ehci_dump_regs(ehci_softc_t *sc) +{ + uint32_t i; + + i = EOREAD4(sc, EHCI_USBCMD); + printf("cmd=0x%08x\n", i); + + if (i & EHCI_CMD_ITC_1) + printf(" EHCI_CMD_ITC_1\n"); + if (i & EHCI_CMD_ITC_2) + printf(" EHCI_CMD_ITC_2\n"); + if (i & EHCI_CMD_ITC_4) + printf(" EHCI_CMD_ITC_4\n"); + if (i & EHCI_CMD_ITC_8) + printf(" EHCI_CMD_ITC_8\n"); + if (i & EHCI_CMD_ITC_16) + printf(" EHCI_CMD_ITC_16\n"); + if (i & EHCI_CMD_ITC_32) + printf(" EHCI_CMD_ITC_32\n"); + if (i & EHCI_CMD_ITC_64) + printf(" EHCI_CMD_ITC_64\n"); + if (i & EHCI_CMD_ASPME) + printf(" EHCI_CMD_ASPME\n"); + if (i & EHCI_CMD_ASPMC) + printf(" EHCI_CMD_ASPMC\n"); + if (i & EHCI_CMD_LHCR) + printf(" EHCI_CMD_LHCR\n"); + if (i & EHCI_CMD_IAAD) + printf(" EHCI_CMD_IAAD\n"); + if (i & EHCI_CMD_ASE) + printf(" EHCI_CMD_ASE\n"); + if (i & EHCI_CMD_PSE) + printf(" EHCI_CMD_PSE\n"); + if (i & EHCI_CMD_FLS_M) + printf(" EHCI_CMD_FLS_M\n"); + if (i & EHCI_CMD_HCRESET) + printf(" EHCI_CMD_HCRESET\n"); + if (i & EHCI_CMD_RS) + printf(" EHCI_CMD_RS\n"); + + i = EOREAD4(sc, EHCI_USBSTS); + + printf("sts=0x%08x\n", i); + + if (i & EHCI_STS_ASS) + printf(" EHCI_STS_ASS\n"); + if (i & EHCI_STS_PSS) + printf(" EHCI_STS_PSS\n"); + if (i & EHCI_STS_REC) + printf(" EHCI_STS_REC\n"); + if (i & EHCI_STS_HCH) + printf(" EHCI_STS_HCH\n"); + if (i & EHCI_STS_IAA) + printf(" EHCI_STS_IAA\n"); + if (i & EHCI_STS_HSE) + printf(" EHCI_STS_HSE\n"); + if (i & EHCI_STS_FLR) + printf(" EHCI_STS_FLR\n"); + if (i & EHCI_STS_PCD) + printf(" EHCI_STS_PCD\n"); + if (i & EHCI_STS_ERRINT) + printf(" EHCI_STS_ERRINT\n"); + if (i & EHCI_STS_INT) + printf(" EHCI_STS_INT\n"); + + printf("ien=0x%08x\n", + EOREAD4(sc, EHCI_USBINTR)); + printf("frindex=0x%08x ctrdsegm=0x%08x periodic=0x%08x async=0x%08x\n", + EOREAD4(sc, EHCI_FRINDEX), + EOREAD4(sc, EHCI_CTRLDSSEGMENT), + EOREAD4(sc, EHCI_PERIODICLISTBASE), + EOREAD4(sc, EHCI_ASYNCLISTADDR)); + for (i = 1; i <= sc->sc_noport; i++) { + printf("port %d status=0x%08x\n", i, + EOREAD4(sc, EHCI_PORTSC(i))); + } +} + +static void +ehci_dump_link(ehci_softc_t *sc, uint32_t link, int type) +{ + link = hc32toh(sc, link); + printf("0x%08x", link); + if (link & EHCI_LINK_TERMINATE) + printf(""); + else { + printf("<"); + if (type) { + switch (EHCI_LINK_TYPE(link)) { + case EHCI_LINK_ITD: + printf("ITD"); + break; + case EHCI_LINK_QH: + printf("QH"); + break; + case EHCI_LINK_SITD: + printf("SITD"); + break; + case EHCI_LINK_FSTN: + printf("FSTN"); + break; + } + } + printf(">"); + } +} + +static void +ehci_dump_qtd(ehci_softc_t *sc, ehci_qtd_t *qtd) +{ + uint32_t s; + + printf(" next="); + ehci_dump_link(sc, qtd->qtd_next, 0); + printf(" altnext="); + ehci_dump_link(sc, qtd->qtd_altnext, 0); + printf("\n"); + s = hc32toh(sc, qtd->qtd_status); + printf(" status=0x%08x: toggle=%d bytes=0x%x ioc=%d c_page=0x%x\n", + s, EHCI_QTD_GET_TOGGLE(s), EHCI_QTD_GET_BYTES(s), + EHCI_QTD_GET_IOC(s), EHCI_QTD_GET_C_PAGE(s)); + printf(" cerr=%d pid=%d stat=%s%s%s%s%s%s%s%s\n", + EHCI_QTD_GET_CERR(s), EHCI_QTD_GET_PID(s), + (s & EHCI_QTD_ACTIVE) ? "ACTIVE" : "NOT_ACTIVE", + (s & EHCI_QTD_HALTED) ? "-HALTED" : "", + (s & EHCI_QTD_BUFERR) ? "-BUFERR" : "", + (s & EHCI_QTD_BABBLE) ? "-BABBLE" : "", + (s & EHCI_QTD_XACTERR) ? "-XACTERR" : "", + (s & EHCI_QTD_MISSEDMICRO) ? "-MISSED" : "", + (s & EHCI_QTD_SPLITXSTATE) ? "-SPLIT" : "", + (s & EHCI_QTD_PINGSTATE) ? "-PING" : ""); + + for (s = 0; s < 5; s++) { + printf(" buffer[%d]=0x%08x\n", s, + hc32toh(sc, qtd->qtd_buffer[s])); + } + for (s = 0; s < 5; s++) { + printf(" buffer_hi[%d]=0x%08x\n", s, + hc32toh(sc, qtd->qtd_buffer_hi[s])); + } +} + +static uint8_t +ehci_dump_sqtd(ehci_softc_t *sc, ehci_qtd_t *sqtd) +{ + uint8_t temp; + + usb_pc_cpu_invalidate(sqtd->page_cache); + printf("QTD(%p) at 0x%08x:\n", sqtd, hc32toh(sc, sqtd->qtd_self)); + ehci_dump_qtd(sc, sqtd); + temp = (sqtd->qtd_next & htohc32(sc, EHCI_LINK_TERMINATE)) ? 1 : 0; + return (temp); +} + +static void +ehci_dump_sqtds(ehci_softc_t *sc, ehci_qtd_t *sqtd) +{ + uint16_t i; + uint8_t stop; + + stop = 0; + for (i = 0; sqtd && (i < 20) && !stop; sqtd = sqtd->obj_next, i++) { + stop = ehci_dump_sqtd(sc, sqtd); + } + if (sqtd) { + printf("dump aborted, too many TDs\n"); + } +} + +static void +ehci_dump_sqh(ehci_softc_t *sc, ehci_qh_t *qh) +{ + uint32_t endp; + uint32_t endphub; + + usb_pc_cpu_invalidate(qh->page_cache); + printf("QH(%p) at 0x%08x:\n", qh, hc32toh(sc, qh->qh_self) & ~0x1F); + printf(" link="); + ehci_dump_link(sc, qh->qh_link, 1); + printf("\n"); + endp = hc32toh(sc, qh->qh_endp); + printf(" endp=0x%08x\n", endp); + printf(" addr=0x%02x inact=%d endpt=%d eps=%d dtc=%d hrecl=%d\n", + EHCI_QH_GET_ADDR(endp), EHCI_QH_GET_INACT(endp), + EHCI_QH_GET_ENDPT(endp), EHCI_QH_GET_EPS(endp), + EHCI_QH_GET_DTC(endp), EHCI_QH_GET_HRECL(endp)); + printf(" mpl=0x%x ctl=%d nrl=%d\n", + EHCI_QH_GET_MPL(endp), EHCI_QH_GET_CTL(endp), + EHCI_QH_GET_NRL(endp)); + endphub = hc32toh(sc, qh->qh_endphub); + printf(" endphub=0x%08x\n", endphub); + printf(" smask=0x%02x cmask=0x%02x huba=0x%02x port=%d mult=%d\n", + EHCI_QH_GET_SMASK(endphub), EHCI_QH_GET_CMASK(endphub), + EHCI_QH_GET_HUBA(endphub), EHCI_QH_GET_PORT(endphub), + EHCI_QH_GET_MULT(endphub)); + printf(" curqtd="); + ehci_dump_link(sc, qh->qh_curqtd, 0); + printf("\n"); + printf("Overlay qTD:\n"); + ehci_dump_qtd(sc, (void *)&qh->qh_qtd); +} + +static void +ehci_dump_sitd(ehci_softc_t *sc, ehci_sitd_t *sitd) +{ + usb_pc_cpu_invalidate(sitd->page_cache); + printf("SITD(%p) at 0x%08x\n", sitd, hc32toh(sc, sitd->sitd_self) & ~0x1F); + printf(" next=0x%08x\n", hc32toh(sc, sitd->sitd_next)); + printf(" portaddr=0x%08x dir=%s addr=%d endpt=0x%x port=0x%x huba=0x%x\n", + hc32toh(sc, sitd->sitd_portaddr), + (sitd->sitd_portaddr & htohc32(sc, EHCI_SITD_SET_DIR_IN)) + ? "in" : "out", + EHCI_SITD_GET_ADDR(hc32toh(sc, sitd->sitd_portaddr)), + EHCI_SITD_GET_ENDPT(hc32toh(sc, sitd->sitd_portaddr)), + EHCI_SITD_GET_PORT(hc32toh(sc, sitd->sitd_portaddr)), + EHCI_SITD_GET_HUBA(hc32toh(sc, sitd->sitd_portaddr))); + printf(" mask=0x%08x\n", hc32toh(sc, sitd->sitd_mask)); + printf(" status=0x%08x <%s> len=0x%x\n", hc32toh(sc, sitd->sitd_status), + (sitd->sitd_status & htohc32(sc, EHCI_SITD_ACTIVE)) ? "ACTIVE" : "", + EHCI_SITD_GET_LEN(hc32toh(sc, sitd->sitd_status))); + printf(" back=0x%08x, bp=0x%08x,0x%08x,0x%08x,0x%08x\n", + hc32toh(sc, sitd->sitd_back), + hc32toh(sc, sitd->sitd_bp[0]), + hc32toh(sc, sitd->sitd_bp[1]), + hc32toh(sc, sitd->sitd_bp_hi[0]), + hc32toh(sc, sitd->sitd_bp_hi[1])); +} + +static void +ehci_dump_itd(ehci_softc_t *sc, ehci_itd_t *itd) +{ + usb_pc_cpu_invalidate(itd->page_cache); + printf("ITD(%p) at 0x%08x\n", itd, hc32toh(sc, itd->itd_self) & ~0x1F); + printf(" next=0x%08x\n", hc32toh(sc, itd->itd_next)); + printf(" status[0]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[0]), + (itd->itd_status[0] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[1]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[1]), + (itd->itd_status[1] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[2]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[2]), + (itd->itd_status[2] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[3]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[3]), + (itd->itd_status[3] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[4]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[4]), + (itd->itd_status[4] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[5]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[5]), + (itd->itd_status[5] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[6]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[6]), + (itd->itd_status[6] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[7]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[7]), + (itd->itd_status[7] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" bp[0]=0x%08x\n", hc32toh(sc, itd->itd_bp[0])); + printf(" addr=0x%02x; endpt=0x%01x\n", + EHCI_ITD_GET_ADDR(hc32toh(sc, itd->itd_bp[0])), + EHCI_ITD_GET_ENDPT(hc32toh(sc, itd->itd_bp[0]))); + printf(" bp[1]=0x%08x\n", hc32toh(sc, itd->itd_bp[1])); + printf(" dir=%s; mpl=0x%02x\n", + (hc32toh(sc, itd->itd_bp[1]) & EHCI_ITD_SET_DIR_IN) ? "in" : "out", + EHCI_ITD_GET_MPL(hc32toh(sc, itd->itd_bp[1]))); + printf(" bp[2..6]=0x%08x,0x%08x,0x%08x,0x%08x,0x%08x\n", + hc32toh(sc, itd->itd_bp[2]), + hc32toh(sc, itd->itd_bp[3]), + hc32toh(sc, itd->itd_bp[4]), + hc32toh(sc, itd->itd_bp[5]), + hc32toh(sc, itd->itd_bp[6])); + printf(" bp_hi=0x%08x,0x%08x,0x%08x,0x%08x,\n" + " 0x%08x,0x%08x,0x%08x\n", + hc32toh(sc, itd->itd_bp_hi[0]), + hc32toh(sc, itd->itd_bp_hi[1]), + hc32toh(sc, itd->itd_bp_hi[2]), + hc32toh(sc, itd->itd_bp_hi[3]), + hc32toh(sc, itd->itd_bp_hi[4]), + hc32toh(sc, itd->itd_bp_hi[5]), + hc32toh(sc, itd->itd_bp_hi[6])); +} + +static void +ehci_dump_isoc(ehci_softc_t *sc) +{ + ehci_itd_t *itd; + ehci_sitd_t *sitd; + uint16_t max = 1000; + uint16_t pos; + + pos = (EOREAD4(sc, EHCI_FRINDEX) / 8) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + printf("%s: isochronous dump from frame 0x%03x:\n", + __FUNCTION__, pos); + + itd = sc->sc_isoc_hs_p_last[pos]; + sitd = sc->sc_isoc_fs_p_last[pos]; + + while (itd && max && max--) { + ehci_dump_itd(sc, itd); + itd = itd->prev; + } + + while (sitd && max && max--) { + ehci_dump_sitd(sc, sitd); + sitd = sitd->prev; + } +} + +#endif + +static void +ehci_transfer_intr_enqueue(struct usb_xfer *xfer) +{ + /* check for early completion */ + if (ehci_check_transfer(xfer)) { + return; + } + /* put transfer on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usbd_transfer_timeout_ms(xfer, &ehci_timeout, xfer->timeout); + } +} + +#define EHCI_APPEND_FS_TD(std,last) (last) = _ehci_append_fs_td(std,last) +static ehci_sitd_t * +_ehci_append_fs_td(ehci_sitd_t *std, ehci_sitd_t *last) +{ + DPRINTFN(11, "%p to %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->next = last->next; + std->sitd_next = last->sitd_next; + + std->prev = last; + + usb_pc_cpu_flush(std->page_cache); + + /* + * the last->next->prev is never followed: std->next->prev = std; + */ + last->next = std; + last->sitd_next = std->sitd_self; + + usb_pc_cpu_flush(last->page_cache); + + return (std); +} + +#define EHCI_APPEND_HS_TD(std,last) (last) = _ehci_append_hs_td(std,last) +static ehci_itd_t * +_ehci_append_hs_td(ehci_itd_t *std, ehci_itd_t *last) +{ + DPRINTFN(11, "%p to %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->next = last->next; + std->itd_next = last->itd_next; + + std->prev = last; + + usb_pc_cpu_flush(std->page_cache); + + /* + * the last->next->prev is never followed: std->next->prev = std; + */ + last->next = std; + last->itd_next = std->itd_self; + + usb_pc_cpu_flush(last->page_cache); + + return (std); +} + +#define EHCI_APPEND_QH(sqh,last) (last) = _ehci_append_qh(sqh,last) +static ehci_qh_t * +_ehci_append_qh(ehci_qh_t *sqh, ehci_qh_t *last) +{ + DPRINTFN(11, "%p to %p\n", sqh, last); + + if (sqh->prev != NULL) { + /* should not happen */ + DPRINTFN(0, "QH already linked!\n"); + return (last); + } + /* (sc->sc_bus.mtx) must be locked */ + + sqh->next = last->next; + sqh->qh_link = last->qh_link; + + sqh->prev = last; + + usb_pc_cpu_flush(sqh->page_cache); + + /* + * the last->next->prev is never followed: sqh->next->prev = sqh; + */ + + last->next = sqh; + last->qh_link = sqh->qh_self; + + usb_pc_cpu_flush(last->page_cache); + + return (sqh); +} + +#define EHCI_REMOVE_FS_TD(std,last) (last) = _ehci_remove_fs_td(std,last) +static ehci_sitd_t * +_ehci_remove_fs_td(ehci_sitd_t *std, ehci_sitd_t *last) +{ + DPRINTFN(11, "%p from %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->prev->next = std->next; + std->prev->sitd_next = std->sitd_next; + + usb_pc_cpu_flush(std->prev->page_cache); + + if (std->next) { + std->next->prev = std->prev; + usb_pc_cpu_flush(std->next->page_cache); + } + return ((last == std) ? std->prev : last); +} + +#define EHCI_REMOVE_HS_TD(std,last) (last) = _ehci_remove_hs_td(std,last) +static ehci_itd_t * +_ehci_remove_hs_td(ehci_itd_t *std, ehci_itd_t *last) +{ + DPRINTFN(11, "%p from %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->prev->next = std->next; + std->prev->itd_next = std->itd_next; + + usb_pc_cpu_flush(std->prev->page_cache); + + if (std->next) { + std->next->prev = std->prev; + usb_pc_cpu_flush(std->next->page_cache); + } + return ((last == std) ? std->prev : last); +} + +#define EHCI_REMOVE_QH(sqh,last) (last) = _ehci_remove_qh(sqh,last) +static ehci_qh_t * +_ehci_remove_qh(ehci_qh_t *sqh, ehci_qh_t *last) +{ + DPRINTFN(11, "%p from %p\n", sqh, last); + + /* (sc->sc_bus.mtx) must be locked */ + + /* only remove if not removed from a queue */ + if (sqh->prev) { + + sqh->prev->next = sqh->next; + sqh->prev->qh_link = sqh->qh_link; + + usb_pc_cpu_flush(sqh->prev->page_cache); + + if (sqh->next) { + sqh->next->prev = sqh->prev; + usb_pc_cpu_flush(sqh->next->page_cache); + } + last = ((last == sqh) ? sqh->prev : last); + + sqh->prev = 0; + + usb_pc_cpu_flush(sqh->page_cache); + } + return (last); +} + +static void +ehci_data_toggle_update(struct usb_xfer *xfer, uint16_t actlen, uint16_t xlen) +{ + uint16_t rem; + uint8_t dt; + + /* count number of full packets */ + dt = (actlen / xfer->max_packet_size) & 1; + + /* compute remainder */ + rem = actlen % xfer->max_packet_size; + + if (rem > 0) + dt ^= 1; /* short packet at the end */ + else if (actlen != xlen) + dt ^= 1; /* zero length packet at the end */ + else if (xlen == 0) + dt ^= 1; /* zero length transfer */ + + xfer->endpoint->toggle_next ^= dt; +} + +static usb_error_t +ehci_non_isoc_done_sub(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + ehci_qtd_t *td; + ehci_qtd_t *td_alt_next; + uint32_t status; + uint16_t len; + + td = xfer->td_transfer_cache; + td_alt_next = td->alt_next; + + if (xfer->aframes != xfer->nframes) { + usbd_xfer_set_frame_len(xfer, xfer->aframes, 0); + } + while (1) { + + usb_pc_cpu_invalidate(td->page_cache); + status = hc32toh(sc, td->qtd_status); + + len = EHCI_QTD_GET_BYTES(status); + + /* + * Verify the status length and + * add the length to "frlengths[]": + */ + if (len > td->len) { + /* should not happen */ + DPRINTF("Invalid status length, " + "0x%04x/0x%04x bytes\n", len, td->len); + status |= EHCI_QTD_HALTED; + } else if (xfer->aframes != xfer->nframes) { + xfer->frlengths[xfer->aframes] += td->len - len; + /* manually update data toggle */ + ehci_data_toggle_update(xfer, td->len - len, td->len); + } + + /* Check for last transfer */ + if (((void *)td) == xfer->td_transfer_last) { + td = NULL; + break; + } + /* Check for transfer error */ + if (status & EHCI_QTD_HALTED) { + /* the transfer is finished */ + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + td = td->alt_next; + } else { + /* the transfer is finished */ + td = NULL; + } + break; + } + td = td->obj_next; + + if (td->alt_next != td_alt_next) { + /* this USB frame is complete */ + break; + } + } + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + +#ifdef USB_DEBUG + if (status & EHCI_QTD_STATERRS) { + DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x" + "status=%s%s%s%s%s%s%s%s\n", + xfer->address, xfer->endpointno, xfer->aframes, + (status & EHCI_QTD_ACTIVE) ? "[ACTIVE]" : "[NOT_ACTIVE]", + (status & EHCI_QTD_HALTED) ? "[HALTED]" : "", + (status & EHCI_QTD_BUFERR) ? "[BUFERR]" : "", + (status & EHCI_QTD_BABBLE) ? "[BABBLE]" : "", + (status & EHCI_QTD_XACTERR) ? "[XACTERR]" : "", + (status & EHCI_QTD_MISSEDMICRO) ? "[MISSED]" : "", + (status & EHCI_QTD_SPLITXSTATE) ? "[SPLIT]" : "", + (status & EHCI_QTD_PINGSTATE) ? "[PING]" : ""); + } +#endif + + return ((status & EHCI_QTD_HALTED) ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); +} + +static void +ehci_non_isoc_done(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + ehci_qh_t *qh; + uint32_t status; + usb_error_t err = 0; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + +#ifdef USB_DEBUG + if (ehcidebug > 10) { + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + + ehci_dump_sqtds(sc, xfer->td_transfer_first); + } +#endif + + /* extract data toggle directly from the QH's overlay area */ + + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + usb_pc_cpu_invalidate(qh->page_cache); + + status = hc32toh(sc, qh->qh_qtd.qtd_status); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = ehci_non_isoc_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = ehci_non_isoc_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = ehci_non_isoc_done_sub(xfer); + } +done: + ehci_device_done(xfer, err); +} + +/*------------------------------------------------------------------------* + * ehci_check_transfer + * + * Return values: + * 0: USB transfer is not finished + * Else: USB transfer is finished + *------------------------------------------------------------------------*/ +static uint8_t +ehci_check_transfer(struct usb_xfer *xfer) +{ + struct usb_pipe_methods *methods = xfer->endpoint->methods; + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + + uint32_t status; + + DPRINTFN(13, "xfer=%p checking transfer\n", xfer); + + if (methods == &ehci_device_isoc_fs_methods) { + ehci_sitd_t *td; + + /* isochronous full speed transfer */ + + td = xfer->td_transfer_last; + usb_pc_cpu_invalidate(td->page_cache); + status = hc32toh(sc, td->sitd_status); + + /* also check if first is complete */ + + td = xfer->td_transfer_first; + usb_pc_cpu_invalidate(td->page_cache); + status |= hc32toh(sc, td->sitd_status); + + if (!(status & EHCI_SITD_ACTIVE)) { + ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); + goto transferred; + } + } else if (methods == &ehci_device_isoc_hs_methods) { + ehci_itd_t *td; + + /* isochronous high speed transfer */ + + /* check last transfer */ + td = xfer->td_transfer_last; + usb_pc_cpu_invalidate(td->page_cache); + status = td->itd_status[0]; + status |= td->itd_status[1]; + status |= td->itd_status[2]; + status |= td->itd_status[3]; + status |= td->itd_status[4]; + status |= td->itd_status[5]; + status |= td->itd_status[6]; + status |= td->itd_status[7]; + + /* also check first transfer */ + td = xfer->td_transfer_first; + usb_pc_cpu_invalidate(td->page_cache); + status |= td->itd_status[0]; + status |= td->itd_status[1]; + status |= td->itd_status[2]; + status |= td->itd_status[3]; + status |= td->itd_status[4]; + status |= td->itd_status[5]; + status |= td->itd_status[6]; + status |= td->itd_status[7]; + + /* if no transactions are active we continue */ + if (!(status & htohc32(sc, EHCI_ITD_ACTIVE))) { + ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); + goto transferred; + } + } else { + ehci_qtd_t *td; + ehci_qh_t *qh; + + /* non-isochronous transfer */ + + /* + * check whether there is an error somewhere in the middle, + * or whether there was a short packet (SPD and not ACTIVE) + */ + td = xfer->td_transfer_cache; + + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + usb_pc_cpu_invalidate(qh->page_cache); + + status = hc32toh(sc, qh->qh_qtd.qtd_status); + if (status & EHCI_QTD_ACTIVE) { + /* transfer is pending */ + goto done; + } + + while (1) { + usb_pc_cpu_invalidate(td->page_cache); + status = hc32toh(sc, td->qtd_status); + + /* + * Check if there is an active TD which + * indicates that the transfer isn't done. + */ + if (status & EHCI_QTD_ACTIVE) { + /* update cache */ + xfer->td_transfer_cache = td; + goto done; + } + /* + * last transfer descriptor makes the transfer done + */ + if (((void *)td) == xfer->td_transfer_last) { + break; + } + /* + * any kind of error makes the transfer done + */ + if (status & EHCI_QTD_HALTED) { + break; + } + /* + * if there is no alternate next transfer, a short + * packet also makes the transfer done + */ + if (EHCI_QTD_GET_BYTES(status)) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + td = td->alt_next; + continue; + } + } + /* transfer is done */ + break; + } + td = td->obj_next; + } + ehci_non_isoc_done(xfer); + goto transferred; + } + +done: + DPRINTFN(13, "xfer=%p is still active\n", xfer); + return (0); + +transferred: + return (1); +} + +static void +ehci_pcd_enable(ehci_softc_t *sc) +{ + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + sc->sc_eintrs |= EHCI_STS_PCD; + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + /* acknowledge any PCD interrupt */ + EOWRITE4(sc, EHCI_USBSTS, EHCI_STS_PCD); + + ehci_root_intr(sc); +} + +static void +ehci_interrupt_poll(ehci_softc_t *sc) +{ + struct usb_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + /* + * check if transfer is transferred + */ + if (ehci_check_transfer(xfer)) { + /* queue has been modified */ + goto repeat; + } + } +} + +/* + * Some EHCI chips from VIA / ATI seem to trigger interrupts before + * writing back the qTD status, or miss signalling occasionally under + * heavy load. If the host machine is too fast, we can miss + * transaction completion - when we scan the active list the + * transaction still seems to be active. This generally exhibits + * itself as a umass stall that never recovers. + * + * We work around this behaviour by setting up this callback after any + * softintr that completes with transactions still pending, giving us + * another chance to check for completion after the writeback has + * taken place. + */ +static void +ehci_poll_timeout(void *arg) +{ + ehci_softc_t *sc = arg; + + DPRINTFN(3, "\n"); + ehci_interrupt_poll(sc); +} + +/*------------------------------------------------------------------------* + * ehci_interrupt - EHCI interrupt handler + * + * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler, + * hence the interrupt handler will be setup before "sc->sc_bus.bdev" + * is present ! + *------------------------------------------------------------------------*/ +void +ehci_interrupt(ehci_softc_t *sc) +{ + uint32_t status; + + USB_BUS_LOCK(&sc->sc_bus); + + DPRINTFN(16, "real interrupt\n"); + +#ifdef USB_DEBUG + if (ehcidebug > 15) { + ehci_dump_regs(sc); + } +#endif + + status = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS)); + if (status == 0) { + /* the interrupt was not for us */ + goto done; + } + if (!(status & sc->sc_eintrs)) { + goto done; + } + EOWRITE4(sc, EHCI_USBSTS, status); /* acknowledge */ + + status &= sc->sc_eintrs; + + if (status & EHCI_STS_HSE) { + printf("%s: unrecoverable error, " + "controller halted\n", __FUNCTION__); +#ifdef USB_DEBUG + ehci_dump_regs(sc); + ehci_dump_isoc(sc); +#endif + } + if (status & EHCI_STS_PCD) { + /* + * Disable PCD interrupt for now, because it will be + * on until the port has been reset. + */ + sc->sc_eintrs &= ~EHCI_STS_PCD; + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + ehci_root_intr(sc); + + /* do not allow RHSC interrupts > 1 per second */ + usb_callout_reset(&sc->sc_tmo_pcd, hz, + (void *)&ehci_pcd_enable, sc); + } + status &= ~(EHCI_STS_INT | EHCI_STS_ERRINT | EHCI_STS_PCD | EHCI_STS_IAA); + + if (status != 0) { + /* block unprocessed interrupts */ + sc->sc_eintrs &= ~status; + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + printf("%s: blocking interrupts 0x%x\n", __FUNCTION__, status); + } + /* poll all the USB transfers */ + ehci_interrupt_poll(sc); + + if (sc->sc_flags & EHCI_SCFLG_LOSTINTRBUG) { + usb_callout_reset(&sc->sc_tmo_poll, hz / 128, + (void *)&ehci_poll_timeout, sc); + } + +done: + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/* + * called when a request does not complete + */ +static void +ehci_timeout(void *arg) +{ + struct usb_xfer *xfer = arg; + + DPRINTF("xfer=%p\n", xfer); + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* transfer is transferred */ + ehci_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +ehci_do_poll(struct usb_bus *bus) +{ + ehci_softc_t *sc = EHCI_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + ehci_interrupt_poll(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +ehci_setup_standard_chain_sub(struct ehci_std_temp *temp) +{ + struct usb_page_search buf_res; + ehci_qtd_t *td; + ehci_qtd_t *td_next; + ehci_qtd_t *td_alt_next; + uint32_t buf_offset; + uint32_t average; + uint32_t len_old; + uint32_t terminate; + uint32_t qtd_altnext; + uint8_t shortpkt_old; + uint8_t precompute; + + terminate = temp->sc->sc_terminate_self; + qtd_altnext = temp->sc->sc_terminate_self; + td_alt_next = NULL; + buf_offset = 0; + shortpkt_old = temp->shortpkt; + len_old = temp->len; + precompute = 1; + +restart: + + td = temp->td; + td_next = temp->td_next; + + while (1) { + + if (temp->len == 0) { + + if (temp->shortpkt) { + break; + } + /* send a Zero Length Packet, ZLP, last */ + + temp->shortpkt = 1; + average = 0; + + } else { + + average = temp->average; + + if (temp->len < average) { + if (temp->len % temp->max_frame_size) { + temp->shortpkt = 1; + } + average = temp->len; + } + } + + if (td_next == NULL) { + panic("%s: out of EHCI transfer descriptors!", __FUNCTION__); + } + /* get next TD */ + + td = td_next; + td_next = td->obj_next; + + /* check if we are pre-computing */ + + if (precompute) { + + /* update remaining length */ + + temp->len -= average; + + continue; + } + /* fill out current TD */ + + td->qtd_status = + temp->qtd_status | + htohc32(temp->sc, EHCI_QTD_IOC | + EHCI_QTD_SET_BYTES(average)); + + if (average == 0) { + + if (temp->auto_data_toggle == 0) { + + /* update data toggle, ZLP case */ + + temp->qtd_status ^= + htohc32(temp->sc, EHCI_QTD_TOGGLE_MASK); + } + td->len = 0; + + td->qtd_buffer[0] = 0; + td->qtd_buffer_hi[0] = 0; + + td->qtd_buffer[1] = 0; + td->qtd_buffer_hi[1] = 0; + + } else { + + uint8_t x; + + if (temp->auto_data_toggle == 0) { + + /* update data toggle */ + + if (((average + temp->max_frame_size - 1) / + temp->max_frame_size) & 1) { + temp->qtd_status ^= + htohc32(temp->sc, EHCI_QTD_TOGGLE_MASK); + } + } + td->len = average; + + /* update remaining length */ + + temp->len -= average; + + /* fill out buffer pointers */ + + usbd_get_page(temp->pc, buf_offset, &buf_res); + td->qtd_buffer[0] = + htohc32(temp->sc, buf_res.physaddr); + td->qtd_buffer_hi[0] = 0; + + x = 1; + + while (average > EHCI_PAGE_SIZE) { + average -= EHCI_PAGE_SIZE; + buf_offset += EHCI_PAGE_SIZE; + usbd_get_page(temp->pc, buf_offset, &buf_res); + td->qtd_buffer[x] = + htohc32(temp->sc, + buf_res.physaddr & (~0xFFF)); + td->qtd_buffer_hi[x] = 0; + x++; + } + + /* + * NOTE: The "average" variable is never zero after + * exiting the loop above ! + * + * NOTE: We have to subtract one from the offset to + * ensure that we are computing the physical address + * of a valid page ! + */ + buf_offset += average; + usbd_get_page(temp->pc, buf_offset - 1, &buf_res); + td->qtd_buffer[x] = + htohc32(temp->sc, + buf_res.physaddr & (~0xFFF)); + td->qtd_buffer_hi[x] = 0; + } + + if (td_next) { + /* link the current TD with the next one */ + td->qtd_next = td_next->qtd_self; + } + td->qtd_altnext = qtd_altnext; + td->alt_next = td_alt_next; + + usb_pc_cpu_flush(td->page_cache); + } + + if (precompute) { + precompute = 0; + + /* setup alt next pointer, if any */ + if (temp->last_frame) { + td_alt_next = NULL; + qtd_altnext = terminate; + } else { + /* we use this field internally */ + td_alt_next = td_next; + if (temp->setup_alt_next) { + qtd_altnext = td_next->qtd_self; + } else { + qtd_altnext = terminate; + } + } + + /* restore */ + temp->shortpkt = shortpkt_old; + temp->len = len_old; + goto restart; + } + temp->td = td; + temp->td_next = td_next; +} + +static void +ehci_setup_standard_chain(struct usb_xfer *xfer, ehci_qh_t **qh_last) +{ + struct ehci_std_temp temp; + struct usb_pipe_methods *methods; + ehci_qh_t *qh; + ehci_qtd_t *td; + uint32_t qh_endp; + uint32_t qh_endphub; + uint32_t x; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpointno), + xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); + + temp.average = xfer->max_hc_frame_size; + temp.max_frame_size = xfer->max_frame_size; + temp.sc = EHCI_BUS2SC(xfer->xroot->bus); + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + temp.td = NULL; + temp.td_next = td; + temp.qtd_status = 0; + temp.last_frame = 0; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + + if (xfer->flags_int.control_xfr) { + if (xfer->endpoint->toggle_next) { + /* DATA1 is next */ + temp.qtd_status |= + htohc32(temp.sc, EHCI_QTD_SET_TOGGLE(1)); + } + temp.auto_data_toggle = 0; + } else { + temp.auto_data_toggle = 1; + } + + if ((xfer->xroot->udev->parent_hs_hub != NULL) || + (xfer->xroot->udev->address != 0)) { + /* max 3 retries */ + temp.qtd_status |= + htohc32(temp.sc, EHCI_QTD_SET_CERR(3)); + } + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + xfer->endpoint->toggle_next = 0; + + temp.qtd_status &= + htohc32(temp.sc, EHCI_QTD_SET_CERR(3)); + temp.qtd_status |= htohc32(temp.sc, + EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_SETUP) | + EHCI_QTD_SET_TOGGLE(0)); + + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.shortpkt = temp.len ? 1 : 0; + /* check for last frame */ + if (xfer->nframes == 1) { + /* no STATUS stage yet, SETUP is last */ + if (xfer->flags_int.control_act) { + temp.last_frame = 1; + temp.setup_alt_next = 0; + } + } + ehci_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + temp.pc = xfer->frbuffers + x; + + x++; + + if (x == xfer->nframes) { + if (xfer->flags_int.control_xfr) { + /* no STATUS stage yet, DATA is last */ + if (xfer->flags_int.control_act) { + temp.last_frame = 1; + temp.setup_alt_next = 0; + } + } else { + temp.last_frame = 1; + temp.setup_alt_next = 0; + } + } + /* keep previous data toggle and error count */ + + temp.qtd_status &= + htohc32(temp.sc, EHCI_QTD_SET_CERR(3) | + EHCI_QTD_SET_TOGGLE(1)); + + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.shortpkt = 0; + + } else { + + /* regular data transfer */ + + temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + /* set endpoint direction */ + + temp.qtd_status |= + (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ? + htohc32(temp.sc, EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_IN)) : + htohc32(temp.sc, EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT)); + + ehci_setup_standard_chain_sub(&temp); + } + + /* check if we should append a status stage */ + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + /* + * Send a DATA1 message and invert the current endpoint + * direction. + */ + + temp.qtd_status &= htohc32(temp.sc, EHCI_QTD_SET_CERR(3) | + EHCI_QTD_SET_TOGGLE(1)); + temp.qtd_status |= + (UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT) ? + htohc32(temp.sc, EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_IN) | + EHCI_QTD_SET_TOGGLE(1)) : + htohc32(temp.sc, EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT) | + EHCI_QTD_SET_TOGGLE(1)); + + temp.len = 0; + temp.pc = NULL; + temp.shortpkt = 0; + temp.last_frame = 1; + temp.setup_alt_next = 0; + + ehci_setup_standard_chain_sub(&temp); + } + td = temp.td; + + /* the last TD terminates the transfer: */ + td->qtd_next = htohc32(temp.sc, EHCI_LINK_TERMINATE); + td->qtd_altnext = htohc32(temp.sc, EHCI_LINK_TERMINATE); + + usb_pc_cpu_flush(td->page_cache); + + /* must have at least one frame! */ + + xfer->td_transfer_last = td; + +#ifdef USB_DEBUG + if (ehcidebug > 8) { + DPRINTF("nexttog=%d; data before transfer:\n", + xfer->endpoint->toggle_next); + ehci_dump_sqtds(temp.sc, + xfer->td_transfer_first); + } +#endif + + methods = xfer->endpoint->methods; + + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + /* the "qh_link" field is filled when the QH is added */ + + qh_endp = + (EHCI_QH_SET_ADDR(xfer->address) | + EHCI_QH_SET_ENDPT(UE_GET_ADDR(xfer->endpointno)) | + EHCI_QH_SET_MPL(xfer->max_packet_size)); + + if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) { + qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH); + if (methods != &ehci_device_intr_methods) + qh_endp |= EHCI_QH_SET_NRL(8); + } else { + + if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_FULL) { + qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_FULL); + } else { + qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_LOW); + } + + if (methods == &ehci_device_ctrl_methods) { + qh_endp |= EHCI_QH_CTL; + } + if (methods != &ehci_device_intr_methods) { + /* Only try one time per microframe! */ + qh_endp |= EHCI_QH_SET_NRL(1); + } + } + + if (temp.auto_data_toggle == 0) { + /* software computes the data toggle */ + qh_endp |= EHCI_QH_DTC; + } + + qh->qh_endp = htohc32(temp.sc, qh_endp); + + qh_endphub = + (EHCI_QH_SET_MULT(xfer->max_packet_count & 3) | + EHCI_QH_SET_CMASK(xfer->endpoint->usb_cmask) | + EHCI_QH_SET_SMASK(xfer->endpoint->usb_smask) | + EHCI_QH_SET_HUBA(xfer->xroot->udev->hs_hub_addr) | + EHCI_QH_SET_PORT(xfer->xroot->udev->hs_port_no)); + + qh->qh_endphub = htohc32(temp.sc, qh_endphub); + qh->qh_curqtd = 0; + + /* fill the overlay qTD */ + + if (temp.auto_data_toggle && xfer->endpoint->toggle_next) { + /* DATA1 is next */ + qh->qh_qtd.qtd_status = htohc32(temp.sc, EHCI_QTD_SET_TOGGLE(1)); + } else { + qh->qh_qtd.qtd_status = 0; + } + + td = xfer->td_transfer_first; + + qh->qh_qtd.qtd_next = td->qtd_self; + qh->qh_qtd.qtd_altnext = + htohc32(temp.sc, EHCI_LINK_TERMINATE); + + usb_pc_cpu_flush(qh->page_cache); + + if (xfer->xroot->udev->flags.self_suspended == 0) { + EHCI_APPEND_QH(qh, *qh_last); + } +} + +static void +ehci_root_intr(ehci_softc_t *sc) +{ + uint16_t i; + uint16_t m; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* clear any old interrupt data */ + memset(sc->sc_hub_idata, 0, sizeof(sc->sc_hub_idata)); + + /* set bits */ + m = (sc->sc_noport + 1); + if (m > (8 * sizeof(sc->sc_hub_idata))) { + m = (8 * sizeof(sc->sc_hub_idata)); + } + for (i = 1; i < m; i++) { + /* pick out CHANGE bits from the status register */ + if (EOREAD4(sc, EHCI_PORTSC(i)) & EHCI_PS_CLEAR) { + sc->sc_hub_idata[i / 8] |= 1 << (i % 8); + DPRINTF("port %d changed\n", i); + } + } + uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, + sizeof(sc->sc_hub_idata)); +} + +static void +ehci_isoc_fs_done(ehci_softc_t *sc, struct usb_xfer *xfer) +{ + uint32_t nframes = xfer->nframes; + uint32_t status; + uint32_t *plen = xfer->frlengths; + uint16_t len = 0; + ehci_sitd_t *td = xfer->td_transfer_first; + ehci_sitd_t **pp_last = &sc->sc_isoc_fs_p_last[xfer->qh_pos]; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_fs_p_last[0]; + } +#ifdef USB_DEBUG + if (ehcidebug > 15) { + DPRINTF("isoc FS-TD\n"); + ehci_dump_sitd(sc, td); + } +#endif + usb_pc_cpu_invalidate(td->page_cache); + status = hc32toh(sc, td->sitd_status); + + len = EHCI_SITD_GET_LEN(status); + + DPRINTFN(2, "status=0x%08x, rem=%u\n", status, len); + + if (*plen >= len) { + len = *plen - len; + } else { + len = 0; + } + + *plen = len; + + /* remove FS-TD from schedule */ + EHCI_REMOVE_FS_TD(td, *pp_last); + + pp_last++; + plen++; + td = td->obj_next; + } + + xfer->aframes = xfer->nframes; +} + +static void +ehci_isoc_hs_done(ehci_softc_t *sc, struct usb_xfer *xfer) +{ + uint32_t nframes = xfer->nframes; + uint32_t status; + uint32_t *plen = xfer->frlengths; + uint16_t len = 0; + uint8_t td_no = 0; + ehci_itd_t *td = xfer->td_transfer_first; + ehci_itd_t **pp_last = &sc->sc_isoc_hs_p_last[xfer->qh_pos]; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + + while (nframes) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_hs_p_last[0]; + } +#ifdef USB_DEBUG + if (ehcidebug > 15) { + DPRINTF("isoc HS-TD\n"); + ehci_dump_itd(sc, td); + } +#endif + + usb_pc_cpu_invalidate(td->page_cache); + status = hc32toh(sc, td->itd_status[td_no]); + + len = EHCI_ITD_GET_LEN(status); + + DPRINTFN(2, "status=0x%08x, len=%u\n", status, len); + + if (xfer->endpoint->usb_smask & (1 << td_no)) { + + if (*plen >= len) { + /* + * The length is valid. NOTE: The + * complete length is written back + * into the status field, and not the + * remainder like with other transfer + * descriptor types. + */ + } else { + /* Invalid length - truncate */ + len = 0; + } + + *plen = len; + plen++; + nframes--; + } + + td_no++; + + if ((td_no == 8) || (nframes == 0)) { + /* remove HS-TD from schedule */ + EHCI_REMOVE_HS_TD(td, *pp_last); + pp_last++; + + td_no = 0; + td = td->obj_next; + } + } + xfer->aframes = xfer->nframes; +} + +/* NOTE: "done" can be run two times in a row, + * from close and from interrupt + */ +static void +ehci_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_pipe_methods *methods = xfer->endpoint->methods; + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); + + if ((methods == &ehci_device_bulk_methods) || + (methods == &ehci_device_ctrl_methods)) { +#ifdef USB_DEBUG + if (ehcidebug > 8) { + DPRINTF("nexttog=%d; data after transfer:\n", + xfer->endpoint->toggle_next); + ehci_dump_sqtds(sc, + xfer->td_transfer_first); + } +#endif + + EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_async_p_last); + } + if (methods == &ehci_device_intr_methods) { + EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_intr_p_last[xfer->qh_pos]); + } + /* + * Only finish isochronous transfers once which will update + * "xfer->frlengths". + */ + if (xfer->td_transfer_first && + xfer->td_transfer_last) { + if (methods == &ehci_device_isoc_fs_methods) { + ehci_isoc_fs_done(sc, xfer); + } + if (methods == &ehci_device_isoc_hs_methods) { + ehci_isoc_hs_done(sc, xfer); + } + xfer->td_transfer_first = NULL; + xfer->td_transfer_last = NULL; + } + /* dequeue transfer and start next transfer */ + usbd_transfer_done(xfer, error); +} + +/*------------------------------------------------------------------------* + * ehci bulk support + *------------------------------------------------------------------------*/ +static void +ehci_device_bulk_open(struct usb_xfer *xfer) +{ + return; +} + +static void +ehci_device_bulk_close(struct usb_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +ehci_device_bulk_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +ehci_device_bulk_start(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + uint32_t temp; + + /* setup TD's and QH */ + ehci_setup_standard_chain(xfer, &sc->sc_async_p_last); + + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); + + /* + * XXX Certain nVidia chipsets choke when using the IAAD + * feature too frequently. + */ + if (sc->sc_flags & EHCI_SCFLG_IAADBUG) + return; + + /* XXX Performance quirk: Some Host Controllers have a too low + * interrupt rate. Issue an IAAD to stimulate the Host + * Controller after queueing the BULK transfer. + */ + temp = EOREAD4(sc, EHCI_USBCMD); + if (!(temp & EHCI_CMD_IAAD)) + EOWRITE4(sc, EHCI_USBCMD, temp | EHCI_CMD_IAAD); +} + +struct usb_pipe_methods ehci_device_bulk_methods = +{ + .open = ehci_device_bulk_open, + .close = ehci_device_bulk_close, + .enter = ehci_device_bulk_enter, + .start = ehci_device_bulk_start, +}; + +/*------------------------------------------------------------------------* + * ehci control support + *------------------------------------------------------------------------*/ +static void +ehci_device_ctrl_open(struct usb_xfer *xfer) +{ + return; +} + +static void +ehci_device_ctrl_close(struct usb_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +ehci_device_ctrl_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +ehci_device_ctrl_start(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + + /* setup TD's and QH */ + ehci_setup_standard_chain(xfer, &sc->sc_async_p_last); + + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods ehci_device_ctrl_methods = +{ + .open = ehci_device_ctrl_open, + .close = ehci_device_ctrl_close, + .enter = ehci_device_ctrl_enter, + .start = ehci_device_ctrl_start, +}; + +/*------------------------------------------------------------------------* + * ehci interrupt support + *------------------------------------------------------------------------*/ +static void +ehci_device_intr_open(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + uint16_t best; + uint16_t bit; + uint16_t x; + + usb_hs_bandwidth_alloc(xfer); + + /* + * Find the best QH position corresponding to the given interval: + */ + + best = 0; + bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 2; + while (bit) { + if (xfer->interval >= bit) { + x = bit; + best = bit; + while (x & bit) { + if (sc->sc_intr_stat[x] < + sc->sc_intr_stat[best]) { + best = x; + } + x++; + } + break; + } + bit >>= 1; + } + + sc->sc_intr_stat[best]++; + xfer->qh_pos = best; + + DPRINTFN(3, "best=%d interval=%d\n", + best, xfer->interval); +} + +static void +ehci_device_intr_close(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + + sc->sc_intr_stat[xfer->qh_pos]--; + + ehci_device_done(xfer, USB_ERR_CANCELLED); + + /* bandwidth must be freed after device done */ + usb_hs_bandwidth_free(xfer); +} + +static void +ehci_device_intr_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +ehci_device_intr_start(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + + /* setup TD's and QH */ + ehci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]); + + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods ehci_device_intr_methods = +{ + .open = ehci_device_intr_open, + .close = ehci_device_intr_close, + .enter = ehci_device_intr_enter, + .start = ehci_device_intr_start, +}; + +/*------------------------------------------------------------------------* + * ehci full speed isochronous support + *------------------------------------------------------------------------*/ +static void +ehci_device_isoc_fs_open(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + ehci_sitd_t *td; + uint32_t sitd_portaddr; + uint8_t ds; + + sitd_portaddr = + EHCI_SITD_SET_ADDR(xfer->address) | + EHCI_SITD_SET_ENDPT(UE_GET_ADDR(xfer->endpointno)) | + EHCI_SITD_SET_HUBA(xfer->xroot->udev->hs_hub_addr) | + EHCI_SITD_SET_PORT(xfer->xroot->udev->hs_port_no); + + if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) { + sitd_portaddr |= EHCI_SITD_SET_DIR_IN; + } + sitd_portaddr = htohc32(sc, sitd_portaddr); + + /* initialize all TD's */ + + for (ds = 0; ds != 2; ds++) { + + for (td = xfer->td_start[ds]; td; td = td->obj_next) { + + td->sitd_portaddr = sitd_portaddr; + + /* + * TODO: make some kind of automatic + * SMASK/CMASK selection based on micro-frame + * usage + * + * micro-frame usage (8 microframes per 1ms) + */ + td->sitd_back = htohc32(sc, EHCI_LINK_TERMINATE); + + usb_pc_cpu_flush(td->page_cache); + } + } +} + +static void +ehci_device_isoc_fs_close(struct usb_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +ehci_device_isoc_fs_enter(struct usb_xfer *xfer) +{ + struct usb_page_search buf_res; + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + struct usb_fs_isoc_schedule *fss_start; + struct usb_fs_isoc_schedule *fss_end; + struct usb_fs_isoc_schedule *fss; + ehci_sitd_t *td; + ehci_sitd_t *td_last = NULL; + ehci_sitd_t **pp_last; + uint32_t *plen; + uint32_t buf_offset; + uint32_t nframes; + uint32_t temp; + uint32_t sitd_mask; + uint16_t tlen; + uint8_t sa; + uint8_t sb; + uint8_t error; + +#ifdef USB_DEBUG + uint8_t once = 1; + +#endif + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes); + + /* get the current frame index */ + + nframes = EOREAD4(sc, EHCI_FRINDEX) / 8; + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + buf_offset = (nframes - xfer->endpoint->isoc_next) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + if ((xfer->endpoint->is_synced == 0) || + (buf_offset < xfer->nframes)) { + /* + * If there is data underflow or the pipe queue is empty we + * schedule the transfer a few frames ahead of the current + * frame position. Else two isochronous transfers might + * overlap. + */ + xfer->endpoint->isoc_next = (nframes + 3) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + buf_offset = (xfer->endpoint->isoc_next - nframes) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usbd_fs_isoc_schedule_isoc_time_expand + (xfer->xroot->udev, &fss_start, &fss_end, nframes) + buf_offset + + xfer->nframes; + + /* get the real number of frames */ + + nframes = xfer->nframes; + + buf_offset = 0; + + plen = xfer->frlengths; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + xfer->td_transfer_first = td; + + pp_last = &sc->sc_isoc_fs_p_last[xfer->endpoint->isoc_next]; + + /* store starting position */ + + xfer->qh_pos = xfer->endpoint->isoc_next; + + fss = fss_start + (xfer->qh_pos % USB_ISOC_TIME_MAX); + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_fs_p_last[0]; + } + if (fss >= fss_end) { + fss = fss_start; + } + /* reuse sitd_portaddr and sitd_back from last transfer */ + + if (*plen > xfer->max_frame_size) { +#ifdef USB_DEBUG + if (once) { + once = 0; + printf("%s: frame length(%d) exceeds %d " + "bytes (frame truncated)\n", + __FUNCTION__, *plen, + xfer->max_frame_size); + } +#endif + *plen = xfer->max_frame_size; + } + /* + * We currently don't care if the ISOCHRONOUS schedule is + * full! + */ + error = usbd_fs_isoc_schedule_alloc(fss, &sa, *plen); + if (error) { + /* + * The FULL speed schedule is FULL! Set length + * to zero. + */ + *plen = 0; + } + if (*plen) { + /* + * only call "usbd_get_page()" when we have a + * non-zero length + */ + usbd_get_page(xfer->frbuffers, buf_offset, &buf_res); + td->sitd_bp[0] = htohc32(sc, buf_res.physaddr); + buf_offset += *plen; + /* + * NOTE: We need to subtract one from the offset so + * that we are on a valid page! + */ + usbd_get_page(xfer->frbuffers, buf_offset - 1, + &buf_res); + temp = buf_res.physaddr & ~0xFFF; + } else { + td->sitd_bp[0] = 0; + temp = 0; + } + + if (UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT) { + tlen = *plen; + if (tlen <= 188) { + temp |= 1; /* T-count = 1, TP = ALL */ + tlen = 1; + } else { + tlen += 187; + tlen /= 188; + temp |= tlen; /* T-count = [1..6] */ + temp |= 8; /* TP = Begin */ + } + + tlen += sa; + + if (tlen >= 8) { + sb = 0; + } else { + sb = (1 << tlen); + } + + sa = (1 << sa); + sa = (sb - sa) & 0x3F; + sb = 0; + } else { + sb = (-(4 << sa)) & 0xFE; + sa = (1 << sa) & 0x3F; + } + + sitd_mask = (EHCI_SITD_SET_SMASK(sa) | + EHCI_SITD_SET_CMASK(sb)); + + td->sitd_bp[1] = htohc32(sc, temp); + + td->sitd_mask = htohc32(sc, sitd_mask); + + if (nframes == 0) { + td->sitd_status = htohc32(sc, + EHCI_SITD_IOC | + EHCI_SITD_ACTIVE | + EHCI_SITD_SET_LEN(*plen)); + } else { + td->sitd_status = htohc32(sc, + EHCI_SITD_ACTIVE | + EHCI_SITD_SET_LEN(*plen)); + } + usb_pc_cpu_flush(td->page_cache); + +#ifdef USB_DEBUG + if (ehcidebug > 15) { + DPRINTF("FS-TD %d\n", nframes); + ehci_dump_sitd(sc, td); + } +#endif + /* insert TD into schedule */ + EHCI_APPEND_FS_TD(td, *pp_last); + pp_last++; + + plen++; + fss++; + td_last = td; + td = td->obj_next; + } + + xfer->td_transfer_last = td_last; + + /* update isoc_next */ + xfer->endpoint->isoc_next = (pp_last - &sc->sc_isoc_fs_p_last[0]) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); +} + +static void +ehci_device_isoc_fs_start(struct usb_xfer *xfer) +{ + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods ehci_device_isoc_fs_methods = +{ + .open = ehci_device_isoc_fs_open, + .close = ehci_device_isoc_fs_close, + .enter = ehci_device_isoc_fs_enter, + .start = ehci_device_isoc_fs_start, +}; + +/*------------------------------------------------------------------------* + * ehci high speed isochronous support + *------------------------------------------------------------------------*/ +static void +ehci_device_isoc_hs_open(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + ehci_itd_t *td; + uint32_t temp; + uint8_t ds; + + usb_hs_bandwidth_alloc(xfer); + + /* initialize all TD's */ + + for (ds = 0; ds != 2; ds++) { + + for (td = xfer->td_start[ds]; td; td = td->obj_next) { + + /* set TD inactive */ + td->itd_status[0] = 0; + td->itd_status[1] = 0; + td->itd_status[2] = 0; + td->itd_status[3] = 0; + td->itd_status[4] = 0; + td->itd_status[5] = 0; + td->itd_status[6] = 0; + td->itd_status[7] = 0; + + /* set endpoint and address */ + td->itd_bp[0] = htohc32(sc, + EHCI_ITD_SET_ADDR(xfer->address) | + EHCI_ITD_SET_ENDPT(UE_GET_ADDR(xfer->endpointno))); + + temp = + EHCI_ITD_SET_MPL(xfer->max_packet_size & 0x7FF); + + /* set direction */ + if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) { + temp |= EHCI_ITD_SET_DIR_IN; + } + /* set maximum packet size */ + td->itd_bp[1] = htohc32(sc, temp); + + /* set transfer multiplier */ + td->itd_bp[2] = htohc32(sc, xfer->max_packet_count & 3); + + usb_pc_cpu_flush(td->page_cache); + } + } +} + +static void +ehci_device_isoc_hs_close(struct usb_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); + + /* bandwidth must be freed after device done */ + usb_hs_bandwidth_free(xfer); +} + +static void +ehci_device_isoc_hs_enter(struct usb_xfer *xfer) +{ + struct usb_page_search buf_res; + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + ehci_itd_t *td; + ehci_itd_t *td_last = NULL; + ehci_itd_t **pp_last; + bus_size_t page_addr; + uint32_t *plen; + uint32_t status; + uint32_t buf_offset; + uint32_t nframes; + uint32_t itd_offset[8 + 1]; + uint8_t x; + uint8_t td_no; + uint8_t page_no; + uint8_t shift = usbd_xfer_get_fps_shift(xfer); + +#ifdef USB_DEBUG + uint8_t once = 1; + +#endif + + DPRINTFN(6, "xfer=%p next=%d nframes=%d shift=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes, (int)shift); + + /* get the current frame index */ + + nframes = EOREAD4(sc, EHCI_FRINDEX) / 8; + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + buf_offset = (nframes - xfer->endpoint->isoc_next) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + if ((xfer->endpoint->is_synced == 0) || + (buf_offset < (((xfer->nframes << shift) + 7) / 8))) { + /* + * If there is data underflow or the pipe queue is empty we + * schedule the transfer a few frames ahead of the current + * frame position. Else two isochronous transfers might + * overlap. + */ + xfer->endpoint->isoc_next = (nframes + 3) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + buf_offset = (xfer->endpoint->isoc_next - nframes) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset + + (((xfer->nframes << shift) + 7) / 8); + + /* get the real number of frames */ + + nframes = xfer->nframes; + + buf_offset = 0; + td_no = 0; + + plen = xfer->frlengths; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + xfer->td_transfer_first = td; + + pp_last = &sc->sc_isoc_hs_p_last[xfer->endpoint->isoc_next]; + + /* store starting position */ + + xfer->qh_pos = xfer->endpoint->isoc_next; + + while (nframes) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_hs_p_last[0]; + } + /* range check */ + if (*plen > xfer->max_frame_size) { +#ifdef USB_DEBUG + if (once) { + once = 0; + printf("%s: frame length(%d) exceeds %d bytes " + "(frame truncated)\n", + __FUNCTION__, *plen, xfer->max_frame_size); + } +#endif + *plen = xfer->max_frame_size; + } + + if (xfer->endpoint->usb_smask & (1 << td_no)) { + status = (EHCI_ITD_SET_LEN(*plen) | + EHCI_ITD_ACTIVE | + EHCI_ITD_SET_PG(0)); + td->itd_status[td_no] = htohc32(sc, status); + itd_offset[td_no] = buf_offset; + buf_offset += *plen; + plen++; + nframes --; + } else { + td->itd_status[td_no] = 0; /* not active */ + itd_offset[td_no] = buf_offset; + } + + td_no++; + + if ((td_no == 8) || (nframes == 0)) { + + /* the rest of the transfers are not active, if any */ + for (x = td_no; x != 8; x++) { + td->itd_status[x] = 0; /* not active */ + } + + /* check if there is any data to be transferred */ + if (itd_offset[0] != buf_offset) { + page_no = 0; + itd_offset[td_no] = buf_offset; + + /* get first page offset */ + usbd_get_page(xfer->frbuffers, itd_offset[0], &buf_res); + /* get page address */ + page_addr = buf_res.physaddr & ~0xFFF; + /* update page address */ + td->itd_bp[0] &= htohc32(sc, 0xFFF); + td->itd_bp[0] |= htohc32(sc, page_addr); + + for (x = 0; x != td_no; x++) { + /* set page number and page offset */ + status = (EHCI_ITD_SET_PG(page_no) | + (buf_res.physaddr & 0xFFF)); + td->itd_status[x] |= htohc32(sc, status); + + /* get next page offset */ + if (itd_offset[x + 1] == buf_offset) { + /* + * We subtract one so that + * we don't go off the last + * page! + */ + usbd_get_page(xfer->frbuffers, buf_offset - 1, &buf_res); + } else { + usbd_get_page(xfer->frbuffers, itd_offset[x + 1], &buf_res); + } + + /* check if we need a new page */ + if ((buf_res.physaddr ^ page_addr) & ~0xFFF) { + /* new page needed */ + page_addr = buf_res.physaddr & ~0xFFF; + if (page_no == 6) { + panic("%s: too many pages\n", __FUNCTION__); + } + page_no++; + /* update page address */ + td->itd_bp[page_no] &= htohc32(sc, 0xFFF); + td->itd_bp[page_no] |= htohc32(sc, page_addr); + } + } + } + /* set IOC bit if we are complete */ + if (nframes == 0) { + td->itd_status[td_no - 1] |= htohc32(sc, EHCI_ITD_IOC); + } + usb_pc_cpu_flush(td->page_cache); +#ifdef USB_DEBUG + if (ehcidebug > 15) { + DPRINTF("HS-TD %d\n", nframes); + ehci_dump_itd(sc, td); + } +#endif + /* insert TD into schedule */ + EHCI_APPEND_HS_TD(td, *pp_last); + pp_last++; + + td_no = 0; + td_last = td; + td = td->obj_next; + } + } + + xfer->td_transfer_last = td_last; + + /* update isoc_next */ + xfer->endpoint->isoc_next = (pp_last - &sc->sc_isoc_hs_p_last[0]) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); +} + +static void +ehci_device_isoc_hs_start(struct usb_xfer *xfer) +{ + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods ehci_device_isoc_hs_methods = +{ + .open = ehci_device_isoc_hs_open, + .close = ehci_device_isoc_hs_close, + .enter = ehci_device_isoc_hs_enter, + .start = ehci_device_isoc_hs_start, +}; + +/*------------------------------------------------------------------------* + * ehci root control support + *------------------------------------------------------------------------* + * Simulate a hardware hub by handling all the necessary requests. + *------------------------------------------------------------------------*/ + +static const +struct usb_device_descriptor ehci_devd = +{ + sizeof(struct usb_device_descriptor), + UDESC_DEVICE, /* type */ + {0x00, 0x02}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_HSHUBSTT, /* protocol */ + 64, /* max packet */ + {0}, {0}, {0x00, 0x01}, /* device id */ + 1, 2, 0, /* string indicies */ + 1 /* # of configurations */ +}; + +static const +struct usb_device_qualifier ehci_odevd = +{ + sizeof(struct usb_device_qualifier), + UDESC_DEVICE_QUALIFIER, /* type */ + {0x00, 0x02}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_FSHUB, /* protocol */ + 0, /* max packet */ + 0, /* # of configurations */ + 0 +}; + +static const struct ehci_config_desc ehci_confd = { + .confd = { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(ehci_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0 /* max power */ + }, + .ifcd = { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = 0, + }, + .endpd = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = UE_DIR_IN | EHCI_INTR_ENDPT, + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, /* max packet (63 ports) */ + .bInterval = 255, + }, +}; + +static const +struct usb_hub_descriptor ehci_hubd = +{ + 0, /* dynamic length */ + UDESC_HUB, + 0, + {0, 0}, + 0, + 0, + {0}, +}; + +static void +ehci_disown(ehci_softc_t *sc, uint16_t index, uint8_t lowspeed) +{ + uint32_t port; + uint32_t v; + + DPRINTF("index=%d lowspeed=%d\n", index, lowspeed); + + port = EHCI_PORTSC(index); + v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR; + EOWRITE4(sc, port, v | EHCI_PS_PO); +} + +static usb_error_t +ehci_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + ehci_softc_t *sc = EHCI_BUS2SC(udev->bus); + const char *str_ptr; + const void *ptr; + uint32_t port; + uint32_t v; + uint16_t len; + uint16_t i; + uint16_t value; + uint16_t index; + usb_error_t err; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* buffer reset */ + ptr = (const void *)&sc->sc_hub_desc; + len = 0; + err = 0; + + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " + "wValue=0x%04x wIndex=0x%04x\n", + req->bmRequestType, req->bRequest, + UGETW(req->wLength), value, index); + +#define C(x,y) ((x) | ((y) << 8)) + switch (C(req->bRequest, req->bmRequestType)) { + case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): + case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): + case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): + /* + * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops + * for the integrated root hub. + */ + break; + case C(UR_GET_CONFIG, UT_READ_DEVICE): + len = 1; + sc->sc_hub_desc.temp[0] = sc->sc_conf; + break; + case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): + switch (value >> 8) { + case UDESC_DEVICE: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(ehci_devd); + ptr = (const void *)&ehci_devd; + break; + /* + * We can't really operate at another speed, + * but the specification says we need this + * descriptor: + */ + case UDESC_DEVICE_QUALIFIER: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(ehci_odevd); + ptr = (const void *)&ehci_odevd; + break; + + case UDESC_CONFIG: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(ehci_confd); + ptr = (const void *)&ehci_confd; + break; + + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + str_ptr = "\001"; + break; + + case 1: /* Vendor */ + str_ptr = sc->sc_vendor; + break; + + case 2: /* Product */ + str_ptr = "EHCI root HUB"; + break; + + default: + str_ptr = ""; + break; + } + + len = usb_make_str_desc( + sc->sc_hub_desc.temp, + sizeof(sc->sc_hub_desc.temp), + str_ptr); + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_INTERFACE, UT_READ_INTERFACE): + len = 1; + sc->sc_hub_desc.temp[0] = 0; + break; + case C(UR_GET_STATUS, UT_READ_DEVICE): + len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED); + break; + case C(UR_GET_STATUS, UT_READ_INTERFACE): + case C(UR_GET_STATUS, UT_READ_ENDPOINT): + len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, 0); + break; + case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): + if (value >= EHCI_MAX_DEVICES) { + err = USB_ERR_IOERROR; + goto done; + } + sc->sc_addr = value; + break; + case C(UR_SET_CONFIG, UT_WRITE_DEVICE): + if ((value != 0) && (value != 1)) { + err = USB_ERR_IOERROR; + goto done; + } + sc->sc_conf = value; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_DEVICE): + case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): + case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): + err = USB_ERR_IOERROR; + goto done; + case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + break; + case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): + break; + /* Hub requests */ + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): + DPRINTFN(9, "UR_CLEAR_PORT_FEATURE\n"); + + if ((index < 1) || + (index > sc->sc_noport)) { + err = USB_ERR_IOERROR; + goto done; + } + port = EHCI_PORTSC(index); + v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR; + switch (value) { + case UHF_PORT_ENABLE: + EOWRITE4(sc, port, v & ~EHCI_PS_PE); + break; + case UHF_PORT_SUSPEND: + if ((v & EHCI_PS_SUSP) && (!(v & EHCI_PS_FPR))) { + + /* + * waking up a High Speed device is rather + * complicated if + */ + EOWRITE4(sc, port, v | EHCI_PS_FPR); + } + /* wait 20ms for resume sequence to complete */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50); + + EOWRITE4(sc, port, v & ~(EHCI_PS_SUSP | + EHCI_PS_FPR | (3 << 10) /* High Speed */ )); + + /* 4ms settle time */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250); + break; + case UHF_PORT_POWER: + EOWRITE4(sc, port, v & ~EHCI_PS_PP); + break; + case UHF_PORT_TEST: + DPRINTFN(3, "clear port test " + "%d\n", index); + break; + case UHF_PORT_INDICATOR: + DPRINTFN(3, "clear port ind " + "%d\n", index); + EOWRITE4(sc, port, v & ~EHCI_PS_PIC); + break; + case UHF_C_PORT_CONNECTION: + EOWRITE4(sc, port, v | EHCI_PS_CSC); + break; + case UHF_C_PORT_ENABLE: + EOWRITE4(sc, port, v | EHCI_PS_PEC); + break; + case UHF_C_PORT_SUSPEND: + EOWRITE4(sc, port, v | EHCI_PS_SUSP); + break; + case UHF_C_PORT_OVER_CURRENT: + EOWRITE4(sc, port, v | EHCI_PS_OCC); + break; + case UHF_C_PORT_RESET: + sc->sc_isreset = 0; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + v = EREAD4(sc, EHCI_HCSPARAMS); + + sc->sc_hub_desc.hubd = ehci_hubd; + sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport; + + if (EHCI_HCS_PPC(v)) + i = UHD_PWR_INDIVIDUAL; + else + i = UHD_PWR_NO_SWITCH; + + if (EHCI_HCS_P_INDICATOR(v)) + i |= UHD_PORT_IND; + + USETW(sc->sc_hub_desc.hubd.wHubCharacteristics, i); + /* XXX can't find out? */ + sc->sc_hub_desc.hubd.bPwrOn2PwrGood = 200; + /* XXX don't know if ports are removable or not */ + sc->sc_hub_desc.hubd.bDescLength = + 8 + ((sc->sc_noport + 7) / 8); + len = sc->sc_hub_desc.hubd.bDescLength; + break; + case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): + len = 16; + memset(sc->sc_hub_desc.temp, 0, 16); + break; + case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): + DPRINTFN(9, "get port status i=%d\n", + index); + if ((index < 1) || + (index > sc->sc_noport)) { + err = USB_ERR_IOERROR; + goto done; + } + v = EOREAD4(sc, EHCI_PORTSC(index)); + DPRINTFN(9, "port status=0x%04x\n", v); + if (sc->sc_flags & (EHCI_SCFLG_FORCESPEED | EHCI_SCFLG_TT)) { + if ((v & 0xc000000) == 0x8000000) + i = UPS_HIGH_SPEED; + else if ((v & 0xc000000) == 0x4000000) + i = UPS_LOW_SPEED; + else + i = 0; + } else { + i = UPS_HIGH_SPEED; + } + if (v & EHCI_PS_CS) + i |= UPS_CURRENT_CONNECT_STATUS; + if (v & EHCI_PS_PE) + i |= UPS_PORT_ENABLED; + if ((v & EHCI_PS_SUSP) && !(v & EHCI_PS_FPR)) + i |= UPS_SUSPEND; + if (v & EHCI_PS_OCA) + i |= UPS_OVERCURRENT_INDICATOR; + if (v & EHCI_PS_PR) + i |= UPS_RESET; + if (v & EHCI_PS_PP) + i |= UPS_PORT_POWER; + USETW(sc->sc_hub_desc.ps.wPortStatus, i); + i = 0; + if (v & EHCI_PS_CSC) + i |= UPS_C_CONNECT_STATUS; + if (v & EHCI_PS_PEC) + i |= UPS_C_PORT_ENABLED; + if (v & EHCI_PS_OCC) + i |= UPS_C_OVERCURRENT_INDICATOR; + if (v & EHCI_PS_FPR) + i |= UPS_C_SUSPEND; + if (sc->sc_isreset) + i |= UPS_C_PORT_RESET; + USETW(sc->sc_hub_desc.ps.wPortChange, i); + len = sizeof(sc->sc_hub_desc.ps); + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): + err = USB_ERR_IOERROR; + goto done; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): + if ((index < 1) || + (index > sc->sc_noport)) { + err = USB_ERR_IOERROR; + goto done; + } + port = EHCI_PORTSC(index); + v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR; + switch (value) { + case UHF_PORT_ENABLE: + EOWRITE4(sc, port, v | EHCI_PS_PE); + break; + case UHF_PORT_SUSPEND: + EOWRITE4(sc, port, v | EHCI_PS_SUSP); + break; + case UHF_PORT_RESET: + DPRINTFN(6, "reset port %d\n", index); +#ifdef USB_DEBUG + if (ehcinohighspeed) { + /* + * Connect USB device to companion + * controller. + */ + ehci_disown(sc, index, 1); + break; + } +#endif + if (EHCI_PS_IS_LOWSPEED(v) && + (sc->sc_flags & EHCI_SCFLG_TT) == 0) { + /* Low speed device, give up ownership. */ + ehci_disown(sc, index, 1); + break; + } + /* Start reset sequence. */ + v &= ~(EHCI_PS_PE | EHCI_PS_PR); + EOWRITE4(sc, port, v | EHCI_PS_PR); + + /* Wait for reset to complete. */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(USB_PORT_ROOT_RESET_DELAY)); + + /* Terminate reset sequence. */ + if (!(sc->sc_flags & EHCI_SCFLG_NORESTERM)) + EOWRITE4(sc, port, v); + + /* Wait for HC to complete reset. */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(EHCI_PORT_RESET_COMPLETE)); + + v = EOREAD4(sc, port); + DPRINTF("ehci after reset, status=0x%08x\n", v); + if (v & EHCI_PS_PR) { + device_printf(sc->sc_bus.bdev, + "port reset timeout\n"); + err = USB_ERR_TIMEOUT; + goto done; + } + if (!(v & EHCI_PS_PE) && + (sc->sc_flags & EHCI_SCFLG_TT) == 0) { + /* Not a high speed device, give up ownership.*/ + ehci_disown(sc, index, 0); + break; + } + sc->sc_isreset = 1; + DPRINTF("ehci port %d reset, status = 0x%08x\n", + index, v); + break; + + case UHF_PORT_POWER: + DPRINTFN(3, "set port power %d\n", index); + EOWRITE4(sc, port, v | EHCI_PS_PP); + break; + + case UHF_PORT_TEST: + DPRINTFN(3, "set port test %d\n", index); + break; + + case UHF_PORT_INDICATOR: + DPRINTFN(3, "set port ind %d\n", index); + EOWRITE4(sc, port, v | EHCI_PS_PIC); + break; + + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER): + case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER): + case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER): + case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER): + break; + default: + err = USB_ERR_IOERROR; + goto done; + } +done: + *plength = len; + *pptr = ptr; + return (err); +} + +static void +ehci_xfer_setup(struct usb_setup_params *parm) +{ + struct usb_page_search page_info; + struct usb_page_cache *pc; + ehci_softc_t *sc; + struct usb_xfer *xfer; + void *last_obj; + uint32_t nqtd; + uint32_t nqh; + uint32_t nsitd; + uint32_t nitd; + uint32_t n; + + sc = EHCI_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + nqtd = 0; + nqh = 0; + nsitd = 0; + nitd = 0; + + /* + * compute maximum number of some structures + */ + if (parm->methods == &ehci_device_ctrl_methods) { + + /* + * The proof for the "nqtd" formula is illustrated like + * this: + * + * +------------------------------------+ + * | | + * | |remainder -> | + * | +-----+---+ | + * | | xxx | x | frm 0 | + * | +-----+---++ | + * | | xxx | xx | frm 1 | + * | +-----+----+ | + * | ... | + * +------------------------------------+ + * + * "xxx" means a completely full USB transfer descriptor + * + * "x" and "xx" means a short USB packet + * + * For the remainder of an USB transfer modulo + * "max_data_length" we need two USB transfer descriptors. + * One to transfer the remaining data and one to finalise + * with a zero length packet in case the "force_short_xfer" + * flag is set. We only need two USB transfer descriptors in + * the case where the transfer length of the first one is a + * factor of "max_frame_size". The rest of the needed USB + * transfer descriptors is given by the buffer size divided + * by the maximum data payload. + */ + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX; + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nqh = 1; + nqtd = ((2 * xfer->nframes) + 1 /* STATUS */ + + (xfer->max_data_length / xfer->max_hc_frame_size)); + + } else if (parm->methods == &ehci_device_bulk_methods) { + + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX; + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nqh = 1; + nqtd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_hc_frame_size)); + + } else if (parm->methods == &ehci_device_intr_methods) { + + if (parm->speed == USB_SPEED_HIGH) { + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 3; + } else if (parm->speed == USB_SPEED_FULL) { + parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME; + parm->hc_max_packet_count = 1; + } else { + parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME / 8; + parm->hc_max_packet_count = 1; + } + + parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX; + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nqh = 1; + nqtd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_hc_frame_size)); + + } else if (parm->methods == &ehci_device_isoc_fs_methods) { + + parm->hc_max_packet_size = 0x3FF; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x3FF; + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nsitd = xfer->nframes; + + } else if (parm->methods == &ehci_device_isoc_hs_methods) { + + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 3; + parm->hc_max_frame_size = 0xC00; + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nitd = ((xfer->nframes + 7) / 8) << + usbd_xfer_get_fps_shift(xfer); + + } else { + + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x400; + + usbd_transfer_setup_sub(parm); + } + +alloc_dma_set: + + if (parm->err) { + return; + } + /* + * Allocate queue heads and transfer descriptors + */ + last_obj = NULL; + + if (usbd_transfer_setup_sub_malloc( + parm, &pc, sizeof(ehci_itd_t), + EHCI_ITD_ALIGN, nitd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nitd; n++) { + ehci_itd_t *td; + + usbd_get_page(pc + n, 0, &page_info); + + td = page_info.buffer; + + /* init TD */ + td->itd_self = htohc32(sc, page_info.physaddr | EHCI_LINK_ITD); + td->obj_next = last_obj; + td->page_cache = pc + n; + + last_obj = td; + + usb_pc_cpu_flush(pc + n); + } + } + if (usbd_transfer_setup_sub_malloc( + parm, &pc, sizeof(ehci_sitd_t), + EHCI_SITD_ALIGN, nsitd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nsitd; n++) { + ehci_sitd_t *td; + + usbd_get_page(pc + n, 0, &page_info); + + td = page_info.buffer; + + /* init TD */ + td->sitd_self = htohc32(sc, page_info.physaddr | EHCI_LINK_SITD); + td->obj_next = last_obj; + td->page_cache = pc + n; + + last_obj = td; + + usb_pc_cpu_flush(pc + n); + } + } + if (usbd_transfer_setup_sub_malloc( + parm, &pc, sizeof(ehci_qtd_t), + EHCI_QTD_ALIGN, nqtd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nqtd; n++) { + ehci_qtd_t *qtd; + + usbd_get_page(pc + n, 0, &page_info); + + qtd = page_info.buffer; + + /* init TD */ + qtd->qtd_self = htohc32(sc, page_info.physaddr); + qtd->obj_next = last_obj; + qtd->page_cache = pc + n; + + last_obj = qtd; + + usb_pc_cpu_flush(pc + n); + } + } + xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj; + + last_obj = NULL; + + if (usbd_transfer_setup_sub_malloc( + parm, &pc, sizeof(ehci_qh_t), + EHCI_QH_ALIGN, nqh)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nqh; n++) { + ehci_qh_t *qh; + + usbd_get_page(pc + n, 0, &page_info); + + qh = page_info.buffer; + + /* init QH */ + qh->qh_self = htohc32(sc, page_info.physaddr | EHCI_LINK_QH); + qh->obj_next = last_obj; + qh->page_cache = pc + n; + + last_obj = qh; + + usb_pc_cpu_flush(pc + n); + } + } + xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj; + + if (!xfer->flags_int.curr_dma_set) { + xfer->flags_int.curr_dma_set = 1; + goto alloc_dma_set; + } +} + +static void +ehci_xfer_unsetup(struct usb_xfer *xfer) +{ + return; +} + +static void +ehci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *ep) +{ + ehci_softc_t *sc = EHCI_BUS2SC(udev->bus); + + DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", + ep, udev->address, + edesc->bEndpointAddress, udev->flags.usb_mode, + sc->sc_addr); + + if (udev->flags.usb_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + if (udev->device_index != sc->sc_addr) { + + if ((udev->speed != USB_SPEED_HIGH) && + ((udev->hs_hub_addr == 0) || + (udev->hs_port_no == 0) || + (udev->parent_hs_hub == NULL) || + (udev->parent_hs_hub->hub == NULL))) { + /* We need a transaction translator */ + goto done; + } + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + ep->methods = &ehci_device_ctrl_methods; + break; + case UE_INTERRUPT: + ep->methods = &ehci_device_intr_methods; + break; + case UE_ISOCHRONOUS: + if (udev->speed == USB_SPEED_HIGH) { + ep->methods = &ehci_device_isoc_hs_methods; + } else if (udev->speed == USB_SPEED_FULL) { + ep->methods = &ehci_device_isoc_fs_methods; + } + break; + case UE_BULK: + ep->methods = &ehci_device_bulk_methods; + break; + default: + /* do nothing */ + break; + } + } +done: + return; +} + +static void +ehci_get_dma_delay(struct usb_device *udev, uint32_t *pus) +{ + /* + * Wait until the hardware has finished any possible use of + * the transfer descriptor(s) and QH + */ + *pus = (188); /* microseconds */ +} + +static void +ehci_device_resume(struct usb_device *udev) +{ + ehci_softc_t *sc = EHCI_BUS2SC(udev->bus); + struct usb_xfer *xfer; + struct usb_pipe_methods *methods; + + DPRINTF("\n"); + + USB_BUS_LOCK(udev->bus); + + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + + if (xfer->xroot->udev == udev) { + + methods = xfer->endpoint->methods; + + if ((methods == &ehci_device_bulk_methods) || + (methods == &ehci_device_ctrl_methods)) { + EHCI_APPEND_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_async_p_last); + } + if (methods == &ehci_device_intr_methods) { + EHCI_APPEND_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_intr_p_last[xfer->qh_pos]); + } + } + } + + USB_BUS_UNLOCK(udev->bus); + + return; +} + +static void +ehci_device_suspend(struct usb_device *udev) +{ + ehci_softc_t *sc = EHCI_BUS2SC(udev->bus); + struct usb_xfer *xfer; + struct usb_pipe_methods *methods; + + DPRINTF("\n"); + + USB_BUS_LOCK(udev->bus); + + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + + if (xfer->xroot->udev == udev) { + + methods = xfer->endpoint->methods; + + if ((methods == &ehci_device_bulk_methods) || + (methods == &ehci_device_ctrl_methods)) { + EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_async_p_last); + } + if (methods == &ehci_device_intr_methods) { + EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_intr_p_last[xfer->qh_pos]); + } + } + } + + USB_BUS_UNLOCK(udev->bus); +} + +static void +ehci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) +{ + struct ehci_softc *sc = EHCI_BUS2SC(bus); + + switch (state) { + case USB_HW_POWER_SUSPEND: + case USB_HW_POWER_SHUTDOWN: + ehci_suspend(sc); + break; + case USB_HW_POWER_RESUME: + ehci_resume(sc); + break; + default: + break; + } +} + +static void +ehci_set_hw_power(struct usb_bus *bus) +{ + ehci_softc_t *sc = EHCI_BUS2SC(bus); + uint32_t temp; + uint32_t flags; + + DPRINTF("\n"); + + USB_BUS_LOCK(bus); + + flags = bus->hw_power_state; + + temp = EOREAD4(sc, EHCI_USBCMD); + + temp &= ~(EHCI_CMD_ASE | EHCI_CMD_PSE); + + if (flags & (USB_HW_POWER_CONTROL | + USB_HW_POWER_BULK)) { + DPRINTF("Async is active\n"); + temp |= EHCI_CMD_ASE; + } + if (flags & (USB_HW_POWER_INTERRUPT | + USB_HW_POWER_ISOC)) { + DPRINTF("Periodic is active\n"); + temp |= EHCI_CMD_PSE; + } + EOWRITE4(sc, EHCI_USBCMD, temp); + + USB_BUS_UNLOCK(bus); + + return; +} + +struct usb_bus_methods ehci_bus_methods = +{ + .endpoint_init = ehci_ep_init, + .xfer_setup = ehci_xfer_setup, + .xfer_unsetup = ehci_xfer_unsetup, + .get_dma_delay = ehci_get_dma_delay, + .device_resume = ehci_device_resume, + .device_suspend = ehci_device_suspend, + .set_hw_power = ehci_set_hw_power, + .set_hw_power_sleep = ehci_set_hw_power_sleep, + .roothub_exec = ehci_roothub_exec, + .xfer_poll = ehci_do_poll, +}; diff --git a/sys/bus/u4b/controller/ehci.h b/sys/bus/u4b/controller/ehci.h new file mode 100644 index 0000000000..b8b6985df7 --- /dev/null +++ b/sys/bus/u4b/controller/ehci.h @@ -0,0 +1,448 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _EHCI_H_ +#define _EHCI_H_ + +#define EHCI_MAX_DEVICES MIN(USB_MAX_DEVICES, 128) + +/* + * Alignment NOTE: structures must be aligned so that the hardware can index + * without performing addition. + */ +#define EHCI_FRAMELIST_ALIGN 0x1000 /* bytes */ +#define EHCI_FRAMELIST_COUNT 1024 /* units */ +#define EHCI_VIRTUAL_FRAMELIST_COUNT 128 /* units */ + +#if ((8*EHCI_VIRTUAL_FRAMELIST_COUNT) < USB_MAX_HS_ISOC_FRAMES_PER_XFER) +#error "maximum number of high-speed isochronous frames is higher than supported!" +#endif + +#if (EHCI_VIRTUAL_FRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER) +#error "maximum number of full-speed isochronous frames is higher than supported!" +#endif + +/* Link types */ +#define EHCI_LINK_TERMINATE 0x00000001 +#define EHCI_LINK_TYPE(x) ((x) & 0x00000006) +#define EHCI_LINK_ITD 0x0 +#define EHCI_LINK_QH 0x2 +#define EHCI_LINK_SITD 0x4 +#define EHCI_LINK_FSTN 0x6 +#define EHCI_LINK_ADDR(x) ((x) &~ 0x1f) + +/* Structures alignment (bytes) */ +#define EHCI_ITD_ALIGN 128 +#define EHCI_SITD_ALIGN 64 +#define EHCI_QTD_ALIGN 64 +#define EHCI_QH_ALIGN 128 +#define EHCI_FSTN_ALIGN 32 +/* Data buffers are divided into one or more pages */ +#define EHCI_PAGE_SIZE 0x1000 +#if ((USB_PAGE_SIZE < EHCI_PAGE_SIZE) || (EHCI_PAGE_SIZE == 0) || \ + (USB_PAGE_SIZE < EHCI_ITD_ALIGN) || (EHCI_ITD_ALIGN == 0) || \ + (USB_PAGE_SIZE < EHCI_SITD_ALIGN) || (EHCI_SITD_ALIGN == 0) || \ + (USB_PAGE_SIZE < EHCI_QTD_ALIGN) || (EHCI_QTD_ALIGN == 0) || \ + (USB_PAGE_SIZE < EHCI_QH_ALIGN) || (EHCI_QH_ALIGN == 0) || \ + (USB_PAGE_SIZE < EHCI_FSTN_ALIGN) || (EHCI_FSTN_ALIGN == 0)) +#error "Invalid USB page size!" +#endif + + +/* + * Isochronous Transfer Descriptor. This descriptor is used for high speed + * transfers only. + */ +struct ehci_itd { + volatile uint32_t itd_next; + volatile uint32_t itd_status[8]; +#define EHCI_ITD_SET_LEN(x) ((x) << 16) +#define EHCI_ITD_GET_LEN(x) (((x) >> 16) & 0xFFF) +#define EHCI_ITD_IOC (1 << 15) +#define EHCI_ITD_SET_PG(x) ((x) << 12) +#define EHCI_ITD_GET_PG(x) (((x) >> 12) & 0x7) +#define EHCI_ITD_SET_OFFS(x) (x) +#define EHCI_ITD_GET_OFFS(x) (((x) >> 0) & 0xFFF) +#define EHCI_ITD_ACTIVE (1 << 31) +#define EHCI_ITD_DATABUFERR (1 << 30) +#define EHCI_ITD_BABBLE (1 << 29) +#define EHCI_ITD_XACTERR (1 << 28) + volatile uint32_t itd_bp[7]; + /* itd_bp[0] */ +#define EHCI_ITD_SET_ADDR(x) (x) +#define EHCI_ITD_GET_ADDR(x) (((x) >> 0) & 0x7F) +#define EHCI_ITD_SET_ENDPT(x) ((x) << 8) +#define EHCI_ITD_GET_ENDPT(x) (((x) >> 8) & 0xF) + /* itd_bp[1] */ +#define EHCI_ITD_SET_DIR_IN (1 << 11) +#define EHCI_ITD_SET_DIR_OUT (0 << 11) +#define EHCI_ITD_SET_MPL(x) (x) +#define EHCI_ITD_GET_MPL(x) (((x) >> 0) & 0x7FF) + volatile uint32_t itd_bp_hi[7]; +/* + * Extra information needed: + */ + uint32_t itd_self; + struct ehci_itd *next; + struct ehci_itd *prev; + struct ehci_itd *obj_next; + struct usb_page_cache *page_cache; +} __aligned(EHCI_ITD_ALIGN); + +typedef struct ehci_itd ehci_itd_t; + +/* + * Split Transaction Isochronous Transfer Descriptor. This descriptor is used + * for full speed transfers only. + */ +struct ehci_sitd { + volatile uint32_t sitd_next; + volatile uint32_t sitd_portaddr; +#define EHCI_SITD_SET_DIR_OUT (0 << 31) +#define EHCI_SITD_SET_DIR_IN (1 << 31) +#define EHCI_SITD_SET_ADDR(x) (x) +#define EHCI_SITD_GET_ADDR(x) ((x) & 0x7F) +#define EHCI_SITD_SET_ENDPT(x) ((x) << 8) +#define EHCI_SITD_GET_ENDPT(x) (((x) >> 8) & 0xF) +#define EHCI_SITD_GET_DIR(x) ((x) >> 31) +#define EHCI_SITD_SET_PORT(x) ((x) << 24) +#define EHCI_SITD_GET_PORT(x) (((x) >> 24) & 0x7F) +#define EHCI_SITD_SET_HUBA(x) ((x) << 16) +#define EHCI_SITD_GET_HUBA(x) (((x) >> 16) & 0x7F) + volatile uint32_t sitd_mask; +#define EHCI_SITD_SET_SMASK(x) (x) +#define EHCI_SITD_SET_CMASK(x) ((x) << 8) + volatile uint32_t sitd_status; +#define EHCI_SITD_COMPLETE_SPLIT (1<<1) +#define EHCI_SITD_START_SPLIT (0<<1) +#define EHCI_SITD_MISSED_MICRO_FRAME (1<<2) +#define EHCI_SITD_XACTERR (1<<3) +#define EHCI_SITD_BABBLE (1<<4) +#define EHCI_SITD_DATABUFERR (1<<5) +#define EHCI_SITD_ERROR (1<<6) +#define EHCI_SITD_ACTIVE (1<<7) +#define EHCI_SITD_IOC (1<<31) +#define EHCI_SITD_SET_LEN(len) ((len)<<16) +#define EHCI_SITD_GET_LEN(x) (((x)>>16) & 0x3FF) + volatile uint32_t sitd_bp[2]; + volatile uint32_t sitd_back; + volatile uint32_t sitd_bp_hi[2]; +/* + * Extra information needed: + */ + uint32_t sitd_self; + struct ehci_sitd *next; + struct ehci_sitd *prev; + struct ehci_sitd *obj_next; + struct usb_page_cache *page_cache; +} __aligned(EHCI_SITD_ALIGN); + +typedef struct ehci_sitd ehci_sitd_t; + +/* Queue Element Transfer Descriptor */ +struct ehci_qtd { + volatile uint32_t qtd_next; + volatile uint32_t qtd_altnext; + volatile uint32_t qtd_status; +#define EHCI_QTD_GET_STATUS(x) (((x) >> 0) & 0xff) +#define EHCI_QTD_SET_STATUS(x) ((x) << 0) +#define EHCI_QTD_ACTIVE 0x80 +#define EHCI_QTD_HALTED 0x40 +#define EHCI_QTD_BUFERR 0x20 +#define EHCI_QTD_BABBLE 0x10 +#define EHCI_QTD_XACTERR 0x08 +#define EHCI_QTD_MISSEDMICRO 0x04 +#define EHCI_QTD_SPLITXSTATE 0x02 +#define EHCI_QTD_PINGSTATE 0x01 +#define EHCI_QTD_STATERRS 0x74 +#define EHCI_QTD_GET_PID(x) (((x) >> 8) & 0x3) +#define EHCI_QTD_SET_PID(x) ((x) << 8) +#define EHCI_QTD_PID_OUT 0x0 +#define EHCI_QTD_PID_IN 0x1 +#define EHCI_QTD_PID_SETUP 0x2 +#define EHCI_QTD_GET_CERR(x) (((x) >> 10) & 0x3) +#define EHCI_QTD_SET_CERR(x) ((x) << 10) +#define EHCI_QTD_GET_C_PAGE(x) (((x) >> 12) & 0x7) +#define EHCI_QTD_SET_C_PAGE(x) ((x) << 12) +#define EHCI_QTD_GET_IOC(x) (((x) >> 15) & 0x1) +#define EHCI_QTD_IOC 0x00008000 +#define EHCI_QTD_GET_BYTES(x) (((x) >> 16) & 0x7fff) +#define EHCI_QTD_SET_BYTES(x) ((x) << 16) +#define EHCI_QTD_GET_TOGGLE(x) (((x) >> 31) & 0x1) +#define EHCI_QTD_SET_TOGGLE(x) ((x) << 31) +#define EHCI_QTD_TOGGLE_MASK 0x80000000 +#define EHCI_QTD_NBUFFERS 5 +#define EHCI_QTD_PAYLOAD_MAX ((EHCI_QTD_NBUFFERS-1)*EHCI_PAGE_SIZE) + volatile uint32_t qtd_buffer[EHCI_QTD_NBUFFERS]; + volatile uint32_t qtd_buffer_hi[EHCI_QTD_NBUFFERS]; +/* + * Extra information needed: + */ + struct ehci_qtd *alt_next; + struct ehci_qtd *obj_next; + struct usb_page_cache *page_cache; + uint32_t qtd_self; + uint16_t len; +} __aligned(EHCI_QTD_ALIGN); + +typedef struct ehci_qtd ehci_qtd_t; + +/* Queue Head Sub Structure */ +struct ehci_qh_sub { + volatile uint32_t qtd_next; + volatile uint32_t qtd_altnext; + volatile uint32_t qtd_status; + volatile uint32_t qtd_buffer[EHCI_QTD_NBUFFERS]; + volatile uint32_t qtd_buffer_hi[EHCI_QTD_NBUFFERS]; +} __aligned(4); + +/* Queue Head */ +struct ehci_qh { + volatile uint32_t qh_link; + volatile uint32_t qh_endp; +#define EHCI_QH_GET_ADDR(x) (((x) >> 0) & 0x7f) /* endpoint addr */ +#define EHCI_QH_SET_ADDR(x) (x) +#define EHCI_QH_ADDRMASK 0x0000007f +#define EHCI_QH_GET_INACT(x) (((x) >> 7) & 0x01) /* inactivate on next */ +#define EHCI_QH_INACT 0x00000080 +#define EHCI_QH_GET_ENDPT(x) (((x) >> 8) & 0x0f) /* endpoint no */ +#define EHCI_QH_SET_ENDPT(x) ((x) << 8) +#define EHCI_QH_GET_EPS(x) (((x) >> 12) & 0x03) /* endpoint speed */ +#define EHCI_QH_SET_EPS(x) ((x) << 12) +#define EHCI_QH_SPEED_FULL 0x0 +#define EHCI_QH_SPEED_LOW 0x1 +#define EHCI_QH_SPEED_HIGH 0x2 +#define EHCI_QH_GET_DTC(x) (((x) >> 14) & 0x01) /* data toggle control */ +#define EHCI_QH_DTC 0x00004000 +#define EHCI_QH_GET_HRECL(x) (((x) >> 15) & 0x01) /* head of reclamation */ +#define EHCI_QH_HRECL 0x00008000 +#define EHCI_QH_GET_MPL(x) (((x) >> 16) & 0x7ff) /* max packet len */ +#define EHCI_QH_SET_MPL(x) ((x) << 16) +#define EHCI_QH_MPLMASK 0x07ff0000 +#define EHCI_QH_GET_CTL(x) (((x) >> 27) & 0x01) /* control endpoint */ +#define EHCI_QH_CTL 0x08000000 +#define EHCI_QH_GET_NRL(x) (((x) >> 28) & 0x0f) /* NAK reload */ +#define EHCI_QH_SET_NRL(x) ((x) << 28) + volatile uint32_t qh_endphub; +#define EHCI_QH_GET_SMASK(x) (((x) >> 0) & 0xff) /* intr sched mask */ +#define EHCI_QH_SET_SMASK(x) ((x) << 0) +#define EHCI_QH_GET_CMASK(x) (((x) >> 8) & 0xff) /* split completion mask */ +#define EHCI_QH_SET_CMASK(x) ((x) << 8) +#define EHCI_QH_GET_HUBA(x) (((x) >> 16) & 0x7f) /* hub address */ +#define EHCI_QH_SET_HUBA(x) ((x) << 16) +#define EHCI_QH_GET_PORT(x) (((x) >> 23) & 0x7f) /* hub port */ +#define EHCI_QH_SET_PORT(x) ((x) << 23) +#define EHCI_QH_GET_MULT(x) (((x) >> 30) & 0x03) /* pipe multiplier */ +#define EHCI_QH_SET_MULT(x) ((x) << 30) + volatile uint32_t qh_curqtd; + struct ehci_qh_sub qh_qtd; +/* + * Extra information needed: + */ + struct ehci_qh *next; + struct ehci_qh *prev; + struct ehci_qh *obj_next; + struct usb_page_cache *page_cache; + uint32_t qh_self; +} __aligned(EHCI_QH_ALIGN); + +typedef struct ehci_qh ehci_qh_t; + +/* Periodic Frame Span Traversal Node */ +struct ehci_fstn { + volatile uint32_t fstn_link; + volatile uint32_t fstn_back; +} __aligned(EHCI_FSTN_ALIGN); + +typedef struct ehci_fstn ehci_fstn_t; + +struct ehci_hw_softc { + struct usb_page_cache pframes_pc; + struct usb_page_cache terminate_pc; + struct usb_page_cache async_start_pc; + struct usb_page_cache intr_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct usb_page_cache isoc_hs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct usb_page_cache isoc_fs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT]; + + struct usb_page pframes_pg; + struct usb_page terminate_pg; + struct usb_page async_start_pg; + struct usb_page intr_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct usb_page isoc_hs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct usb_page isoc_fs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT]; +}; + +struct ehci_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union ehci_hub_desc { + struct usb_status stat; + struct usb_port_status ps; + struct usb_hub_descriptor hubd; + uint8_t temp[128]; +}; + +typedef struct ehci_softc { + struct ehci_hw_softc sc_hw; + struct usb_bus sc_bus; /* base device */ + struct usb_callout sc_tmo_pcd; + struct usb_callout sc_tmo_poll; + union ehci_hub_desc sc_hub_desc; + + struct usb_device *sc_devices[EHCI_MAX_DEVICES]; + struct resource *sc_io_res; + struct resource *sc_irq_res; + struct ehci_qh *sc_async_p_last; + struct ehci_qh *sc_intr_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct ehci_sitd *sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct ehci_itd *sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]; + void *sc_intr_hdl; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + uint32_t sc_terminate_self; /* TD short packet termination pointer */ + uint32_t sc_eintrs; + + uint16_t sc_intr_stat[EHCI_VIRTUAL_FRAMELIST_COUNT]; + uint16_t sc_id_vendor; /* vendor ID for root hub */ + uint16_t sc_flags; /* chip specific flags */ +#define EHCI_SCFLG_SETMODE 0x0001 /* set bridge mode again after init */ +#define EHCI_SCFLG_FORCESPEED 0x0002 /* force speed */ +#define EHCI_SCFLG_NORESTERM 0x0004 /* don't terminate reset sequence */ +#define EHCI_SCFLG_BIGEDESC 0x0008 /* big-endian byte order descriptors */ +#define EHCI_SCFLG_BIGEMMIO 0x0010 /* big-endian byte order MMIO */ +#define EHCI_SCFLG_TT 0x0020 /* transaction translator present */ +#define EHCI_SCFLG_LOSTINTRBUG 0x0040 /* workaround for VIA / ATI chipsets */ +#define EHCI_SCFLG_IAADBUG 0x0080 /* workaround for nVidia chipsets */ + + uint8_t sc_offs; /* offset to operational registers */ + uint8_t sc_doorbell_disable; /* set on doorbell failure */ + uint8_t sc_noport; + uint8_t sc_addr; /* device address */ + uint8_t sc_conf; /* device configuration */ + uint8_t sc_isreset; + uint8_t sc_hub_idata[8]; + + char sc_vendor[16]; /* vendor string for root hub */ + +} ehci_softc_t; + +#define EREAD1(sc, a) bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (a)) +#define EREAD2(sc, a) bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a)) +#define EREAD4(sc, a) bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a)) +#define EWRITE1(sc, a, x) \ + bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x)) +#define EWRITE2(sc, a, x) \ + bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x)) +#define EWRITE4(sc, a, x) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x)) +#define EOREAD1(sc, a) \ + bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a)) +#define EOREAD2(sc, a) \ + bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a)) +#define EOREAD4(sc, a) \ + bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a)) +#define EOWRITE1(sc, a, x) \ + bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x)) +#define EOWRITE2(sc, a, x) \ + bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x)) +#define EOWRITE4(sc, a, x) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x)) + +#ifdef USB_EHCI_BIG_ENDIAN_DESC +/* + * Handle byte order conversion between host and ``host controller''. + * Typically the latter is little-endian but some controllers require + * big-endian in which case we may need to manually swap. + */ +static __inline uint32_t +htohc32(const struct ehci_softc *sc, const uint32_t v) +{ + return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? htobe32(v) : htole32(v); +} + +static __inline uint16_t +htohc16(const struct ehci_softc *sc, const uint16_t v) +{ + return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? htobe16(v) : htole16(v); +} + +static __inline uint32_t +hc32toh(const struct ehci_softc *sc, const uint32_t v) +{ + return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? be32toh(v) : le32toh(v); +} + +static __inline uint16_t +hc16toh(const struct ehci_softc *sc, const uint16_t v) +{ + return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? be16toh(v) : le16toh(v); +} +#else +/* + * Normal little-endian only conversion routines. + */ +static __inline uint32_t +htohc32(const struct ehci_softc *sc, const uint32_t v) +{ + return htole32(v); +} + +static __inline uint16_t +htohc16(const struct ehci_softc *sc, const uint16_t v) +{ + return htole16(v); +} + +static __inline uint32_t +hc32toh(const struct ehci_softc *sc, const uint32_t v) +{ + return le32toh(v); +} + +static __inline uint16_t +hc16toh(const struct ehci_softc *sc, const uint16_t v) +{ + return le16toh(v); +} +#endif + +usb_bus_mem_cb_t ehci_iterate_hw_softc; + +usb_error_t ehci_reset(ehci_softc_t *sc); +usb_error_t ehci_init(ehci_softc_t *sc); +void ehci_detach(struct ehci_softc *sc); +void ehci_interrupt(ehci_softc_t *sc); + +#endif /* _EHCI_H_ */ diff --git a/sys/bus/u4b/controller/ehci_ixp4xx.c b/sys/bus/u4b/controller/ehci_ixp4xx.c new file mode 100644 index 0000000000..45113d9f4e --- /dev/null +++ b/sys/bus/u4b/controller/ehci_ixp4xx.c @@ -0,0 +1,312 @@ +/*- + * Copyright (c) 2008 Sam Leffler. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * IXP435 attachment driver for the USB Enhanced Host Controller. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "opt_bus.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#define EHCI_VENDORID_IXP4XX 0x42fa05 +#define EHCI_HC_DEVSTR "IXP4XX Integrated USB 2.0 controller" + +struct ixp_ehci_softc { + ehci_softc_t base; /* storage for EHCI code */ + bus_space_tag_t iot; + bus_space_handle_t ioh; + struct bus_space tag; /* tag for private bus space ops */ +}; + +static device_attach_t ehci_ixp_attach; +static device_detach_t ehci_ixp_detach; + +static uint8_t ehci_bs_r_1(void *, bus_space_handle_t, bus_size_t); +static void ehci_bs_w_1(void *, bus_space_handle_t, bus_size_t, u_int8_t); +static uint16_t ehci_bs_r_2(void *, bus_space_handle_t, bus_size_t); +static void ehci_bs_w_2(void *, bus_space_handle_t, bus_size_t, uint16_t); +static uint32_t ehci_bs_r_4(void *, bus_space_handle_t, bus_size_t); +static void ehci_bs_w_4(void *, bus_space_handle_t, bus_size_t, uint32_t); + +static int +ehci_ixp_probe(device_t self) +{ + + device_set_desc(self, EHCI_HC_DEVSTR); + + return (BUS_PROBE_DEFAULT); +} + +static int +ehci_ixp_attach(device_t self) +{ + struct ixp_ehci_softc *isc = device_get_softc(self); + ehci_softc_t *sc = &isc->base; + int err; + int rid; + + /* initialise some bus fields */ + sc->sc_bus.parent = self; + sc->sc_bus.devices = sc->sc_devices; + sc->sc_bus.devices_max = EHCI_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_bus, + USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) { + return (ENOMEM); + } + + /* NB: hints fix the memory location and irq */ + + rid = 0; + sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); + if (!sc->sc_io_res) { + device_printf(self, "Could not map memory\n"); + goto error; + } + + /* + * Craft special resource for bus space ops that handle + * byte-alignment of non-word addresses. Also, since + * we're already intercepting bus space ops we handle + * the register window offset that could otherwise be + * done with bus_space_subregion. + */ + isc->iot = rman_get_bustag(sc->sc_io_res); + isc->tag.bs_cookie = isc->iot; + /* read single */ + isc->tag.bs_r_1 = ehci_bs_r_1, + isc->tag.bs_r_2 = ehci_bs_r_2, + isc->tag.bs_r_4 = ehci_bs_r_4, + /* write (single) */ + isc->tag.bs_w_1 = ehci_bs_w_1, + isc->tag.bs_w_2 = ehci_bs_w_2, + isc->tag.bs_w_4 = ehci_bs_w_4, + + sc->sc_io_tag = &isc->tag; + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = IXP435_USB1_SIZE - 0x100; + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + goto error; + } + sc->sc_bus.bdev = device_add_child(self, "usbus", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + device_set_desc(sc->sc_bus.bdev, EHCI_HC_DEVSTR); + + sprintf(sc->sc_vendor, "Intel"); + + + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->sc_intr_hdl = NULL; + goto error; + } + + /* + * Arrange to force Host mode, select big-endian byte alignment, + * and arrange to not terminate reset operations (the adapter + * will ignore it if we do but might as well save a reg write). + * Also, the controller has an embedded Transaction Translator + * which means port speed must be read from the Port Status + * register following a port enable. + */ + sc->sc_flags |= EHCI_SCFLG_TT + | EHCI_SCFLG_SETMODE + | EHCI_SCFLG_BIGEDESC + | EHCI_SCFLG_BIGEMMIO + | EHCI_SCFLG_NORESTERM + ; + + err = ehci_init(sc); + if (!err) { + err = device_probe_and_attach(sc->sc_bus.bdev); + } + if (err) { + device_printf(self, "USB init failed err=%d\n", err); + goto error; + } + return (0); + +error: + ehci_ixp_detach(self); + return (ENXIO); +} + +static int +ehci_ixp_detach(device_t self) +{ + struct ixp_ehci_softc *isc = device_get_softc(self); + ehci_softc_t *sc = &isc->base; + device_t bdev; + int err; + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(self, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(self); + + if (sc->sc_irq_res && sc->sc_intr_hdl) { + /* + * only call ehci_detach() after ehci_init() + */ + ehci_detach(sc); + + err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); + + if (err) + /* XXX or should we panic? */ + device_printf(self, "Could not tear down irq, %d\n", + err); + sc->sc_intr_hdl = NULL; + } + + if (sc->sc_irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(self, SYS_RES_MEMORY, 0, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc); + + return (0); +} + +/* + * Bus space accessors for PIO operations. + */ + +static uint8_t +ehci_bs_r_1(void *t, bus_space_handle_t h, bus_size_t o) +{ + return bus_space_read_1((bus_space_tag_t) t, h, + 0x100 + (o &~ 3) + (3 - (o & 3))); +} + +static void +ehci_bs_w_1(void *t, bus_space_handle_t h, bus_size_t o, u_int8_t v) +{ + panic("%s", __func__); +} + +static uint16_t +ehci_bs_r_2(void *t, bus_space_handle_t h, bus_size_t o) +{ + return bus_space_read_2((bus_space_tag_t) t, h, + 0x100 + (o &~ 3) + (2 - (o & 3))); +} + +static void +ehci_bs_w_2(void *t, bus_space_handle_t h, bus_size_t o, uint16_t v) +{ + panic("%s", __func__); +} + +static uint32_t +ehci_bs_r_4(void *t, bus_space_handle_t h, bus_size_t o) +{ + return bus_space_read_4((bus_space_tag_t) t, h, 0x100 + o); +} + +static void +ehci_bs_w_4(void *t, bus_space_handle_t h, bus_size_t o, uint32_t v) +{ + bus_space_write_4((bus_space_tag_t) t, h, 0x100 + o, v); +} + +static device_method_t ehci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ehci_ixp_probe), + DEVMETHOD(device_attach, ehci_ixp_attach), + DEVMETHOD(device_detach, ehci_ixp_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD_END +}; + +static driver_t ehci_driver = { + "ehci", + ehci_methods, + sizeof(struct ixp_ehci_softc), +}; + +static devclass_t ehci_devclass; + +DRIVER_MODULE(ehci, ixp, ehci_driver, ehci_devclass, 0, 0); +MODULE_DEPEND(ehci, usb, 1, 1, 1); diff --git a/sys/bus/u4b/controller/ehci_mv.c b/sys/bus/u4b/controller/ehci_mv.c new file mode 100644 index 0000000000..a47e253841 --- /dev/null +++ b/sys/bus/u4b/controller/ehci_mv.c @@ -0,0 +1,349 @@ +/*- + * Copyright (C) 2008 MARVELL INTERNATIONAL LTD. + * All rights reserved. + * + * Developed by Semihalf. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of MARVELL nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * FDT attachment driver for the USB Enhanced Host Controller. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "opt_bus.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#define EHCI_VENDORID_MRVL 0x1286 +#define EHCI_HC_DEVSTR "Marvell Integrated USB 2.0 controller" + +static device_attach_t mv_ehci_attach; +static device_detach_t mv_ehci_detach; + +static int err_intr(void *arg); + +static struct resource *irq_err; +static void *ih_err; + +/* EHCI HC regs start at this offset within USB range */ +#define MV_USB_HOST_OFST 0x0100 + +#define USB_BRIDGE_INTR_CAUSE 0x210 +#define USB_BRIDGE_INTR_MASK 0x214 +#define USB_BRIDGE_ERR_ADDR 0x21C + +#define MV_USB_ADDR_DECODE_ERR (1 << 0) +#define MV_USB_HOST_UNDERFLOW (1 << 1) +#define MV_USB_HOST_OVERFLOW (1 << 2) +#define MV_USB_DEVICE_UNDERFLOW (1 << 3) + +static int +mv_ehci_probe(device_t self) +{ + + if (!ofw_bus_is_compatible(self, "mrvl,usb-ehci")) + return (ENXIO); + + device_set_desc(self, EHCI_HC_DEVSTR); + + return (BUS_PROBE_DEFAULT); +} + +static int +mv_ehci_attach(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + bus_space_handle_t bsh; + int err; + int rid; + + /* initialise some bus fields */ + sc->sc_bus.parent = self; + sc->sc_bus.devices = sc->sc_devices; + sc->sc_bus.devices_max = EHCI_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_bus, + USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) { + return (ENOMEM); + } + + rid = 0; + sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); + if (!sc->sc_io_res) { + device_printf(self, "Could not map memory\n"); + goto error; + } + sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); + bsh = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res) - MV_USB_HOST_OFST; + + /* + * Marvell EHCI host controller registers start at certain offset + * within the whole USB registers range, so create a subregion for the + * host mode configuration purposes. + */ + + if (bus_space_subregion(sc->sc_io_tag, bsh, MV_USB_HOST_OFST, + sc->sc_io_size, &sc->sc_io_hdl) != 0) + panic("%s: unable to subregion USB host registers", + device_get_name(self)); + + rid = 0; + irq_err = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (irq_err == NULL) { + device_printf(self, "Could not allocate error irq\n"); + mv_ehci_detach(self); + return (ENXIO); + } + + /* + * Notice: Marvell EHCI controller has TWO interrupt lines, so make + * sure to use the correct rid for the main one (controller interrupt) + * -- refer to DTS for the right resource number to use here. + */ + rid = 1; + sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + goto error; + } + + sc->sc_bus.bdev = device_add_child(self, "usbus", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + device_set_desc(sc->sc_bus.bdev, EHCI_HC_DEVSTR); + + sprintf(sc->sc_vendor, "Marvell"); + + err = bus_setup_intr(self, irq_err, INTR_TYPE_BIO, + err_intr, NULL, sc, &ih_err); + if (err) { + device_printf(self, "Could not setup error irq, %d\n", err); + ih_err = NULL; + goto error; + } + + EWRITE4(sc, USB_BRIDGE_INTR_MASK, MV_USB_ADDR_DECODE_ERR | + MV_USB_HOST_UNDERFLOW | MV_USB_HOST_OVERFLOW | + MV_USB_DEVICE_UNDERFLOW); + + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->sc_intr_hdl = NULL; + goto error; + } + + /* + * Workaround for Marvell integrated EHCI controller: reset of + * the EHCI core clears the USBMODE register, which sets the core in + * an undefined state (neither host nor agent), so it needs to be set + * again for proper operation. + * + * Refer to errata document MV-S500832-00D.pdf (p. 5.24 GL USB-2) for + * details. + */ + sc->sc_flags |= EHCI_SCFLG_SETMODE; + if (bootverbose) + device_printf(self, "5.24 GL USB-2 workaround enabled\n"); + + /* XXX all MV chips need it? */ + sc->sc_flags |= EHCI_SCFLG_FORCESPEED | EHCI_SCFLG_NORESTERM; + + err = ehci_init(sc); + if (!err) { + err = device_probe_and_attach(sc->sc_bus.bdev); + } + if (err) { + device_printf(self, "USB init failed err=%d\n", err); + goto error; + } + return (0); + +error: + mv_ehci_detach(self); + return (ENXIO); +} + +static int +mv_ehci_detach(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + device_t bdev; + int err; + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(self, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(self); + + /* + * disable interrupts that might have been switched on in mv_ehci_attach + */ + if (sc->sc_io_res) { + EWRITE4(sc, USB_BRIDGE_INTR_MASK, 0); + } + if (sc->sc_irq_res && sc->sc_intr_hdl) { + /* + * only call ehci_detach() after ehci_init() + */ + ehci_detach(sc); + + err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); + + if (err) + /* XXX or should we panic? */ + device_printf(self, "Could not tear down irq, %d\n", + err); + sc->sc_intr_hdl = NULL; + } + if (irq_err && ih_err) { + err = bus_teardown_intr(self, irq_err, ih_err); + + if (err) + device_printf(self, "Could not tear down irq, %d\n", + err); + ih_err = NULL; + } + if (irq_err) { + bus_release_resource(self, SYS_RES_IRQ, 0, irq_err); + irq_err = NULL; + } + if (sc->sc_irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 1, sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(self, SYS_RES_MEMORY, 0, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc); + + return (0); +} + +static int +err_intr(void *arg) +{ + ehci_softc_t *sc = arg; + unsigned int cause; + + cause = EREAD4(sc, USB_BRIDGE_INTR_CAUSE); + if (cause) { + printf("USB error: "); + if (cause & MV_USB_ADDR_DECODE_ERR) { + uint32_t addr; + + addr = EREAD4(sc, USB_BRIDGE_ERR_ADDR); + printf("address decoding error (addr=%#x)\n", addr); + } + if (cause & MV_USB_HOST_UNDERFLOW) + printf("host underflow\n"); + if (cause & MV_USB_HOST_OVERFLOW) + printf("host overflow\n"); + if (cause & MV_USB_DEVICE_UNDERFLOW) + printf("device underflow\n"); + if (cause & ~(MV_USB_ADDR_DECODE_ERR | MV_USB_HOST_UNDERFLOW | + MV_USB_HOST_OVERFLOW | MV_USB_DEVICE_UNDERFLOW)) + printf("unknown cause (cause=%#x)\n", cause); + + EWRITE4(sc, USB_BRIDGE_INTR_CAUSE, 0); + } + return (FILTER_HANDLED); +} + +static device_method_t ehci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, mv_ehci_probe), + DEVMETHOD(device_attach, mv_ehci_attach), + DEVMETHOD(device_detach, mv_ehci_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD_END +}; + +static driver_t ehci_driver = { + "ehci", + ehci_methods, + sizeof(ehci_softc_t), +}; + +static devclass_t ehci_devclass; + +DRIVER_MODULE(ehci, simplebus, ehci_driver, ehci_devclass, 0, 0); +MODULE_DEPEND(ehci, usb, 1, 1, 1); diff --git a/sys/bus/u4b/controller/ehci_pci.c b/sys/bus/u4b/controller/ehci_pci.c new file mode 100644 index 0000000000..e293ec8d2c --- /dev/null +++ b/sys/bus/u4b/controller/ehci_pci.c @@ -0,0 +1,558 @@ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (augustss@carlstedt.se) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * USB Enhanced Host Controller Driver, a.k.a. USB 2.0 controller. + * + * The EHCI 1.0 spec can be found at + * http://developer.intel.com/technology/usb/download/ehci-r10.pdf + * and the USB 2.0 spec at + * http://www.usb.org/developers/docs/usb_20.zip + */ + +/* The low level controller code for EHCI has been split into + * PCI probes and EHCI specific code. This was done to facilitate the + * sharing of code between *BSD's + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "usb_if.h" + +#define PCI_EHCI_VENDORID_ACERLABS 0x10b9 +#define PCI_EHCI_VENDORID_AMD 0x1022 +#define PCI_EHCI_VENDORID_APPLE 0x106b +#define PCI_EHCI_VENDORID_ATI 0x1002 +#define PCI_EHCI_VENDORID_CMDTECH 0x1095 +#define PCI_EHCI_VENDORID_INTEL 0x8086 +#define PCI_EHCI_VENDORID_NEC 0x1033 +#define PCI_EHCI_VENDORID_OPTI 0x1045 +#define PCI_EHCI_VENDORID_PHILIPS 0x1131 +#define PCI_EHCI_VENDORID_SIS 0x1039 +#define PCI_EHCI_VENDORID_NVIDIA 0x12D2 +#define PCI_EHCI_VENDORID_NVIDIA2 0x10DE +#define PCI_EHCI_VENDORID_VIA 0x1106 + +static device_probe_t ehci_pci_probe; +static device_attach_t ehci_pci_attach; +static device_detach_t ehci_pci_detach; +static usb_take_controller_t ehci_pci_take_controller; + +static const char * +ehci_pci_match(device_t self) +{ + uint32_t device_id = pci_get_devid(self); + + switch (device_id) { + case 0x268c8086: + return ("Intel 63XXESB USB 2.0 controller"); + + case 0x523910b9: + return "ALi M5239 USB 2.0 controller"; + + case 0x10227463: + return "AMD 8111 USB 2.0 controller"; + + case 0x20951022: + return ("AMD CS5536 (Geode) USB 2.0 controller"); + + case 0x43451002: + return "ATI SB200 USB 2.0 controller"; + case 0x43731002: + return "ATI SB400 USB 2.0 controller"; + + case 0x25ad8086: + return "Intel 6300ESB USB 2.0 controller"; + case 0x24cd8086: + return "Intel 82801DB/L/M (ICH4) USB 2.0 controller"; + case 0x24dd8086: + return "Intel 82801EB/R (ICH5) USB 2.0 controller"; + case 0x265c8086: + return "Intel 82801FB (ICH6) USB 2.0 controller"; + case 0x27cc8086: + return "Intel 82801GB/R (ICH7) USB 2.0 controller"; + + case 0x28368086: + return "Intel 82801H (ICH8) USB 2.0 controller USB2-A"; + case 0x283a8086: + return "Intel 82801H (ICH8) USB 2.0 controller USB2-B"; + case 0x293a8086: + return "Intel 82801I (ICH9) USB 2.0 controller"; + case 0x293c8086: + return "Intel 82801I (ICH9) USB 2.0 controller"; + case 0x3a3a8086: + return "Intel 82801JI (ICH10) USB 2.0 controller USB-A"; + case 0x3a3c8086: + return "Intel 82801JI (ICH10) USB 2.0 controller USB-B"; + case 0x3b348086: + return ("Intel PCH USB 2.0 controller USB-A"); + case 0x3b3c8086: + return ("Intel PCH USB 2.0 controller USB-B"); + + case 0x00e01033: + return ("NEC uPD 720100 USB 2.0 controller"); + + case 0x006810de: + return "NVIDIA nForce2 USB 2.0 controller"; + case 0x008810de: + return "NVIDIA nForce2 Ultra 400 USB 2.0 controller"; + case 0x00d810de: + return "NVIDIA nForce3 USB 2.0 controller"; + case 0x00e810de: + return "NVIDIA nForce3 250 USB 2.0 controller"; + case 0x005b10de: + return "NVIDIA nForce4 USB 2.0 controller"; + case 0x036d10de: + return "NVIDIA nForce MCP55 USB 2.0 controller"; + case 0x03f210de: + return "NVIDIA nForce MCP61 USB 2.0 controller"; + case 0x0aa610de: + return "NVIDIA nForce MCP79 USB 2.0 controller"; + case 0x0aa910de: + return "NVIDIA nForce MCP79 USB 2.0 controller"; + case 0x0aaa10de: + return "NVIDIA nForce MCP79 USB 2.0 controller"; + + case 0x15621131: + return "Philips ISP156x USB 2.0 controller"; + + case 0x31041106: + return ("VIA VT6202 USB 2.0 controller"); + + default: + break; + } + + if ((pci_get_class(self) == PCIC_SERIALBUS) + && (pci_get_subclass(self) == PCIS_SERIALBUS_USB) + && (pci_get_progif(self) == PCI_INTERFACE_EHCI)) { + return ("EHCI (generic) USB 2.0 controller"); + } + return (NULL); /* dunno */ +} + +static int +ehci_pci_probe(device_t self) +{ + const char *desc = ehci_pci_match(self); + + if (desc) { + device_set_desc(self, desc); + return (0); + } else { + return (ENXIO); + } +} + +static void +ehci_pci_ati_quirk(device_t self, uint8_t is_sb700) +{ + device_t smbdev; + uint32_t val; + + if (is_sb700) { + /* Lookup SMBUS PCI device */ + smbdev = pci_find_device(PCI_EHCI_VENDORID_ATI, 0x4385); + if (smbdev == NULL) + return; + val = pci_get_revid(smbdev); + if (val != 0x3a && val != 0x3b) + return; + } + + /* + * Note: this bit is described as reserved in SB700 + * Register Reference Guide. + */ + val = pci_read_config(self, 0x53, 1); + if (!(val & 0x8)) { + val |= 0x8; + pci_write_config(self, 0x53, val, 1); + device_printf(self, "AMD SB600/700 quirk applied\n"); + } +} + +static void +ehci_pci_via_quirk(device_t self) +{ + uint32_t val; + + if ((pci_get_device(self) == 0x3104) && + ((pci_get_revid(self) & 0xf0) == 0x60)) { + /* Correct schedule sleep time to 10us */ + val = pci_read_config(self, 0x4b, 1); + if (val & 0x20) + return; + pci_write_config(self, 0x4b, val, 1); + device_printf(self, "VIA-quirk applied\n"); + } +} + +static int +ehci_pci_attach(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + int err; + int rid; + + /* initialise some bus fields */ + sc->sc_bus.parent = self; + sc->sc_bus.devices = sc->sc_devices; + sc->sc_bus.devices_max = EHCI_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_bus, + USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) { + return (ENOMEM); + } + + pci_enable_busmaster(self); + + switch (pci_read_config(self, PCI_USBREV, 1) & PCI_USB_REV_MASK) { + case PCI_USB_REV_PRE_1_0: + case PCI_USB_REV_1_0: + case PCI_USB_REV_1_1: + /* + * NOTE: some EHCI USB controllers have the wrong USB + * revision number. It appears those controllers are + * fully compliant so we just ignore this value in + * some common cases. + */ + device_printf(self, "pre-2.0 USB revision (ignored)\n"); + /* fallthrough */ + case PCI_USB_REV_2_0: + break; + default: + /* Quirk for Parallels Desktop 4.0 */ + device_printf(self, "USB revision is unknown. Assuming v2.0.\n"); + break; + } + + rid = PCI_CBMEM; + sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (!sc->sc_io_res) { + device_printf(self, "Could not map memory\n"); + goto error; + } + sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res); + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + goto error; + } + sc->sc_bus.bdev = device_add_child(self, "usbus", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + /* + * ehci_pci_match will never return NULL if ehci_pci_probe + * succeeded + */ + device_set_desc(sc->sc_bus.bdev, ehci_pci_match(self)); + switch (pci_get_vendor(self)) { + case PCI_EHCI_VENDORID_ACERLABS: + sprintf(sc->sc_vendor, "AcerLabs"); + break; + case PCI_EHCI_VENDORID_AMD: + sprintf(sc->sc_vendor, "AMD"); + break; + case PCI_EHCI_VENDORID_APPLE: + sprintf(sc->sc_vendor, "Apple"); + break; + case PCI_EHCI_VENDORID_ATI: + sprintf(sc->sc_vendor, "ATI"); + break; + case PCI_EHCI_VENDORID_CMDTECH: + sprintf(sc->sc_vendor, "CMDTECH"); + break; + case PCI_EHCI_VENDORID_INTEL: + sprintf(sc->sc_vendor, "Intel"); + break; + case PCI_EHCI_VENDORID_NEC: + sprintf(sc->sc_vendor, "NEC"); + break; + case PCI_EHCI_VENDORID_OPTI: + sprintf(sc->sc_vendor, "OPTi"); + break; + case PCI_EHCI_VENDORID_PHILIPS: + sprintf(sc->sc_vendor, "Philips"); + break; + case PCI_EHCI_VENDORID_SIS: + sprintf(sc->sc_vendor, "SiS"); + break; + case PCI_EHCI_VENDORID_NVIDIA: + case PCI_EHCI_VENDORID_NVIDIA2: + sprintf(sc->sc_vendor, "nVidia"); + break; + case PCI_EHCI_VENDORID_VIA: + sprintf(sc->sc_vendor, "VIA"); + break; + default: + if (bootverbose) + device_printf(self, "(New EHCI DeviceId=0x%08x)\n", + pci_get_devid(self)); + sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); + } + +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); +#else + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); +#endif + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->sc_intr_hdl = NULL; + goto error; + } + ehci_pci_take_controller(self); + + /* Undocumented quirks taken from Linux */ + + switch (pci_get_vendor(self)) { + case PCI_EHCI_VENDORID_ATI: + /* SB600 and SB700 EHCI quirk */ + switch (pci_get_device(self)) { + case 0x4386: + ehci_pci_ati_quirk(self, 0); + break; + case 0x4396: + ehci_pci_ati_quirk(self, 1); + break; + default: + break; + } + break; + + case PCI_EHCI_VENDORID_VIA: + ehci_pci_via_quirk(self); + break; + + default: + break; + } + + /* Dropped interrupts workaround */ + switch (pci_get_vendor(self)) { + case PCI_EHCI_VENDORID_ATI: + case PCI_EHCI_VENDORID_VIA: + sc->sc_flags |= EHCI_SCFLG_LOSTINTRBUG; + if (bootverbose) + device_printf(self, + "Dropped interrupts workaround enabled\n"); + break; + default: + break; + } + + /* Doorbell feature workaround */ + switch (pci_get_vendor(self)) { + case PCI_EHCI_VENDORID_NVIDIA: + case PCI_EHCI_VENDORID_NVIDIA2: + sc->sc_flags |= EHCI_SCFLG_IAADBUG; + if (bootverbose) + device_printf(self, + "Doorbell workaround enabled\n"); + break; + default: + break; + } + + err = ehci_init(sc); + if (!err) { + err = device_probe_and_attach(sc->sc_bus.bdev); + } + if (err) { + device_printf(self, "USB init failed err=%d\n", err); + goto error; + } + return (0); + +error: + ehci_pci_detach(self); + return (ENXIO); +} + +static int +ehci_pci_detach(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + device_t bdev; + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(self, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(self); + + pci_disable_busmaster(self); + + if (sc->sc_irq_res && sc->sc_intr_hdl) { + /* + * only call ehci_detach() after ehci_init() + */ + ehci_detach(sc); + + int err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); + + if (err) + /* XXX or should we panic? */ + device_printf(self, "Could not tear down irq, %d\n", + err); + sc->sc_intr_hdl = NULL; + } + if (sc->sc_irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(self, SYS_RES_MEMORY, PCI_CBMEM, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc); + + return (0); +} + +static int +ehci_pci_take_controller(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + uint32_t cparams; + uint32_t eec; + uint16_t to; + uint8_t eecp; + uint8_t bios_sem; + + cparams = EREAD4(sc, EHCI_HCCPARAMS); + + /* Synchronise with the BIOS if it owns the controller. */ + for (eecp = EHCI_HCC_EECP(cparams); eecp != 0; + eecp = EHCI_EECP_NEXT(eec)) { + eec = pci_read_config(self, eecp, 4); + if (EHCI_EECP_ID(eec) != EHCI_EC_LEGSUP) { + continue; + } + bios_sem = pci_read_config(self, eecp + + EHCI_LEGSUP_BIOS_SEM, 1); + if (bios_sem == 0) { + continue; + } + device_printf(sc->sc_bus.bdev, "waiting for BIOS " + "to give up control\n"); + pci_write_config(self, eecp + + EHCI_LEGSUP_OS_SEM, 1, 1); + to = 500; + while (1) { + bios_sem = pci_read_config(self, eecp + + EHCI_LEGSUP_BIOS_SEM, 1); + if (bios_sem == 0) + break; + + if (--to == 0) { + device_printf(sc->sc_bus.bdev, + "timed out waiting for BIOS\n"); + break; + } + usb_pause_mtx(NULL, hz / 100); /* wait 10ms */ + } + } + return (0); +} + +static device_method_t ehci_pci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ehci_pci_probe), + DEVMETHOD(device_attach, ehci_pci_attach), + DEVMETHOD(device_detach, ehci_pci_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(usb_take_controller, ehci_pci_take_controller), + + DEVMETHOD_END +}; + +static driver_t ehci_driver = { + .name = "ehci", + .methods = ehci_pci_methods, + .size = sizeof(struct ehci_softc), +}; + +static devclass_t ehci_devclass; + +DRIVER_MODULE(ehci, pci, ehci_driver, ehci_devclass, 0, 0); +MODULE_DEPEND(ehci, usb, 1, 1, 1); diff --git a/sys/bus/u4b/controller/ehcireg.h b/sys/bus/u4b/controller/ehcireg.h new file mode 100644 index 0000000000..1f5fc5c06c --- /dev/null +++ b/sys/bus/u4b/controller/ehcireg.h @@ -0,0 +1,171 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _EHCIREG_H_ +#define _EHCIREG_H_ + +/* PCI config registers */ +#define PCI_CBMEM 0x10 /* configuration base MEM */ +#define PCI_INTERFACE_EHCI 0x20 +#define PCI_USBREV 0x60 /* RO USB protocol revision */ +#define PCI_USB_REV_MASK 0xff +#define PCI_USB_REV_PRE_1_0 0x00 +#define PCI_USB_REV_1_0 0x10 +#define PCI_USB_REV_1_1 0x11 +#define PCI_USB_REV_2_0 0x20 +#define PCI_EHCI_FLADJ 0x61 /* RW Frame len adj, SOF=59488+6*fladj */ +#define PCI_EHCI_PORTWAKECAP 0x62 /* RW Port wake caps (opt) */ + +/* EHCI Extended Capabilities */ +#define EHCI_EC_LEGSUP 0x01 +#define EHCI_EECP_NEXT(x) (((x) >> 8) & 0xff) +#define EHCI_EECP_ID(x) ((x) & 0xff) + +/* Legacy support extended capability */ +#define EHCI_LEGSUP_BIOS_SEM 0x02 +#define EHCI_LEGSUP_OS_SEM 0x03 +#define EHCI_LEGSUP_USBLEGCTLSTS 0x04 + +/* EHCI capability registers */ +#define EHCI_CAPLEN_HCIVERSION 0x00 /* RO Capability register length + * (least-significant byte) and + * interface version number (two + * most significant) + */ +#define EHCI_CAPLENGTH(x) ((x) & 0xff) +#define EHCI_HCIVERSION(x) (((x) >> 16) & 0xffff) +#define EHCI_HCSPARAMS 0x04 /* RO Structural parameters */ +#define EHCI_HCS_DEBUGPORT(x) (((x) >> 20) & 0xf) +#define EHCI_HCS_P_INDICATOR(x) ((x) & 0x10000) +#define EHCI_HCS_N_CC(x) (((x) >> 12) & 0xf) /* # of companion ctlrs */ +#define EHCI_HCS_N_PCC(x) (((x) >> 8) & 0xf) /* # of ports per comp. */ +#define EHCI_HCS_PPC(x) ((x) & 0x10) /* port power control */ +#define EHCI_HCS_N_PORTS(x) ((x) & 0xf) /* # of ports */ +#define EHCI_HCCPARAMS 0x08 /* RO Capability parameters */ +#define EHCI_HCC_EECP(x) (((x) >> 8) & 0xff) /* extended ports caps */ +#define EHCI_HCC_IST(x) (((x) >> 4) & 0xf) /* isoc sched threshold */ +#define EHCI_HCC_ASPC(x) ((x) & 0x4) /* async sched park cap */ +#define EHCI_HCC_PFLF(x) ((x) & 0x2) /* prog frame list flag */ +#define EHCI_HCC_64BIT(x) ((x) & 0x1) /* 64 bit address cap */ +#define EHCI_HCSP_PORTROUTE 0x0c /* RO Companion port route description */ + +/* EHCI operational registers. Offset given by EHCI_CAPLENGTH register */ +#define EHCI_USBCMD 0x00 /* RO, RW, WO Command register */ +#define EHCI_CMD_ITC_M 0x00ff0000 /* RW interrupt threshold ctrl */ +#define EHCI_CMD_ITC_1 0x00010000 +#define EHCI_CMD_ITC_2 0x00020000 +#define EHCI_CMD_ITC_4 0x00040000 +#define EHCI_CMD_ITC_8 0x00080000 +#define EHCI_CMD_ITC_16 0x00100000 +#define EHCI_CMD_ITC_32 0x00200000 +#define EHCI_CMD_ITC_64 0x00400000 +#define EHCI_CMD_ASPME 0x00000800 /* RW/RO async park enable */ +#define EHCI_CMD_ASPMC 0x00000300 /* RW/RO async park count */ +#define EHCI_CMD_LHCR 0x00000080 /* RW light host ctrl reset */ +#define EHCI_CMD_IAAD 0x00000040 /* RW intr on async adv door + * bell */ +#define EHCI_CMD_ASE 0x00000020 /* RW async sched enable */ +#define EHCI_CMD_PSE 0x00000010 /* RW periodic sched enable */ +#define EHCI_CMD_FLS_M 0x0000000c /* RW/RO frame list size */ +#define EHCI_CMD_FLS(x) (((x) >> 2) & 3) /* RW/RO frame list size */ +#define EHCI_CMD_HCRESET 0x00000002 /* RW reset */ +#define EHCI_CMD_RS 0x00000001 /* RW run/stop */ +#define EHCI_USBSTS 0x04 /* RO, RW, RWC Status register */ +#define EHCI_STS_ASS 0x00008000 /* RO async sched status */ +#define EHCI_STS_PSS 0x00004000 /* RO periodic sched status */ +#define EHCI_STS_REC 0x00002000 /* RO reclamation */ +#define EHCI_STS_HCH 0x00001000 /* RO host controller halted */ +#define EHCI_STS_IAA 0x00000020 /* RWC interrupt on async adv */ +#define EHCI_STS_HSE 0x00000010 /* RWC host system error */ +#define EHCI_STS_FLR 0x00000008 /* RWC frame list rollover */ +#define EHCI_STS_PCD 0x00000004 /* RWC port change detect */ +#define EHCI_STS_ERRINT 0x00000002 /* RWC error interrupt */ +#define EHCI_STS_INT 0x00000001 /* RWC interrupt */ +#define EHCI_STS_INTRS(x) ((x) & 0x3f) + +/* + * NOTE: the doorbell interrupt is enabled, but the doorbell is never + * used! SiS chipsets require this. + */ +#define EHCI_NORMAL_INTRS (EHCI_STS_IAA | EHCI_STS_HSE | \ + EHCI_STS_PCD | EHCI_STS_ERRINT | EHCI_STS_INT) + +#define EHCI_USBINTR 0x08 /* RW Interrupt register */ +#define EHCI_INTR_IAAE 0x00000020 /* interrupt on async advance + * ena */ +#define EHCI_INTR_HSEE 0x00000010 /* host system error ena */ +#define EHCI_INTR_FLRE 0x00000008 /* frame list rollover ena */ +#define EHCI_INTR_PCIE 0x00000004 /* port change ena */ +#define EHCI_INTR_UEIE 0x00000002 /* USB error intr ena */ +#define EHCI_INTR_UIE 0x00000001 /* USB intr ena */ + +#define EHCI_FRINDEX 0x0c /* RW Frame Index register */ + +#define EHCI_CTRLDSSEGMENT 0x10 /* RW Control Data Structure Segment */ + +#define EHCI_PERIODICLISTBASE 0x14 /* RW Periodic List Base */ +#define EHCI_ASYNCLISTADDR 0x18 /* RW Async List Base */ + +#define EHCI_CONFIGFLAG 0x40 /* RW Configure Flag register */ +#define EHCI_CONF_CF 0x00000001 /* RW configure flag */ + +#define EHCI_PORTSC(n) (0x40+(4*(n))) /* RO, RW, RWC Port Status reg */ +#define EHCI_PS_WKOC_E 0x00400000 /* RW wake on over current ena */ +#define EHCI_PS_WKDSCNNT_E 0x00200000 /* RW wake on disconnect ena */ +#define EHCI_PS_WKCNNT_E 0x00100000 /* RW wake on connect ena */ +#define EHCI_PS_PTC 0x000f0000 /* RW port test control */ +#define EHCI_PS_PIC 0x0000c000 /* RW port indicator control */ +#define EHCI_PS_PO 0x00002000 /* RW port owner */ +#define EHCI_PS_PP 0x00001000 /* RW,RO port power */ +#define EHCI_PS_LS 0x00000c00 /* RO line status */ +#define EHCI_PS_IS_LOWSPEED(x) (((x) & EHCI_PS_LS) == 0x00000400) +#define EHCI_PS_PR 0x00000100 /* RW port reset */ +#define EHCI_PS_SUSP 0x00000080 /* RW suspend */ +#define EHCI_PS_FPR 0x00000040 /* RW force port resume */ +#define EHCI_PS_OCC 0x00000020 /* RWC over current change */ +#define EHCI_PS_OCA 0x00000010 /* RO over current active */ +#define EHCI_PS_PEC 0x00000008 /* RWC port enable change */ +#define EHCI_PS_PE 0x00000004 /* RW port enable */ +#define EHCI_PS_CSC 0x00000002 /* RWC connect status change */ +#define EHCI_PS_CS 0x00000001 /* RO connect status */ +#define EHCI_PS_CLEAR (EHCI_PS_OCC | EHCI_PS_PEC | EHCI_PS_CSC) + +#define EHCI_USBMODE 0x68 /* RW USB Device mode register */ +#define EHCI_UM_CM 0x00000003 /* R/WO Controller Mode */ +#define EHCI_UM_CM_IDLE 0x0 /* Idle */ +#define EHCI_UM_CM_HOST 0x3 /* Host Controller */ +#define EHCI_UM_ES 0x00000004 /* R/WO Endian Select */ +#define EHCI_UM_ES_LE 0x0 /* Little-endian byte alignment */ +#define EHCI_UM_ES_BE 0x4 /* Big-endian byte alignment */ +#define EHCI_UM_SDIS 0x00000010 /* R/WO Stream Disable Mode */ + +#define EHCI_PORT_RESET_COMPLETE 2 /* ms */ + +#endif /* _EHCIREG_H_ */ diff --git a/sys/bus/u4b/controller/musb_otg.c b/sys/bus/u4b/controller/musb_otg.c new file mode 100644 index 0000000000..d463052d0e --- /dev/null +++ b/sys/bus/u4b/controller/musb_otg.c @@ -0,0 +1,2810 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Thanks to Mentor Graphics for providing a reference driver for this USB chip + * at their homepage. + */ + +/* + * This file contains the driver for the Mentor Graphics Inventra USB + * 2.0 High Speed Dual-Role controller. + * + * NOTE: The current implementation only supports Device Side Mode! + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define USB_DEBUG_VAR musbotgdebug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define MUSBOTG_INTR_ENDPT 1 + +#define MUSBOTG_BUS2SC(bus) \ + ((struct musbotg_softc *)(((uint8_t *)(bus)) - \ + USB_P2U(&(((struct musbotg_softc *)0)->sc_bus)))) + +#define MUSBOTG_PC2SC(pc) \ + MUSBOTG_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus) + +#ifdef USB_DEBUG +static int musbotgdebug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, musbotg, CTLFLAG_RW, 0, "USB musbotg"); +SYSCTL_INT(_hw_usb_musbotg, OID_AUTO, debug, CTLFLAG_RW, + &musbotgdebug, 0, "Debug level"); +#endif + +/* prototypes */ + +struct usb_bus_methods musbotg_bus_methods; +struct usb_pipe_methods musbotg_device_bulk_methods; +struct usb_pipe_methods musbotg_device_ctrl_methods; +struct usb_pipe_methods musbotg_device_intr_methods; +struct usb_pipe_methods musbotg_device_isoc_methods; + +static musbotg_cmd_t musbotg_setup_rx; +static musbotg_cmd_t musbotg_setup_data_rx; +static musbotg_cmd_t musbotg_setup_data_tx; +static musbotg_cmd_t musbotg_setup_status; +static musbotg_cmd_t musbotg_data_rx; +static musbotg_cmd_t musbotg_data_tx; +static void musbotg_device_done(struct usb_xfer *, usb_error_t); +static void musbotg_do_poll(struct usb_bus *); +static void musbotg_standard_done(struct usb_xfer *); +static void musbotg_interrupt_poll(struct musbotg_softc *); +static void musbotg_root_intr(struct musbotg_softc *); + +/* + * Here is a configuration that the chip supports. + */ +static const struct usb_hw_ep_profile musbotg_ep_profile[1] = { + + [0] = { + .max_in_frame_size = 64,/* fixed */ + .max_out_frame_size = 64, /* fixed */ + .is_simplex = 1, + .support_control = 1, + } +}; + +static void +musbotg_get_hw_ep_profile(struct usb_device *udev, + const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) +{ + struct musbotg_softc *sc; + + sc = MUSBOTG_BUS2SC(udev->bus); + + if (ep_addr == 0) { + /* control endpoint */ + *ppf = musbotg_ep_profile; + } else if (ep_addr <= sc->sc_ep_max) { + /* other endpoints */ + *ppf = sc->sc_hw_ep_profile + ep_addr; + } else { + *ppf = NULL; + } +} + +static void +musbotg_clocks_on(struct musbotg_softc *sc) +{ + if (sc->sc_flags.clocks_off && + sc->sc_flags.port_powered) { + + DPRINTFN(4, "\n"); + + if (sc->sc_clocks_on) { + (sc->sc_clocks_on) (sc->sc_clocks_arg); + } + sc->sc_flags.clocks_off = 0; + + /* XXX enable Transceiver */ + } +} + +static void +musbotg_clocks_off(struct musbotg_softc *sc) +{ + if (!sc->sc_flags.clocks_off) { + + DPRINTFN(4, "\n"); + + /* XXX disable Transceiver */ + + if (sc->sc_clocks_off) { + (sc->sc_clocks_off) (sc->sc_clocks_arg); + } + sc->sc_flags.clocks_off = 1; + } +} + +static void +musbotg_pull_common(struct musbotg_softc *sc, uint8_t on) +{ + uint8_t temp; + + temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); + if (on) + temp |= MUSB2_MASK_SOFTC; + else + temp &= ~MUSB2_MASK_SOFTC; + + MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); +} + +static void +musbotg_pull_up(struct musbotg_softc *sc) +{ + /* pullup D+, if possible */ + + if (!sc->sc_flags.d_pulled_up && + sc->sc_flags.port_powered) { + sc->sc_flags.d_pulled_up = 1; + musbotg_pull_common(sc, 1); + } +} + +static void +musbotg_pull_down(struct musbotg_softc *sc) +{ + /* pulldown D+, if possible */ + + if (sc->sc_flags.d_pulled_up) { + sc->sc_flags.d_pulled_up = 0; + musbotg_pull_common(sc, 0); + } +} + +static void +musbotg_wakeup_peer(struct musbotg_softc *sc) +{ + uint8_t temp; + + if (!(sc->sc_flags.status_suspend)) { + return; + } + + temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); + temp |= MUSB2_MASK_RESUME; + MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); + + /* wait 8 milliseconds */ + /* Wait for reset to complete. */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125); + + temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); + temp &= ~MUSB2_MASK_RESUME; + MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp); +} + +static void +musbotg_set_address(struct musbotg_softc *sc, uint8_t addr) +{ + DPRINTFN(4, "addr=%d\n", addr); + addr &= 0x7F; + MUSB2_WRITE_1(sc, MUSB2_REG_FADDR, addr); +} + +static uint8_t +musbotg_setup_rx(struct musbotg_td *td) +{ + struct musbotg_softc *sc; + struct usb_device_request req; + uint16_t count; + uint8_t csr; + + /* get pointer to softc */ + sc = MUSBOTG_PC2SC(td->pc); + + /* select endpoint 0 */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); + + /* read out FIFO status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + + DPRINTFN(4, "csr=0x%02x\n", csr); + + /* + * NOTE: If DATAEND is set we should not call the + * callback, hence the status stage is not complete. + */ + if (csr & MUSB2_MASK_CSR0L_DATAEND) { + /* do not stall at this point */ + td->did_stall = 1; + /* wait for interrupt */ + goto not_complete; + } + if (csr & MUSB2_MASK_CSR0L_SENTSTALL) { + /* clear SENTSTALL */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); + /* get latest status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + /* update EP0 state */ + sc->sc_ep0_busy = 0; + } + if (csr & MUSB2_MASK_CSR0L_SETUPEND) { + /* clear SETUPEND */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSR0L_SETUPEND_CLR); + /* get latest status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + /* update EP0 state */ + sc->sc_ep0_busy = 0; + } + if (sc->sc_ep0_busy) { + goto not_complete; + } + if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) { + goto not_complete; + } + /* clear did stall flag */ + td->did_stall = 0; + /* get the packet byte count */ + count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); + + /* verify data length */ + if (count != td->remainder) { + DPRINTFN(0, "Invalid SETUP packet " + "length, %d bytes\n", count); + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSR0L_RXPKTRDY_CLR); + goto not_complete; + } + if (count != sizeof(req)) { + DPRINTFN(0, "Unsupported SETUP packet " + "length, %d bytes\n", count); + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSR0L_RXPKTRDY_CLR); + goto not_complete; + } + /* receive data */ + bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), (void *)&req, sizeof(req)); + + /* copy data into real buffer */ + usbd_copy_in(td->pc, 0, &req, sizeof(req)); + + td->offset = sizeof(req); + td->remainder = 0; + + /* set pending command */ + sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR; + + /* we need set stall or dataend after this */ + sc->sc_ep0_busy = 1; + + /* sneak peek the set address */ + if ((req.bmRequestType == UT_WRITE_DEVICE) && + (req.bRequest == UR_SET_ADDRESS)) { + sc->sc_dv_addr = req.wValue[0] & 0x7F; + } else { + sc->sc_dv_addr = 0xFF; + } + return (0); /* complete */ + +not_complete: + /* abort any ongoing transfer */ + if (!td->did_stall) { + DPRINTFN(4, "stalling\n"); + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSR0L_SENDSTALL); + td->did_stall = 1; + } + return (1); /* not complete */ +} + +/* Control endpoint only data handling functions (RX/TX/SYNC) */ + +static uint8_t +musbotg_setup_data_rx(struct musbotg_td *td) +{ + struct usb_page_search buf_res; + struct musbotg_softc *sc; + uint16_t count; + uint8_t csr; + uint8_t got_short; + + /* get pointer to softc */ + sc = MUSBOTG_PC2SC(td->pc); + + /* select endpoint 0 */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); + + /* check if a command is pending */ + if (sc->sc_ep0_cmd) { + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd); + sc->sc_ep0_cmd = 0; + } + /* read out FIFO status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + + DPRINTFN(4, "csr=0x%02x\n", csr); + + got_short = 0; + + if (csr & (MUSB2_MASK_CSR0L_SETUPEND | + MUSB2_MASK_CSR0L_SENTSTALL)) { + if (td->remainder == 0) { + /* + * We are actually complete and have + * received the next SETUP + */ + DPRINTFN(4, "faking complete\n"); + return (0); /* complete */ + } + /* + * USB Host Aborted the transfer. + */ + td->error = 1; + return (0); /* complete */ + } + if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) { + return (1); /* not complete */ + } + /* get the packet byte count */ + count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); + + /* verify the packet byte count */ + if (count != td->max_frame_size) { + if (count < td->max_frame_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + } + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + while (count > 0) { + uint32_t temp; + + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* check for unaligned memory address */ + if (USB_P2U(buf_res.buffer) & 3) { + + temp = count & ~3; + + if (temp) { + /* receive data 4 bytes at a time */ + bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf, + temp / 4); + } + temp = count & 3; + if (temp) { + /* receive data 1 byte at a time */ + bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), + (void *)(&sc->sc_bounce_buf[count / 4]), temp); + } + usbd_copy_in(td->pc, td->offset, + sc->sc_bounce_buf, count); + + /* update offset and remainder */ + td->offset += count; + td->remainder -= count; + break; + } + /* check if we can optimise */ + if (buf_res.length >= 4) { + + /* receive data 4 bytes at a time */ + bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), buf_res.buffer, + buf_res.length / 4); + + temp = buf_res.length & ~3; + + /* update counters */ + count -= temp; + td->offset += temp; + td->remainder -= temp; + continue; + } + /* receive data */ + bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) { + /* we are complete */ + sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR; + return (0); + } + /* else need to receive a zero length packet */ + } + /* write command - need more data */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSR0L_RXPKTRDY_CLR); + return (1); /* not complete */ +} + +static uint8_t +musbotg_setup_data_tx(struct musbotg_td *td) +{ + struct usb_page_search buf_res; + struct musbotg_softc *sc; + uint16_t count; + uint8_t csr; + + /* get pointer to softc */ + sc = MUSBOTG_PC2SC(td->pc); + + /* select endpoint 0 */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); + + /* check if a command is pending */ + if (sc->sc_ep0_cmd) { + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd); + sc->sc_ep0_cmd = 0; + } + /* read out FIFO status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + + DPRINTFN(4, "csr=0x%02x\n", csr); + + if (csr & (MUSB2_MASK_CSR0L_SETUPEND | + MUSB2_MASK_CSR0L_SENTSTALL)) { + /* + * The current transfer was aborted + * by the USB Host + */ + td->error = 1; + return (0); /* complete */ + } + if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) { + return (1); /* not complete */ + } + count = td->max_frame_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + while (count > 0) { + uint32_t temp; + + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* check for unaligned memory address */ + if (USB_P2U(buf_res.buffer) & 3) { + + usbd_copy_out(td->pc, td->offset, + sc->sc_bounce_buf, count); + + temp = count & ~3; + + if (temp) { + /* transmit data 4 bytes at a time */ + bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf, + temp / 4); + } + temp = count & 3; + if (temp) { + /* receive data 1 byte at a time */ + bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), + ((void *)&sc->sc_bounce_buf[count / 4]), temp); + } + /* update offset and remainder */ + td->offset += count; + td->remainder -= count; + break; + } + /* check if we can optimise */ + if (buf_res.length >= 4) { + + /* transmit data 4 bytes at a time */ + bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), buf_res.buffer, + buf_res.length / 4); + + temp = buf_res.length & ~3; + + /* update counters */ + count -= temp; + td->offset += temp; + td->remainder -= temp; + continue; + } + /* transmit data */ + bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) { + sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_TXPKTRDY; + return (0); /* complete */ + } + /* else we need to transmit a short packet */ + } + /* write command */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSR0L_TXPKTRDY); + + return (1); /* not complete */ +} + +static uint8_t +musbotg_setup_status(struct musbotg_td *td) +{ + struct musbotg_softc *sc; + uint8_t csr; + + /* get pointer to softc */ + sc = MUSBOTG_PC2SC(td->pc); + + /* select endpoint 0 */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); + + if (sc->sc_ep0_busy) { + sc->sc_ep0_busy = 0; + sc->sc_ep0_cmd |= MUSB2_MASK_CSR0L_DATAEND; + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd); + sc->sc_ep0_cmd = 0; + } + /* read out FIFO status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + + DPRINTFN(4, "csr=0x%02x\n", csr); + + if (csr & MUSB2_MASK_CSR0L_DATAEND) { + /* wait for interrupt */ + return (1); /* not complete */ + } + if (sc->sc_dv_addr != 0xFF) { + /* write function address */ + musbotg_set_address(sc, sc->sc_dv_addr); + } + return (0); /* complete */ +} + +static uint8_t +musbotg_data_rx(struct musbotg_td *td) +{ + struct usb_page_search buf_res; + struct musbotg_softc *sc; + uint16_t count; + uint8_t csr; + uint8_t to; + uint8_t got_short; + + to = 8; /* don't loop forever! */ + got_short = 0; + + /* get pointer to softc */ + sc = MUSBOTG_PC2SC(td->pc); + + /* select endpoint */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->ep_no); + +repeat: + /* read out FIFO status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); + + DPRINTFN(4, "csr=0x%02x\n", csr); + + /* clear overrun */ + if (csr & MUSB2_MASK_CSRL_RXOVERRUN) { + /* make sure we don't clear "RXPKTRDY" */ + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, + MUSB2_MASK_CSRL_RXPKTRDY); + } + /* check status */ + if (!(csr & MUSB2_MASK_CSRL_RXPKTRDY)) { + return (1); /* not complete */ + } + /* get the packet byte count */ + count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT); + + DPRINTFN(4, "count=0x%04x\n", count); + + /* + * Check for short or invalid packet: + */ + if (count != td->max_frame_size) { + if (count < td->max_frame_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + } + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + while (count > 0) { + uint32_t temp; + + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* check for unaligned memory address */ + if (USB_P2U(buf_res.buffer) & 3) { + + temp = count & ~3; + + if (temp) { + /* receive data 4 bytes at a time */ + bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(td->ep_no), sc->sc_bounce_buf, + temp / 4); + } + temp = count & 3; + if (temp) { + /* receive data 1 byte at a time */ + bus_space_read_multi_1(sc->sc_io_tag, + sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->ep_no), + ((void *)&sc->sc_bounce_buf[count / 4]), temp); + } + usbd_copy_in(td->pc, td->offset, + sc->sc_bounce_buf, count); + + /* update offset and remainder */ + td->offset += count; + td->remainder -= count; + break; + } + /* check if we can optimise */ + if (buf_res.length >= 4) { + + /* receive data 4 bytes at a time */ + bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer, + buf_res.length / 4); + + temp = buf_res.length & ~3; + + /* update counters */ + count -= temp; + td->offset += temp; + td->remainder -= temp; + continue; + } + /* receive data */ + bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer, + buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* clear status bits */ + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) { + /* we are complete */ + return (0); + } + /* else need to receive a zero length packet */ + } + if (--to) { + goto repeat; + } + return (1); /* not complete */ +} + +static uint8_t +musbotg_data_tx(struct musbotg_td *td) +{ + struct usb_page_search buf_res; + struct musbotg_softc *sc; + uint16_t count; + uint8_t csr; + uint8_t to; + + to = 8; /* don't loop forever! */ + + /* get pointer to softc */ + sc = MUSBOTG_PC2SC(td->pc); + + /* select endpoint */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->ep_no); + +repeat: + + /* read out FIFO status */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + + DPRINTFN(4, "csr=0x%02x\n", csr); + + if (csr & (MUSB2_MASK_CSRL_TXINCOMP | + MUSB2_MASK_CSRL_TXUNDERRUN)) { + /* clear status bits */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); + } + if (csr & MUSB2_MASK_CSRL_TXPKTRDY) { + return (1); /* not complete */ + } + /* check for short packet */ + count = td->max_frame_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + while (count > 0) { + uint32_t temp; + + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* check for unaligned memory address */ + if (USB_P2U(buf_res.buffer) & 3) { + + usbd_copy_out(td->pc, td->offset, + sc->sc_bounce_buf, count); + + temp = count & ~3; + + if (temp) { + /* transmit data 4 bytes at a time */ + bus_space_write_multi_4(sc->sc_io_tag, + sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->ep_no), + sc->sc_bounce_buf, temp / 4); + } + temp = count & 3; + if (temp) { + /* receive data 1 byte at a time */ + bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(td->ep_no), + ((void *)&sc->sc_bounce_buf[count / 4]), temp); + } + /* update offset and remainder */ + td->offset += count; + td->remainder -= count; + break; + } + /* check if we can optimise */ + if (buf_res.length >= 4) { + + /* transmit data 4 bytes at a time */ + bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer, + buf_res.length / 4); + + temp = buf_res.length & ~3; + + /* update counters */ + count -= temp; + td->offset += temp; + td->remainder -= temp; + continue; + } + /* transmit data */ + bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl, + MUSB2_REG_EPFIFO(td->ep_no), buf_res.buffer, + buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* write command */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSRL_TXPKTRDY); + + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) { + return (0); /* complete */ + } + /* else we need to transmit a short packet */ + } + if (--to) { + goto repeat; + } + return (1); /* not complete */ +} + +static uint8_t +musbotg_xfer_do_fifo(struct usb_xfer *xfer) +{ + struct musbotg_softc *sc; + struct musbotg_td *td; + + DPRINTFN(8, "\n"); + + td = xfer->td_transfer_cache; + while (1) { + if ((td->func) (td)) { + /* operation in progress */ + break; + } + if (((void *)td) == xfer->td_transfer_last) { + goto done; + } + if (td->error) { + goto done; + } else if (td->remainder > 0) { + /* + * We had a short transfer. If there is no alternate + * next, stop processing ! + */ + if (!td->alt_next) { + goto done; + } + } + /* + * Fetch the next transfer descriptor and transfer + * some flags to the next transfer descriptor + */ + td = td->obj_next; + xfer->td_transfer_cache = td; + } + return (1); /* not complete */ + +done: + sc = MUSBOTG_BUS2SC(xfer->xroot->bus); + + /* compute all actual lengths */ + + musbotg_standard_done(xfer); + + return (0); /* complete */ +} + +static void +musbotg_interrupt_poll(struct musbotg_softc *sc) +{ + struct usb_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + if (!musbotg_xfer_do_fifo(xfer)) { + /* queue has been modified */ + goto repeat; + } + } +} + +void +musbotg_vbus_interrupt(struct musbotg_softc *sc, uint8_t is_on) +{ + DPRINTFN(4, "vbus = %u\n", is_on); + + USB_BUS_LOCK(&sc->sc_bus); + if (is_on) { + if (!sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 1; + + /* complete root HUB interrupt endpoint */ + musbotg_root_intr(sc); + } + } else { + if (sc->sc_flags.status_vbus) { + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* complete root HUB interrupt endpoint */ + musbotg_root_intr(sc); + } + } + + USB_BUS_UNLOCK(&sc->sc_bus); +} + +void +musbotg_interrupt(struct musbotg_softc *sc) +{ + uint16_t rx_status; + uint16_t tx_status; + uint8_t usb_status; + uint8_t temp; + uint8_t to = 2; + + USB_BUS_LOCK(&sc->sc_bus); + +repeat: + + /* read all interrupt registers */ + usb_status = MUSB2_READ_1(sc, MUSB2_REG_INTUSB); + + /* read all FIFO interrupts */ + rx_status = MUSB2_READ_2(sc, MUSB2_REG_INTRX); + tx_status = MUSB2_READ_2(sc, MUSB2_REG_INTTX); + + /* check for any bus state change interrupts */ + + if (usb_status & (MUSB2_MASK_IRESET | + MUSB2_MASK_IRESUME | MUSB2_MASK_ISUSP)) { + + DPRINTFN(4, "real bus interrupt 0x%08x\n", usb_status); + + if (usb_status & MUSB2_MASK_IRESET) { + + /* set correct state */ + sc->sc_flags.status_bus_reset = 1; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* determine line speed */ + temp = MUSB2_READ_1(sc, MUSB2_REG_POWER); + if (temp & MUSB2_MASK_HSMODE) + sc->sc_flags.status_high_speed = 1; + else + sc->sc_flags.status_high_speed = 0; + + /* + * After reset all interrupts are on and we need to + * turn them off! + */ + temp = MUSB2_MASK_IRESET; + /* disable resume interrupt */ + temp &= ~MUSB2_MASK_IRESUME; + /* enable suspend interrupt */ + temp |= MUSB2_MASK_ISUSP; + MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp); + /* disable TX and RX interrupts */ + MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0); + MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0); + } + /* + * If RXRSM and RXSUSP is set at the same time we interpret + * that like RESUME. Resume is set when there is at least 3 + * milliseconds of inactivity on the USB BUS. + */ + if (usb_status & MUSB2_MASK_IRESUME) { + if (sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 1; + + temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE); + /* disable resume interrupt */ + temp &= ~MUSB2_MASK_IRESUME; + /* enable suspend interrupt */ + temp |= MUSB2_MASK_ISUSP; + MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp); + } + } else if (usb_status & MUSB2_MASK_ISUSP) { + if (!sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 1; + sc->sc_flags.change_suspend = 1; + + temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE); + /* disable suspend interrupt */ + temp &= ~MUSB2_MASK_ISUSP; + /* enable resume interrupt */ + temp |= MUSB2_MASK_IRESUME; + MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp); + } + } + /* complete root HUB interrupt endpoint */ + musbotg_root_intr(sc); + } + /* check for any endpoint interrupts */ + + if (rx_status || tx_status) { + DPRINTFN(4, "real endpoint interrupt " + "rx=0x%04x, tx=0x%04x\n", rx_status, tx_status); + } + /* poll one time regardless of FIFO status */ + + musbotg_interrupt_poll(sc); + + if (--to) + goto repeat; + + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +musbotg_setup_standard_chain_sub(struct musbotg_std_temp *temp) +{ + struct musbotg_td *td; + + /* get current Transfer Descriptor */ + td = temp->td_next; + temp->td = td; + + /* prepare for next TD */ + temp->td_next = td->obj_next; + + /* fill out the Transfer Descriptor */ + td->func = temp->func; + td->pc = temp->pc; + td->offset = temp->offset; + td->remainder = temp->len; + td->error = 0; + td->did_stall = temp->did_stall; + td->short_pkt = temp->short_pkt; + td->alt_next = temp->setup_alt_next; +} + +static void +musbotg_setup_standard_chain(struct usb_xfer *xfer) +{ + struct musbotg_std_temp temp; + struct musbotg_softc *sc; + struct musbotg_td *td; + uint32_t x; + uint8_t ep_no; + + DPRINTFN(8, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpointno), + xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); + + temp.max_frame_size = xfer->max_frame_size; + + td = xfer->td_start[0]; + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + /* setup temp */ + + temp.pc = NULL; + temp.td = NULL; + temp.td_next = xfer->td_start[0]; + temp.offset = 0; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.did_stall = !xfer->flags_int.control_stall; + + sc = MUSBOTG_BUS2SC(xfer->xroot->bus); + ep_no = (xfer->endpointno & UE_ADDR); + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.func = &musbotg_setup_rx; + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.short_pkt = temp.len ? 1 : 0; + + musbotg_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + if (x != xfer->nframes) { + if (xfer->endpointno & UE_DIR_IN) { + if (xfer->flags_int.control_xfr) + temp.func = &musbotg_setup_data_tx; + else + temp.func = &musbotg_data_tx; + } else { + if (xfer->flags_int.control_xfr) + temp.func = &musbotg_setup_data_rx; + else + temp.func = &musbotg_data_rx; + } + + /* setup "pc" pointer */ + temp.pc = xfer->frbuffers + x; + } + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + + x++; + + if (x == xfer->nframes) { + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_act) { + temp.setup_alt_next = 0; + } + } else { + temp.setup_alt_next = 0; + } + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.short_pkt = 0; + + } else { + + /* regular data transfer */ + + temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + musbotg_setup_standard_chain_sub(&temp); + + if (xfer->flags_int.isochronous_xfr) { + temp.offset += temp.len; + } else { + /* get next Page Cache pointer */ + temp.pc = xfer->frbuffers + x; + } + } + + /* check for control transfer */ + if (xfer->flags_int.control_xfr) { + + /* always setup a valid "pc" pointer for status and sync */ + temp.pc = xfer->frbuffers + 0; + temp.len = 0; + temp.short_pkt = 0; + temp.setup_alt_next = 0; + + /* check if we should append a status stage */ + if (!xfer->flags_int.control_act) { + /* + * Send a DATA1 message and invert the current + * endpoint direction. + */ + temp.func = &musbotg_setup_status; + musbotg_setup_standard_chain_sub(&temp); + } + } + /* must have at least one frame! */ + td = temp.td; + xfer->td_transfer_last = td; +} + +static void +musbotg_timeout(void *arg) +{ + struct usb_xfer *xfer = arg; + + DPRINTFN(1, "xfer=%p\n", xfer); + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* transfer is transferred */ + musbotg_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +musbotg_ep_int_set(struct usb_xfer *xfer, uint8_t on) +{ + struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus); + uint16_t temp; + uint8_t ep_no = xfer->endpointno & UE_ADDR; + + /* + * Only enable the endpoint interrupt when we are + * actually waiting for data, hence we are dealing + * with level triggered interrupts ! + */ + if (ep_no == 0) { + temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE); + if (on) + temp |= MUSB2_MASK_EPINT(0); + else + temp &= ~MUSB2_MASK_EPINT(0); + + MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp); + } else { + if (USB_GET_DATA_ISREAD(xfer)) { + temp = MUSB2_READ_2(sc, MUSB2_REG_INTRXE); + if (on) + temp |= MUSB2_MASK_EPINT(ep_no); + else + temp &= ~MUSB2_MASK_EPINT(ep_no); + MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, temp); + + } else { + temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE); + if (on) + temp |= MUSB2_MASK_EPINT(ep_no); + else + temp &= ~MUSB2_MASK_EPINT(ep_no); + MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp); + } + } +} + +static void +musbotg_start_standard_chain(struct usb_xfer *xfer) +{ + DPRINTFN(8, "\n"); + + /* poll one time */ + if (musbotg_xfer_do_fifo(xfer)) { + + musbotg_ep_int_set(xfer, 1); + + DPRINTFN(14, "enabled interrupts on endpoint\n"); + + /* put transfer on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usbd_transfer_timeout_ms(xfer, + &musbotg_timeout, xfer->timeout); + } + } +} + +static void +musbotg_root_intr(struct musbotg_softc *sc) +{ + DPRINTFN(8, "\n"); + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* set port bit */ + sc->sc_hub_idata[0] = 0x02; /* we only have one port */ + + uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, + sizeof(sc->sc_hub_idata)); +} + +static usb_error_t +musbotg_standard_done_sub(struct usb_xfer *xfer) +{ + struct musbotg_td *td; + uint32_t len; + uint8_t error; + + DPRINTFN(8, "\n"); + + td = xfer->td_transfer_cache; + + do { + len = td->remainder; + + if (xfer->aframes != xfer->nframes) { + /* + * Verify the length and subtract + * the remainder from "frlengths[]": + */ + if (len > xfer->frlengths[xfer->aframes]) { + td->error = 1; + } else { + xfer->frlengths[xfer->aframes] -= len; + } + } + /* Check for transfer error */ + if (td->error) { + /* the transfer is finished */ + error = 1; + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + td = td->obj_next; + } else { + td = NULL; + } + } else { + /* the transfer is finished */ + td = NULL; + } + error = 0; + break; + } + td = td->obj_next; + + /* this USB frame is complete */ + error = 0; + break; + + } while (0); + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + return (error ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); +} + +static void +musbotg_standard_done(struct usb_xfer *xfer) +{ + usb_error_t err = 0; + + DPRINTFN(12, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = musbotg_standard_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = musbotg_standard_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = musbotg_standard_done_sub(xfer); + } +done: + musbotg_device_done(xfer, err); +} + +/*------------------------------------------------------------------------* + * musbotg_device_done + * + * NOTE: this function can be called more than one time on the + * same USB transfer! + *------------------------------------------------------------------------*/ +static void +musbotg_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); + + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + + musbotg_ep_int_set(xfer, 0); + + DPRINTFN(14, "disabled interrupts on endpoint\n"); + } + /* dequeue transfer and start next transfer */ + usbd_transfer_done(xfer, error); +} + +static void +musbotg_set_stall(struct usb_device *udev, struct usb_xfer *xfer, + struct usb_endpoint *ep, uint8_t *did_stall) +{ + struct musbotg_softc *sc; + uint8_t ep_no; + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + DPRINTFN(4, "endpoint=%p\n", ep); + + if (xfer) { + /* cancel any ongoing transfers */ + musbotg_device_done(xfer, USB_ERR_STALLED); + } + /* set FORCESTALL */ + sc = MUSBOTG_BUS2SC(udev->bus); + + ep_no = (ep->edesc->bEndpointAddress & UE_ADDR); + + /* select endpoint */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no); + + if (ep->edesc->bEndpointAddress & UE_DIR_IN) { + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSRL_TXSENDSTALL); + } else { + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, + MUSB2_MASK_CSRL_RXSENDSTALL); + } +} + +static void +musbotg_clear_stall_sub(struct musbotg_softc *sc, uint16_t wMaxPacket, + uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) +{ + uint16_t mps; + uint16_t temp; + uint8_t csr; + + if (ep_type == UE_CONTROL) { + /* clearing stall is not needed */ + return; + } + /* select endpoint */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no); + + /* compute max frame size */ + mps = wMaxPacket & 0x7FF; + switch ((wMaxPacket >> 11) & 3) { + case 1: + mps *= 2; + break; + case 2: + mps *= 3; + break; + default: + break; + } + + if (ep_dir == UE_DIR_IN) { + + temp = 0; + + /* Configure endpoint */ + switch (ep_type) { + case UE_INTERRUPT: + MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, wMaxPacket); + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, + MUSB2_MASK_CSRH_TXMODE | temp); + break; + case UE_ISOCHRONOUS: + MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, wMaxPacket); + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, + MUSB2_MASK_CSRH_TXMODE | + MUSB2_MASK_CSRH_TXISO | temp); + break; + case UE_BULK: + MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, wMaxPacket); + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, + MUSB2_MASK_CSRH_TXMODE | temp); + break; + default: + break; + } + + /* Need to flush twice in case of double bufring */ + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) { + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSRL_TXFFLUSH); + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) { + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSRL_TXFFLUSH); + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + } + } + /* reset data toggle */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, + MUSB2_MASK_CSRL_TXDT_CLR); + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + + /* set double/single buffering */ + temp = MUSB2_READ_2(sc, MUSB2_REG_TXDBDIS); + if (mps <= (sc->sc_hw_ep_profile[ep_no]. + max_in_frame_size / 2)) { + /* double buffer */ + temp &= ~(1 << ep_no); + } else { + /* single buffer */ + temp |= (1 << ep_no); + } + MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, temp); + + /* clear sent stall */ + if (csr & MUSB2_MASK_CSRL_TXSENTSTALL) { + MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0); + csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL); + } + } else { + + temp = 0; + + /* Configure endpoint */ + switch (ep_type) { + case UE_INTERRUPT: + MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, wMaxPacket); + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, + MUSB2_MASK_CSRH_RXNYET | temp); + break; + case UE_ISOCHRONOUS: + MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, wMaxPacket); + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, + MUSB2_MASK_CSRH_RXNYET | + MUSB2_MASK_CSRH_RXISO | temp); + break; + case UE_BULK: + MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, wMaxPacket); + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, temp); + break; + default: + break; + } + + /* Need to flush twice in case of double bufring */ + csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); + if (csr & MUSB2_MASK_CSRL_RXPKTRDY) { + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, + MUSB2_MASK_CSRL_RXFFLUSH); + csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); + if (csr & MUSB2_MASK_CSRL_RXPKTRDY) { + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, + MUSB2_MASK_CSRL_RXFFLUSH); + csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); + } + } + /* reset data toggle */ + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, + MUSB2_MASK_CSRL_RXDT_CLR); + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); + csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL); + + /* set double/single buffering */ + temp = MUSB2_READ_2(sc, MUSB2_REG_RXDBDIS); + if (mps <= (sc->sc_hw_ep_profile[ep_no]. + max_out_frame_size / 2)) { + /* double buffer */ + temp &= ~(1 << ep_no); + } else { + /* single buffer */ + temp |= (1 << ep_no); + } + MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, temp); + + /* clear sent stall */ + if (csr & MUSB2_MASK_CSRL_RXSENTSTALL) { + MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0); + } + } +} + +static void +musbotg_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) +{ + struct musbotg_softc *sc; + struct usb_endpoint_descriptor *ed; + + DPRINTFN(4, "endpoint=%p\n", ep); + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + /* check mode */ + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + /* get softc */ + sc = MUSBOTG_BUS2SC(udev->bus); + + /* get endpoint descriptor */ + ed = ep->edesc; + + /* reset endpoint */ + musbotg_clear_stall_sub(sc, + UGETW(ed->wMaxPacketSize), + (ed->bEndpointAddress & UE_ADDR), + (ed->bmAttributes & UE_XFERTYPE), + (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); +} + +usb_error_t +musbotg_init(struct musbotg_softc *sc) +{ + struct usb_hw_ep_profile *pf; + uint16_t offset; + uint8_t nrx; + uint8_t ntx; + uint8_t temp; + uint8_t fsize; + uint8_t frx; + uint8_t ftx; + uint8_t dynfifo; + + DPRINTFN(1, "start\n"); + + /* set up the bus structure */ + sc->sc_bus.usbrev = USB_REV_2_0; + sc->sc_bus.methods = &musbotg_bus_methods; + + USB_BUS_LOCK(&sc->sc_bus); + + /* turn on clocks */ + + if (sc->sc_clocks_on) { + (sc->sc_clocks_on) (sc->sc_clocks_arg); + } + /* wait a little for things to stabilise */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000); + + /* disable all interrupts */ + + MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0); + MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0); + MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0); + + /* disable pullup */ + + musbotg_pull_common(sc, 0); + + /* wait a little bit (10ms) */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); + + /* disable double packet buffering */ + MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, 0xFFFF); + MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, 0xFFFF); + + /* enable HighSpeed and ISO Update flags */ + + MUSB2_WRITE_1(sc, MUSB2_REG_POWER, + MUSB2_MASK_HSENAB | MUSB2_MASK_ISOUPD); + + /* clear Session bit, if set */ + + temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL); + temp &= ~MUSB2_MASK_SESS; + MUSB2_WRITE_1(sc, MUSB2_REG_DEVCTL, temp); + + DPRINTF("DEVCTL=0x%02x\n", temp); + + /* disable testmode */ + + MUSB2_WRITE_1(sc, MUSB2_REG_TESTMODE, 0); + + /* set default value */ + + MUSB2_WRITE_1(sc, MUSB2_REG_MISC, 0); + + /* select endpoint index 0 */ + + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0); + + /* read out number of endpoints */ + + nrx = + (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) / 16); + + ntx = + (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) % 16); + + /* these numbers exclude the control endpoint */ + + DPRINTFN(2, "RX/TX endpoints: %u/%u\n", nrx, ntx); + + sc->sc_ep_max = (nrx > ntx) ? nrx : ntx; + if (sc->sc_ep_max == 0) { + DPRINTFN(2, "ERROR: Looks like the clocks are off!\n"); + } + /* read out configuration data */ + + sc->sc_conf_data = MUSB2_READ_1(sc, MUSB2_REG_CONFDATA); + + DPRINTFN(2, "Config Data: 0x%02x\n", + sc->sc_conf_data); + + dynfifo = (sc->sc_conf_data & MUSB2_MASK_CD_DYNFIFOSZ) ? 1 : 0; + + if (dynfifo) { + device_printf(sc->sc_bus.bdev, "Dynamic FIFO sizing detected, " + "assuming 16Kbytes of FIFO RAM\n"); + } + + DPRINTFN(2, "HW version: 0x%04x\n", + MUSB2_READ_1(sc, MUSB2_REG_HWVERS)); + + /* initialise endpoint profiles */ + + offset = 0; + + for (temp = 1; temp <= sc->sc_ep_max; temp++) { + pf = sc->sc_hw_ep_profile + temp; + + /* select endpoint */ + MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, temp); + + fsize = MUSB2_READ_1(sc, MUSB2_REG_FSIZE); + frx = (fsize & MUSB2_MASK_RX_FSIZE) / 16; + ftx = (fsize & MUSB2_MASK_TX_FSIZE); + + DPRINTF("Endpoint %u FIFO size: IN=%u, OUT=%u, DYN=%d\n", + temp, ftx, frx, dynfifo); + + if (dynfifo) { + if (frx && (temp <= nrx)) { + if (temp < 8) { + frx = 10; /* 1K */ + MUSB2_WRITE_1(sc, MUSB2_REG_RXFIFOSZ, + MUSB2_VAL_FIFOSZ_512 | + MUSB2_MASK_FIFODB); + } else { + frx = 7; /* 128 bytes */ + MUSB2_WRITE_1(sc, MUSB2_REG_RXFIFOSZ, + MUSB2_VAL_FIFOSZ_128); + } + + MUSB2_WRITE_2(sc, MUSB2_REG_RXFIFOADD, + offset >> 3); + + offset += (1 << frx); + } + if (ftx && (temp <= ntx)) { + if (temp < 8) { + ftx = 10; /* 1K */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXFIFOSZ, + MUSB2_VAL_FIFOSZ_512 | + MUSB2_MASK_FIFODB); + } else { + ftx = 7; /* 128 bytes */ + MUSB2_WRITE_1(sc, MUSB2_REG_TXFIFOSZ, + MUSB2_VAL_FIFOSZ_128); + } + + MUSB2_WRITE_2(sc, MUSB2_REG_TXFIFOADD, + offset >> 3); + + offset += (1 << ftx); + } + } + + if (frx && ftx && (temp <= nrx) && (temp <= ntx)) { + pf->max_in_frame_size = 1 << ftx; + pf->max_out_frame_size = 1 << frx; + pf->is_simplex = 0; /* duplex */ + pf->support_multi_buffer = 1; + pf->support_bulk = 1; + pf->support_interrupt = 1; + pf->support_isochronous = 1; + pf->support_in = 1; + pf->support_out = 1; + } else if (frx && (temp <= nrx)) { + pf->max_out_frame_size = 1 << frx; + pf->is_simplex = 1; /* simplex */ + pf->support_multi_buffer = 1; + pf->support_bulk = 1; + pf->support_interrupt = 1; + pf->support_isochronous = 1; + pf->support_out = 1; + } else if (ftx && (temp <= ntx)) { + pf->max_in_frame_size = 1 << ftx; + pf->is_simplex = 1; /* simplex */ + pf->support_multi_buffer = 1; + pf->support_bulk = 1; + pf->support_interrupt = 1; + pf->support_isochronous = 1; + pf->support_in = 1; + } + } + + DPRINTFN(2, "Dynamic FIFO size = %d bytes\n", offset); + + /* turn on default interrupts */ + + MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, + MUSB2_MASK_IRESET); + + musbotg_clocks_off(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); + + /* catch any lost interrupts */ + + musbotg_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +musbotg_uninit(struct musbotg_softc *sc) +{ + USB_BUS_LOCK(&sc->sc_bus); + + /* disable all interrupts */ + MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0); + MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0); + MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0); + + sc->sc_flags.port_powered = 0; + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + musbotg_pull_down(sc); + musbotg_clocks_off(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +musbotg_suspend(struct musbotg_softc *sc) +{ + /* TODO */ +} + +static void +musbotg_resume(struct musbotg_softc *sc) +{ + /* TODO */ +} + +static void +musbotg_do_poll(struct usb_bus *bus) +{ + struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + musbotg_interrupt_poll(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/*------------------------------------------------------------------------* + * musbotg bulk support + *------------------------------------------------------------------------*/ +static void +musbotg_device_bulk_open(struct usb_xfer *xfer) +{ + return; +} + +static void +musbotg_device_bulk_close(struct usb_xfer *xfer) +{ + musbotg_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +musbotg_device_bulk_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +musbotg_device_bulk_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + musbotg_setup_standard_chain(xfer); + musbotg_start_standard_chain(xfer); +} + +struct usb_pipe_methods musbotg_device_bulk_methods = +{ + .open = musbotg_device_bulk_open, + .close = musbotg_device_bulk_close, + .enter = musbotg_device_bulk_enter, + .start = musbotg_device_bulk_start, +}; + +/*------------------------------------------------------------------------* + * musbotg control support + *------------------------------------------------------------------------*/ +static void +musbotg_device_ctrl_open(struct usb_xfer *xfer) +{ + return; +} + +static void +musbotg_device_ctrl_close(struct usb_xfer *xfer) +{ + musbotg_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +musbotg_device_ctrl_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +musbotg_device_ctrl_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + musbotg_setup_standard_chain(xfer); + musbotg_start_standard_chain(xfer); +} + +struct usb_pipe_methods musbotg_device_ctrl_methods = +{ + .open = musbotg_device_ctrl_open, + .close = musbotg_device_ctrl_close, + .enter = musbotg_device_ctrl_enter, + .start = musbotg_device_ctrl_start, +}; + +/*------------------------------------------------------------------------* + * musbotg interrupt support + *------------------------------------------------------------------------*/ +static void +musbotg_device_intr_open(struct usb_xfer *xfer) +{ + return; +} + +static void +musbotg_device_intr_close(struct usb_xfer *xfer) +{ + musbotg_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +musbotg_device_intr_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +musbotg_device_intr_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + musbotg_setup_standard_chain(xfer); + musbotg_start_standard_chain(xfer); +} + +struct usb_pipe_methods musbotg_device_intr_methods = +{ + .open = musbotg_device_intr_open, + .close = musbotg_device_intr_close, + .enter = musbotg_device_intr_enter, + .start = musbotg_device_intr_start, +}; + +/*------------------------------------------------------------------------* + * musbotg full speed isochronous support + *------------------------------------------------------------------------*/ +static void +musbotg_device_isoc_open(struct usb_xfer *xfer) +{ + return; +} + +static void +musbotg_device_isoc_close(struct usb_xfer *xfer) +{ + musbotg_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +musbotg_device_isoc_enter(struct usb_xfer *xfer) +{ + struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus); + uint32_t temp; + uint32_t nframes; + uint32_t fs_frames; + + DPRINTFN(5, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes); + + /* get the current frame index */ + + nframes = MUSB2_READ_2(sc, MUSB2_REG_FRAME); + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + temp = (nframes - xfer->endpoint->isoc_next) & MUSB2_MASK_FRAME; + + if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) { + fs_frames = (xfer->nframes + 7) / 8; + } else { + fs_frames = xfer->nframes; + } + + if ((xfer->endpoint->is_synced == 0) || + (temp < fs_frames)) { + /* + * If there is data underflow or the pipe queue is + * empty we schedule the transfer a few frames ahead + * of the current frame position. Else two isochronous + * transfers might overlap. + */ + xfer->endpoint->isoc_next = (nframes + 3) & MUSB2_MASK_FRAME; + xfer->endpoint->is_synced = 1; + DPRINTFN(2, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + temp = (xfer->endpoint->isoc_next - nframes) & MUSB2_MASK_FRAME; + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + + fs_frames; + + /* compute frame number for next insertion */ + xfer->endpoint->isoc_next += fs_frames; + + /* setup TDs */ + musbotg_setup_standard_chain(xfer); +} + +static void +musbotg_device_isoc_start(struct usb_xfer *xfer) +{ + /* start TD chain */ + musbotg_start_standard_chain(xfer); +} + +struct usb_pipe_methods musbotg_device_isoc_methods = +{ + .open = musbotg_device_isoc_open, + .close = musbotg_device_isoc_close, + .enter = musbotg_device_isoc_enter, + .start = musbotg_device_isoc_start, +}; + +/*------------------------------------------------------------------------* + * musbotg root control support + *------------------------------------------------------------------------* + * Simulate a hardware HUB by handling all the necessary requests. + *------------------------------------------------------------------------*/ + +static const struct usb_device_descriptor musbotg_devd = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = UDESC_DEVICE, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_HSHUBSTT, + .bMaxPacketSize = 64, + .bcdDevice = {0x00, 0x01}, + .iManufacturer = 1, + .iProduct = 2, + .bNumConfigurations = 1, +}; + +static const struct usb_device_qualifier musbotg_odevd = { + .bLength = sizeof(struct usb_device_qualifier), + .bDescriptorType = UDESC_DEVICE_QUALIFIER, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_FSHUB, + .bMaxPacketSize0 = 0, + .bNumConfigurations = 0, +}; + +static const struct musbotg_config_desc musbotg_confd = { + .confd = { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(musbotg_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0, + }, + .ifcd = { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = 0, + }, + .endpd = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = (UE_DIR_IN | MUSBOTG_INTR_ENDPT), + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, + .bInterval = 255, + }, +}; + +static const struct usb_hub_descriptor_min musbotg_hubd = { + .bDescLength = sizeof(musbotg_hubd), + .bDescriptorType = UDESC_HUB, + .bNbrPorts = 1, + .wHubCharacteristics[0] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF, + .wHubCharacteristics[1] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 16, + .bPwrOn2PwrGood = 50, + .bHubContrCurrent = 0, + .DeviceRemovable = {0}, /* port is removable */ +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_VENDOR \ + 'M', 0, 'e', 0, 'n', 0, 't', 0, 'o', 0, 'r', 0, ' ', 0, \ + 'G', 0, 'r', 0, 'a', 0, 'p', 0, 'h', 0, 'i', 0, 'c', 0, 's', 0 + +#define STRING_PRODUCT \ + 'O', 0, 'T', 0, 'G', 0, ' ', 0, 'R', 0, \ + 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \ + 'U', 0, 'B', 0, + +USB_MAKE_STRING_DESC(STRING_LANG, musbotg_langtab); +USB_MAKE_STRING_DESC(STRING_VENDOR, musbotg_vendor); +USB_MAKE_STRING_DESC(STRING_PRODUCT, musbotg_product); + +static usb_error_t +musbotg_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus); + const void *ptr; + uint16_t len; + uint16_t value; + uint16_t index; + usb_error_t err; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* buffer reset */ + ptr = (const void *)&sc->sc_hub_temp; + len = 0; + err = 0; + + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + /* demultiplex the control request */ + + switch (req->bmRequestType) { + case UT_READ_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (req->bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_DESCRIPTOR: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SYNCH_FRAME: + goto tr_valid; /* nop */ + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (req->bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_INTERFACE: + switch (req->bRequest) { + case UR_SET_INTERFACE: + goto tr_handle_set_interface; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (req->bRequest) { + case UR_GET_INTERFACE: + goto tr_handle_get_interface; + case UR_GET_STATUS: + goto tr_handle_get_iface_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_INTERFACE: + case UT_WRITE_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_READ_CLASS_INTERFACE: + case UT_READ_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_WRITE_CLASS_DEVICE: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_valid; + case UR_SET_DESCRIPTOR: + case UR_SET_FEATURE: + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_OTHER: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_handle_clear_port_feature; + case UR_SET_FEATURE: + goto tr_handle_set_port_feature; + case UR_CLEAR_TT_BUFFER: + case UR_RESET_TT: + case UR_STOP_TT: + goto tr_valid; + + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_OTHER: + switch (req->bRequest) { + case UR_GET_TT_STATE: + goto tr_handle_get_tt_state; + case UR_GET_STATUS: + goto tr_handle_get_port_status; + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_class_descriptor; + case UR_GET_STATUS: + goto tr_handle_get_class_status; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + goto tr_valid; + +tr_handle_get_descriptor: + switch (value >> 8) { + case UDESC_DEVICE: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(musbotg_devd); + ptr = (const void *)&musbotg_devd; + goto tr_valid; + case UDESC_CONFIG: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(musbotg_confd); + ptr = (const void *)&musbotg_confd; + goto tr_valid; + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + len = sizeof(musbotg_langtab); + ptr = (const void *)&musbotg_langtab; + goto tr_valid; + + case 1: /* Vendor */ + len = sizeof(musbotg_vendor); + ptr = (const void *)&musbotg_vendor; + goto tr_valid; + + case 2: /* Product */ + len = sizeof(musbotg_product); + ptr = (const void *)&musbotg_product; + goto tr_valid; + default: + break; + } + break; + default: + goto tr_stalled; + } + goto tr_stalled; + +tr_handle_get_config: + len = 1; + sc->sc_hub_temp.wValue[0] = sc->sc_conf; + goto tr_valid; + +tr_handle_get_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); + goto tr_valid; + +tr_handle_set_address: + if (value & 0xFF00) { + goto tr_stalled; + } + sc->sc_rt_addr = value; + goto tr_valid; + +tr_handle_set_config: + if (value >= 2) { + goto tr_stalled; + } + sc->sc_conf = value; + goto tr_valid; + +tr_handle_get_interface: + len = 1; + sc->sc_hub_temp.wValue[0] = 0; + goto tr_valid; + +tr_handle_get_tt_state: +tr_handle_get_class_status: +tr_handle_get_iface_status: +tr_handle_get_ep_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, 0); + goto tr_valid; + +tr_handle_set_halt: +tr_handle_set_interface: +tr_handle_set_wakeup: +tr_handle_clear_wakeup: +tr_handle_clear_halt: + goto tr_valid; + +tr_handle_clear_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(8, "UR_CLEAR_PORT_FEATURE on port %d\n", index); + + switch (value) { + case UHF_PORT_SUSPEND: + musbotg_wakeup_peer(sc); + break; + + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 0; + break; + + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 0; + musbotg_pull_down(sc); + musbotg_clocks_off(sc); + break; + case UHF_C_PORT_CONNECTION: + sc->sc_flags.change_connect = 0; + break; + case UHF_C_PORT_SUSPEND: + sc->sc_flags.change_suspend = 0; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_set_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(8, "UR_SET_PORT_FEATURE\n"); + + switch (value) { + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 1; + break; + case UHF_PORT_SUSPEND: + case UHF_PORT_RESET: + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 1; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_get_port_status: + + DPRINTFN(8, "UR_GET_PORT_STATUS\n"); + + if (index != 1) { + goto tr_stalled; + } + if (sc->sc_flags.status_vbus) { + musbotg_clocks_on(sc); + musbotg_pull_up(sc); + } else { + musbotg_pull_down(sc); + musbotg_clocks_off(sc); + } + + /* Select Device Side Mode */ + value = UPS_PORT_MODE_DEVICE; + + if (sc->sc_flags.status_high_speed) { + value |= UPS_HIGH_SPEED; + } + if (sc->sc_flags.port_powered) { + value |= UPS_PORT_POWER; + } + if (sc->sc_flags.port_enabled) { + value |= UPS_PORT_ENABLED; + } + if (sc->sc_flags.status_vbus && + sc->sc_flags.status_bus_reset) { + value |= UPS_CURRENT_CONNECT_STATUS; + } + if (sc->sc_flags.status_suspend) { + value |= UPS_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortStatus, value); + + value = 0; + + if (sc->sc_flags.change_connect) { + value |= UPS_C_CONNECT_STATUS; + + if (sc->sc_flags.status_vbus && + sc->sc_flags.status_bus_reset) { + /* reset EP0 state */ + sc->sc_ep0_busy = 0; + sc->sc_ep0_cmd = 0; + } + } + if (sc->sc_flags.change_suspend) { + value |= UPS_C_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortChange, value); + len = sizeof(sc->sc_hub_temp.ps); + goto tr_valid; + +tr_handle_get_class_descriptor: + if (value & 0xFF) { + goto tr_stalled; + } + ptr = (const void *)&musbotg_hubd; + len = sizeof(musbotg_hubd); + goto tr_valid; + +tr_stalled: + err = USB_ERR_STALLED; +tr_valid: +done: + *plength = len; + *pptr = ptr; + return (err); +} + +static void +musbotg_xfer_setup(struct usb_setup_params *parm) +{ + const struct usb_hw_ep_profile *pf; + struct musbotg_softc *sc; + struct usb_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t n; + uint8_t ep_no; + + sc = MUSBOTG_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + /* + * NOTE: This driver does not use any of the parameters that + * are computed from the following values. Just set some + * reasonable dummies: + */ + parm->hc_max_packet_size = 0x400; + parm->hc_max_frame_size = 0x400; + + if ((parm->methods == &musbotg_device_isoc_methods) || + (parm->methods == &musbotg_device_intr_methods)) + parm->hc_max_packet_count = 3; + else + parm->hc_max_packet_count = 1; + + usbd_transfer_setup_sub(parm); + + /* + * compute maximum number of TDs + */ + if (parm->methods == &musbotg_device_ctrl_methods) { + + ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ; + + } else if (parm->methods == &musbotg_device_bulk_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else if (parm->methods == &musbotg_device_intr_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else if (parm->methods == &musbotg_device_isoc_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else { + + ntd = 0; + } + + /* + * check if "usbd_transfer_setup_sub" set an error + */ + if (parm->err) { + return; + } + /* + * allocate transfer descriptors + */ + last_obj = NULL; + + /* + * get profile stuff + */ + if (ntd) { + + ep_no = xfer->endpointno & UE_ADDR; + musbotg_get_hw_ep_profile(parm->udev, &pf, ep_no); + + if (pf == NULL) { + /* should not happen */ + parm->err = USB_ERR_INVAL; + return; + } + } else { + ep_no = 0; + pf = NULL; + } + + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + for (n = 0; n != ntd; n++) { + + struct musbotg_td *td; + + if (parm->buf) { + + td = USB_ADD_BYTES(parm->buf, parm->size[0]); + + /* init TD */ + td->max_frame_size = xfer->max_frame_size; + td->ep_no = ep_no; + td->obj_next = last_obj; + + last_obj = td; + } + parm->size[0] += sizeof(*td); + } + + xfer->td_start[0] = last_obj; +} + +static void +musbotg_xfer_unsetup(struct usb_xfer *xfer) +{ + return; +} + +static void +musbotg_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *ep) +{ + struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus); + + DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", + ep, udev->address, + edesc->bEndpointAddress, udev->flags.usb_mode, + sc->sc_rt_addr); + + if (udev->device_index != sc->sc_rt_addr) { + + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + if ((udev->speed != USB_SPEED_FULL) && + (udev->speed != USB_SPEED_HIGH)) { + /* not supported */ + return; + } + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + ep->methods = &musbotg_device_ctrl_methods; + break; + case UE_INTERRUPT: + ep->methods = &musbotg_device_intr_methods; + break; + case UE_ISOCHRONOUS: + ep->methods = &musbotg_device_isoc_methods; + break; + case UE_BULK: + ep->methods = &musbotg_device_bulk_methods; + break; + default: + /* do nothing */ + break; + } + } +} + +static void +musbotg_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) +{ + struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus); + + switch (state) { + case USB_HW_POWER_SUSPEND: + musbotg_suspend(sc); + break; + case USB_HW_POWER_SHUTDOWN: + musbotg_uninit(sc); + break; + case USB_HW_POWER_RESUME: + musbotg_resume(sc); + break; + default: + break; + } +} + +struct usb_bus_methods musbotg_bus_methods = +{ + .endpoint_init = &musbotg_ep_init, + .xfer_setup = &musbotg_xfer_setup, + .xfer_unsetup = &musbotg_xfer_unsetup, + .get_hw_ep_profile = &musbotg_get_hw_ep_profile, + .set_stall = &musbotg_set_stall, + .clear_stall = &musbotg_clear_stall, + .roothub_exec = &musbotg_roothub_exec, + .xfer_poll = &musbotg_do_poll, + .set_hw_power_sleep = &musbotg_set_hw_power_sleep, +}; diff --git a/sys/bus/u4b/controller/musb_otg.h b/sys/bus/u4b/controller/musb_otg.h new file mode 100644 index 0000000000..3b889a9dfe --- /dev/null +++ b/sys/bus/u4b/controller/musb_otg.h @@ -0,0 +1,404 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This header file defines the registers of the Mentor Graphics USB OnTheGo + * Inventra chip. + */ + +#ifndef _MUSB2_OTG_H_ +#define _MUSB2_OTG_H_ + +#define MUSB2_MAX_DEVICES (USB_MIN_DEVICES + 1) + +/* Common registers */ + +#define MUSB2_REG_FADDR 0x0000 /* function address register */ +#define MUSB2_MASK_FADDR 0x7F + +#define MUSB2_REG_POWER 0x0001 /* power register */ +#define MUSB2_MASK_SUSPM_ENA 0x01 +#define MUSB2_MASK_SUSPMODE 0x02 +#define MUSB2_MASK_RESUME 0x04 +#define MUSB2_MASK_RESET 0x08 +#define MUSB2_MASK_HSMODE 0x10 +#define MUSB2_MASK_HSENAB 0x20 +#define MUSB2_MASK_SOFTC 0x40 +#define MUSB2_MASK_ISOUPD 0x80 + +/* Endpoint interrupt handling */ + +#define MUSB2_REG_INTTX 0x0002 /* transmit interrupt register */ +#define MUSB2_REG_INTRX 0x0004 /* receive interrupt register */ +#define MUSB2_REG_INTTXE 0x0006 /* transmit interrupt enable register */ +#define MUSB2_REG_INTRXE 0x0008 /* receive interrupt enable register */ +#define MUSB2_MASK_EPINT(epn) (1 << (epn)) /* epn = [0..15] */ + +/* Common interrupt handling */ + +#define MUSB2_REG_INTUSB 0x000A /* USB interrupt register */ +#define MUSB2_MASK_ISUSP 0x01 +#define MUSB2_MASK_IRESUME 0x02 +#define MUSB2_MASK_IRESET 0x04 +#define MUSB2_MASK_IBABBLE 0x04 +#define MUSB2_MASK_ISOF 0x08 +#define MUSB2_MASK_ICONN 0x10 +#define MUSB2_MASK_IDISC 0x20 +#define MUSB2_MASK_ISESSRQ 0x40 +#define MUSB2_MASK_IVBUSERR 0x80 + +#define MUSB2_REG_INTUSBE 0x000B /* USB interrupt enable register */ +#define MUSB2_REG_FRAME 0x000C /* USB frame register */ +#define MUSB2_MASK_FRAME 0x3FF /* 0..1023 */ + +#define MUSB2_REG_EPINDEX 0x000E /* endpoint index register */ +#define MUSB2_MASK_EPINDEX 0x0F + +#define MUSB2_REG_TESTMODE 0x000F /* test mode register */ +#define MUSB2_MASK_TSE0_NAK 0x01 +#define MUSB2_MASK_TJ 0x02 +#define MUSB2_MASK_TK 0x04 +#define MUSB2_MASK_TPACKET 0x08 +#define MUSB2_MASK_TFORCE_HS 0x10 +#define MUSB2_MASK_TFORCE_LS 0x20 +#define MUSB2_MASK_TFIFO_ACC 0x40 +#define MUSB2_MASK_TFORCE_HC 0x80 + +#define MUSB2_REG_INDEXED_CSR 0x0010 /* EP control status register offset */ + +#define MUSB2_REG_TXMAXP (0x0000 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_REG_RXMAXP (0x0004 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_PKTSIZE 0x03FF /* in bytes, should be even */ +#define MUSB2_MASK_PKTMULT 0xFC00 /* HS packet multiplier: 0..2 */ + +#define MUSB2_REG_TXCSRL (0x0002 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_CSRL_TXPKTRDY 0x01 +#define MUSB2_MASK_CSRL_TXFIFONEMPTY 0x02 +#define MUSB2_MASK_CSRL_TXUNDERRUN 0x04 /* Device Mode */ +#define MUSB2_MASK_CSRL_TXERROR 0x04 /* Host Mode */ +#define MUSB2_MASK_CSRL_TXFFLUSH 0x08 +#define MUSB2_MASK_CSRL_TXSENDSTALL 0x10/* Device Mode */ +#define MUSB2_MASK_CSRL_TXSETUPPKT 0x10 /* Host Mode */ +#define MUSB2_MASK_CSRL_TXSENTSTALL 0x20/* Device Mode */ +#define MUSB2_MASK_CSRL_TXSTALLED 0x20 /* Host Mode */ +#define MUSB2_MASK_CSRL_TXDT_CLR 0x40 +#define MUSB2_MASK_CSRL_TXINCOMP 0x80 + +/* Device Side Mode */ +#define MUSB2_MASK_CSR0L_RXPKTRDY 0x01 +#define MUSB2_MASK_CSR0L_TXPKTRDY 0x02 +#define MUSB2_MASK_CSR0L_SENTSTALL 0x04 +#define MUSB2_MASK_CSR0L_DATAEND 0x08 +#define MUSB2_MASK_CSR0L_SETUPEND 0x10 +#define MUSB2_MASK_CSR0L_SENDSTALL 0x20 +#define MUSB2_MASK_CSR0L_RXPKTRDY_CLR 0x40 +#define MUSB2_MASK_CSR0L_SETUPEND_CLR 0x80 + +/* Host Side Mode */ +#define MUSB2_MASK_CSR0L_RXSTALL 0x04 +#define MUSB2_MASK_CSR0L_SETUPPKT 0x08 +#define MUSB2_MASK_CSR0L_ERROR 0x10 +#define MUSB2_MASK_CSR0L_REQPKT 0x20 +#define MUSB2_MASK_CSR0L_STATUSPKT 0x40 +#define MUSB2_MASK_CSR0L_NAKTIMO 0x80 + +#define MUSB2_REG_TXCSRH (0x0003 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_CSRH_TXDT_VAL 0x01 /* Host Mode */ +#define MUSB2_MASK_CSRH_TXDT_WR 0x02 /* Host Mode */ +#define MUSB2_MASK_CSRH_TXDMAREQMODE 0x04 +#define MUSB2_MASK_CSRH_TXDT_SWITCH 0x08 +#define MUSB2_MASK_CSRH_TXDMAREQENA 0x10 +#define MUSB2_MASK_CSRH_RXMODE 0x00 +#define MUSB2_MASK_CSRH_TXMODE 0x20 +#define MUSB2_MASK_CSRH_TXISO 0x40 /* Device Mode */ +#define MUSB2_MASK_CSRH_TXAUTOSET 0x80 + +#define MUSB2_MASK_CSR0H_FFLUSH 0x01 /* Device Side flush FIFO */ +#define MUSB2_MASK_CSR0H_DT 0x02 /* Host Side data toggle */ +#define MUSB2_MASK_CSR0H_DT_SET 0x04 /* Host Side */ +#define MUSB2_MASK_CSR0H_PING_DIS 0x08 /* Host Side */ + +#define MUSB2_REG_RXCSRL (0x0006 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_CSRL_RXPKTRDY 0x01 +#define MUSB2_MASK_CSRL_RXFIFOFULL 0x02 +#define MUSB2_MASK_CSRL_RXOVERRUN 0x04 +#define MUSB2_MASK_CSRL_RXDATAERR 0x08 +#define MUSB2_MASK_CSRL_RXFFLUSH 0x10 +#define MUSB2_MASK_CSRL_RXSENDSTALL 0x20/* Device Mode */ +#define MUSB2_MASK_CSRL_RXREQPKT 0x20 /* Host Mode */ +#define MUSB2_MASK_CSRL_RXSENTSTALL 0x40/* Device Mode */ +#define MUSB2_MASK_CSRL_RXSTALL 0x40 /* Host Mode */ +#define MUSB2_MASK_CSRL_RXDT_CLR 0x80 + +#define MUSB2_REG_RXCSRH (0x0007 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_CSRH_RXINCOMP 0x01 +#define MUSB2_MASK_CSRH_RXDT_VAL 0x02 /* Host Mode */ +#define MUSB2_MASK_CSRH_RXDT_SET 0x04 /* Host Mode */ +#define MUSB2_MASK_CSRH_RXDMAREQMODE 0x08 +#define MUSB2_MASK_CSRH_RXNYET 0x10 +#define MUSB2_MASK_CSRH_RXDMAREQENA 0x20 +#define MUSB2_MASK_CSRH_RXISO 0x40 /* Device Mode */ +#define MUSB2_MASK_CSRH_RXAUTOREQ 0x40 /* Host Mode */ +#define MUSB2_MASK_CSRH_RXAUTOCLEAR 0x80 + +#define MUSB2_REG_RXCOUNT (0x0008 + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_RXCOUNT 0xFFFF + +#define MUSB2_REG_TXTI (0x000A + MUSB2_REG_INDEXED_CSR) +#define MUSB2_REG_RXTI (0x000C + MUSB2_REG_INDEXED_CSR) + +/* Host Mode */ +#define MUSB2_MASK_TI_SPEED 0xC0 +#define MUSB2_MASK_TI_SPEED_LO 0xC0 +#define MUSB2_MASK_TI_SPEED_FS 0x80 +#define MUSB2_MASK_TI_SPEED_HS 0x40 +#define MUSB2_MASK_TI_PROTO_CTRL 0x00 +#define MUSB2_MASK_TI_PROTO_ISOC 0x10 +#define MUSB2_MASK_TI_PROTO_BULK 0x20 +#define MUSB2_MASK_TI_PROTO_INTR 0x30 +#define MUSB2_MASK_TI_EP_NUM 0x0F + +#define MUSB2_REG_TXNAKLIMIT (0x000B /* EPN=0 */ + MUSB2_REG_INDEXED_CSR) +#define MUSB2_REG_RXNAKLIMIT (0x000D /* EPN=0 */ + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_NAKLIMIT 0xFF + +#define MUSB2_REG_FSIZE (0x000F + MUSB2_REG_INDEXED_CSR) +#define MUSB2_MASK_RX_FSIZE 0xF0 /* 3..13, 2**n bytes */ +#define MUSB2_MASK_TX_FSIZE 0x0F /* 3..13, 2**n bytes */ + +#define MUSB2_REG_EPFIFO(n) (0x0020 + (4*(n))) + +#define MUSB2_REG_CONFDATA (0x000F + MUSB2_REG_INDEXED_CSR) /* EPN=0 */ +#define MUSB2_MASK_CD_UTMI_DW 0x01 +#define MUSB2_MASK_CD_SOFTCONE 0x02 +#define MUSB2_MASK_CD_DYNFIFOSZ 0x04 +#define MUSB2_MASK_CD_HBTXE 0x08 +#define MUSB2_MASK_CD_HBRXE 0x10 +#define MUSB2_MASK_CD_BIGEND 0x20 +#define MUSB2_MASK_CD_MPTXE 0x40 +#define MUSB2_MASK_CD_MPRXE 0x80 + +/* Various registers */ + +#define MUSB2_REG_DEVCTL 0x0060 +#define MUSB2_MASK_SESS 0x01 +#define MUSB2_MASK_HOSTREQ 0x02 +#define MUSB2_MASK_HOSTMD 0x04 +#define MUSB2_MASK_VBUS0 0x08 +#define MUSB2_MASK_VBUS1 0x10 +#define MUSB2_MASK_LSDEV 0x20 +#define MUSB2_MASK_FSDEV 0x40 +#define MUSB2_MASK_BDEV 0x80 + +#define MUSB2_REG_MISC 0x0061 +#define MUSB2_MASK_RXEDMA 0x01 +#define MUSB2_MASK_TXEDMA 0x02 + +#define MUSB2_REG_TXFIFOSZ 0x0062 +#define MUSB2_REG_RXFIFOSZ 0x0063 +#define MUSB2_MASK_FIFODB 0x10 /* set if double buffering, r/w */ +#define MUSB2_MASK_FIFOSZ 0x0F +#define MUSB2_VAL_FIFOSZ_8 0 +#define MUSB2_VAL_FIFOSZ_16 1 +#define MUSB2_VAL_FIFOSZ_32 2 +#define MUSB2_VAL_FIFOSZ_64 3 +#define MUSB2_VAL_FIFOSZ_128 4 +#define MUSB2_VAL_FIFOSZ_256 5 +#define MUSB2_VAL_FIFOSZ_512 6 +#define MUSB2_VAL_FIFOSZ_1024 7 +#define MUSB2_VAL_FIFOSZ_2048 8 +#define MUSB2_VAL_FIFOSZ_4096 9 + +#define MUSB2_REG_TXFIFOADD 0x0064 +#define MUSB2_REG_RXFIFOADD 0x0066 +#define MUSB2_MASK_FIFOADD 0xFFF /* unit is 8-bytes */ + +#define MUSB2_REG_VSTATUS 0x0068 +#define MUSB2_REG_VCONTROL 0x0068 +#define MUSB2_REG_HWVERS 0x006C +#define MUSB2_REG_ULPI_BASE 0x0070 + +#define MUSB2_REG_EPINFO 0x0078 +#define MUSB2_MASK_NRXEP 0xF0 +#define MUSB2_MASK_NTXEP 0x0F + +#define MUSB2_REG_RAMINFO 0x0079 +#define MUSB2_REG_LINKINFO 0x007A + +#define MUSB2_REG_VPLEN 0x007B +#define MUSB2_MASK_VPLEN 0xFF + +#define MUSB2_REG_HS_EOF1 0x007C +#define MUSB2_REG_FS_EOF1 0x007D +#define MUSB2_REG_LS_EOF1 0x007E +#define MUSB2_REG_SOFT_RST 0x007F +#define MUSB2_MASK_SRST 0x01 +#define MUSB2_MASK_SRSTX 0x02 + +#define MUSB2_REG_RQPKTCOUNT(n) (0x0300 + (4*(n)) +#define MUSB2_REG_RXDBDIS 0x0340 +#define MUSB2_REG_TXDBDIS 0x0342 +#define MUSB2_MASK_DB(n) (1 << (n)) /* disable double buffer, n = [0..15] */ + +#define MUSB2_REG_CHIRPTO 0x0344 +#define MUSB2_REG_HSRESUM 0x0346 + +/* Host Mode only registers */ + +#define MUSB2_REG_TXFADDR(n) (0x0080 + (8*(n))) +#define MUSB2_REG_TXHADDR(n) (0x0082 + (8*(n))) +#define MUSB2_REG_TXHUBPORT(n) (0x0083 + (8*(n))) +#define MUSB2_REG_RXFADDR(n) (0x0084 + (8*(n))) +#define MUSB2_REG_RXHADDR(n) (0x0086 + (8*(n))) +#define MUSB2_REG_RXHPORT(n) (0x0087 + (8*(n))) + +#define MUSB2_EP_MAX 16 /* maximum number of endpoints */ + +#define MUSB2_READ_2(sc, reg) \ + bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) + +#define MUSB2_WRITE_2(sc, reg, data) \ + bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data) + +#define MUSB2_READ_1(sc, reg) \ + bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) + +#define MUSB2_WRITE_1(sc, reg, data) \ + bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data) + +struct musbotg_td; +struct musbotg_softc; + +typedef uint8_t (musbotg_cmd_t)(struct musbotg_td *td); + +struct musbotg_dma { + struct musbotg_softc *sc; + uint32_t dma_chan; + uint8_t busy:1; + uint8_t complete:1; + uint8_t error:1; +}; + +struct musbotg_td { + struct musbotg_td *obj_next; + musbotg_cmd_t *func; + struct usb_page_cache *pc; + uint32_t offset; + uint32_t remainder; + uint16_t max_frame_size; /* packet_size * mult */ + uint8_t ep_no; + uint8_t error:1; + uint8_t alt_next:1; + uint8_t short_pkt:1; + uint8_t support_multi_buffer:1; + uint8_t did_stall:1; + uint8_t dma_enabled:1; +}; + +struct musbotg_std_temp { + musbotg_cmd_t *func; + struct usb_page_cache *pc; + struct musbotg_td *td; + struct musbotg_td *td_next; + uint32_t len; + uint32_t offset; + uint16_t max_frame_size; + uint8_t short_pkt; + /* + * short_pkt = 0: transfer should be short terminated + * short_pkt = 1: transfer should not be short terminated + */ + uint8_t setup_alt_next; + uint8_t did_stall; +}; + +struct musbotg_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union musbotg_hub_temp { + uWord wValue; + struct usb_port_status ps; +}; + +struct musbotg_flags { + uint8_t change_connect:1; + uint8_t change_suspend:1; + uint8_t status_suspend:1; /* set if suspended */ + uint8_t status_vbus:1; /* set if present */ + uint8_t status_bus_reset:1; /* set if reset complete */ + uint8_t status_high_speed:1; /* set if High Speed is selected */ + uint8_t remote_wakeup:1; + uint8_t self_powered:1; + uint8_t clocks_off:1; + uint8_t port_powered:1; + uint8_t port_enabled:1; + uint8_t d_pulled_up:1; +}; + +struct musbotg_softc { + struct usb_bus sc_bus; + union musbotg_hub_temp sc_hub_temp; + struct usb_hw_ep_profile sc_hw_ep_profile[16]; + + struct usb_device *sc_devices[MUSB2_MAX_DEVICES]; + struct resource *sc_io_res; + struct resource *sc_irq_res; + void *sc_intr_hdl; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + void (*sc_clocks_on) (void *arg); + void (*sc_clocks_off) (void *arg); + void *sc_clocks_arg; + + uint32_t sc_bounce_buf[(1024 * 3) / 4]; /* bounce buffer */ + + uint8_t sc_ep_max; /* maximum number of RX and TX + * endpoints supported */ + uint8_t sc_rt_addr; /* root HUB address */ + uint8_t sc_dv_addr; /* device address */ + uint8_t sc_conf; /* root HUB config */ + uint8_t sc_ep0_busy; /* set if ep0 is busy */ + uint8_t sc_ep0_cmd; /* pending commands */ + uint8_t sc_conf_data; /* copy of hardware register */ + + uint8_t sc_hub_idata[1]; + + struct musbotg_flags sc_flags; +}; + +/* prototypes */ + +usb_error_t musbotg_init(struct musbotg_softc *sc); +void musbotg_uninit(struct musbotg_softc *sc); +void musbotg_interrupt(struct musbotg_softc *sc); +void musbotg_vbus_interrupt(struct musbotg_softc *sc, uint8_t is_on); + +#endif /* _MUSB2_OTG_H_ */ diff --git a/sys/bus/u4b/controller/musb_otg_atmelarm.c b/sys/bus/u4b/controller/musb_otg_atmelarm.c new file mode 100644 index 0000000000..bb3e60a6d4 --- /dev/null +++ b/sys/bus/u4b/controller/musb_otg_atmelarm.c @@ -0,0 +1,241 @@ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +static device_probe_t musbotg_probe; +static device_attach_t musbotg_attach; +static device_detach_t musbotg_detach; + +struct musbotg_super_softc { + struct musbotg_softc sc_otg; /* must be first */ +}; + +static void +musbotg_vbus_poll(struct musbotg_super_softc *sc) +{ + uint8_t vbus_val = 1; /* fake VBUS on - TODO */ + + /* just forward it */ + musbotg_vbus_interrupt(&sc->sc_otg, vbus_val); +} + +static void +musbotg_clocks_on(void *arg) +{ +#if 0 + struct musbotg_super_softc *sc = arg; + +#endif +} + +static void +musbotg_clocks_off(void *arg) +{ +#if 0 + struct musbotg_super_softc *sc = arg; + +#endif +} + +static int +musbotg_probe(device_t dev) +{ + device_set_desc(dev, "MUSB OTG integrated USB controller"); + return (0); +} + +static int +musbotg_attach(device_t dev) +{ + struct musbotg_super_softc *sc = device_get_softc(dev); + int err; + int rid; + + /* setup MUSB OTG USB controller interface softc */ + sc->sc_otg.sc_clocks_on = &musbotg_clocks_on; + sc->sc_otg.sc_clocks_off = &musbotg_clocks_off; + sc->sc_otg.sc_clocks_arg = sc; + + /* initialise some bus fields */ + sc->sc_otg.sc_bus.parent = dev; + sc->sc_otg.sc_bus.devices = sc->sc_otg.sc_devices; + sc->sc_otg.sc_bus.devices_max = MUSB2_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_otg.sc_bus, + USB_GET_DMA_TAG(dev), NULL)) { + return (ENOMEM); + } + rid = 0; + sc->sc_otg.sc_io_res = + bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); + + if (!(sc->sc_otg.sc_io_res)) { + err = ENOMEM; + goto error; + } + sc->sc_otg.sc_io_tag = rman_get_bustag(sc->sc_otg.sc_io_res); + sc->sc_otg.sc_io_hdl = rman_get_bushandle(sc->sc_otg.sc_io_res); + sc->sc_otg.sc_io_size = rman_get_size(sc->sc_otg.sc_io_res); + + rid = 0; + sc->sc_otg.sc_irq_res = + bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); + if (!(sc->sc_otg.sc_irq_res)) { + goto error; + } + sc->sc_otg.sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (!(sc->sc_otg.sc_bus.bdev)) { + goto error; + } + device_set_ivars(sc->sc_otg.sc_bus.bdev, &sc->sc_otg.sc_bus); + +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)musbotg_interrupt, sc, &sc->sc_otg.sc_intr_hdl); +#else + err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (driver_intr_t *)musbotg_interrupt, sc, &sc->sc_otg.sc_intr_hdl); +#endif + if (err) { + sc->sc_otg.sc_intr_hdl = NULL; + goto error; + } + err = musbotg_init(&sc->sc_otg); + if (!err) { + err = device_probe_and_attach(sc->sc_otg.sc_bus.bdev); + } + if (err) { + goto error; + } else { + /* poll VBUS one time */ + musbotg_vbus_poll(sc); + } + return (0); + +error: + musbotg_detach(dev); + return (ENXIO); +} + +static int +musbotg_detach(device_t dev) +{ + struct musbotg_super_softc *sc = device_get_softc(dev); + device_t bdev; + int err; + + if (sc->sc_otg.sc_bus.bdev) { + bdev = sc->sc_otg.sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(dev); + + if (sc->sc_otg.sc_irq_res && sc->sc_otg.sc_intr_hdl) { + /* + * only call musbotg_uninit() after musbotg_init() + */ + musbotg_uninit(&sc->sc_otg); + + err = bus_teardown_intr(dev, sc->sc_otg.sc_irq_res, + sc->sc_otg.sc_intr_hdl); + sc->sc_otg.sc_intr_hdl = NULL; + } + /* free IRQ channel, if any */ + if (sc->sc_otg.sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 0, + sc->sc_otg.sc_irq_res); + sc->sc_otg.sc_irq_res = NULL; + } + /* free memory resource, if any */ + if (sc->sc_otg.sc_io_res) { + bus_release_resource(dev, SYS_RES_MEMORY, 0, + sc->sc_otg.sc_io_res); + sc->sc_otg.sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_otg.sc_bus, NULL); + + return (0); +} + +static device_method_t musbotg_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, musbotg_probe), + DEVMETHOD(device_attach, musbotg_attach), + DEVMETHOD(device_detach, musbotg_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD_END +}; + +static driver_t musbotg_driver = { + .name = "musbotg", + .methods = musbotg_methods, + .size = sizeof(struct musbotg_super_softc), +}; + +static devclass_t musbotg_devclass; + +DRIVER_MODULE(musbotg, atmelarm, musbotg_driver, musbotg_devclass, 0, 0); +MODULE_DEPEND(musbotg, usb, 1, 1, 1); diff --git a/sys/bus/u4b/controller/ohci.c b/sys/bus/u4b/controller/ohci.c new file mode 100644 index 0000000000..93c2bb2209 --- /dev/null +++ b/sys/bus/u4b/controller/ohci.c @@ -0,0 +1,2741 @@ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * USB Open Host Controller driver. + * + * OHCI spec: http://www.compaq.com/productinfo/development/openhci.html + * USB spec: http://www.usb.org/developers/docs/usbspec.zip + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define USB_DEBUG_VAR ohcidebug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define OHCI_BUS2SC(bus) \ + ((ohci_softc_t *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((ohci_softc_t *)0)->sc_bus)))) + +#ifdef USB_DEBUG +static int ohcidebug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ohci, CTLFLAG_RW, 0, "USB ohci"); +SYSCTL_INT(_hw_usb_ohci, OID_AUTO, debug, CTLFLAG_RW, + &ohcidebug, 0, "ohci debug level"); + +TUNABLE_INT("hw.usb.ohci.debug", &ohcidebug); + +static void ohci_dumpregs(ohci_softc_t *); +static void ohci_dump_tds(ohci_td_t *); +static uint8_t ohci_dump_td(ohci_td_t *); +static void ohci_dump_ed(ohci_ed_t *); +static uint8_t ohci_dump_itd(ohci_itd_t *); +static void ohci_dump_itds(ohci_itd_t *); + +#endif + +#define OBARR(sc) bus_space_barrier((sc)->sc_io_tag, (sc)->sc_io_hdl, 0, (sc)->sc_io_size, \ + BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE) +#define OWRITE1(sc, r, x) \ + do { OBARR(sc); bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0) +#define OWRITE2(sc, r, x) \ + do { OBARR(sc); bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0) +#define OWRITE4(sc, r, x) \ + do { OBARR(sc); bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0) +#define OREAD1(sc, r) (OBARR(sc), bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) +#define OREAD2(sc, r) (OBARR(sc), bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) +#define OREAD4(sc, r) (OBARR(sc), bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) + +#define OHCI_INTR_ENDPT 1 + +extern struct usb_bus_methods ohci_bus_methods; +extern struct usb_pipe_methods ohci_device_bulk_methods; +extern struct usb_pipe_methods ohci_device_ctrl_methods; +extern struct usb_pipe_methods ohci_device_intr_methods; +extern struct usb_pipe_methods ohci_device_isoc_methods; + +static void ohci_do_poll(struct usb_bus *bus); +static void ohci_device_done(struct usb_xfer *xfer, usb_error_t error); +static void ohci_timeout(void *arg); +static uint8_t ohci_check_transfer(struct usb_xfer *xfer); +static void ohci_root_intr(ohci_softc_t *sc); + +struct ohci_std_temp { + struct usb_page_cache *pc; + ohci_td_t *td; + ohci_td_t *td_next; + uint32_t average; + uint32_t td_flags; + uint32_t len; + uint16_t max_frame_size; + uint8_t shortpkt; + uint8_t setup_alt_next; + uint8_t last_frame; +}; + +static struct ohci_hcca * +ohci_get_hcca(ohci_softc_t *sc) +{ + usb_pc_cpu_invalidate(&sc->sc_hw.hcca_pc); + return (sc->sc_hcca_p); +} + +void +ohci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb) +{ + struct ohci_softc *sc = OHCI_BUS2SC(bus); + uint32_t i; + + cb(bus, &sc->sc_hw.hcca_pc, &sc->sc_hw.hcca_pg, + sizeof(ohci_hcca_t), OHCI_HCCA_ALIGN); + + cb(bus, &sc->sc_hw.ctrl_start_pc, &sc->sc_hw.ctrl_start_pg, + sizeof(ohci_ed_t), OHCI_ED_ALIGN); + + cb(bus, &sc->sc_hw.bulk_start_pc, &sc->sc_hw.bulk_start_pg, + sizeof(ohci_ed_t), OHCI_ED_ALIGN); + + cb(bus, &sc->sc_hw.isoc_start_pc, &sc->sc_hw.isoc_start_pg, + sizeof(ohci_ed_t), OHCI_ED_ALIGN); + + for (i = 0; i != OHCI_NO_EDS; i++) { + cb(bus, sc->sc_hw.intr_start_pc + i, sc->sc_hw.intr_start_pg + i, + sizeof(ohci_ed_t), OHCI_ED_ALIGN); + } +} + +static usb_error_t +ohci_controller_init(ohci_softc_t *sc, int do_suspend) +{ + struct usb_page_search buf_res; + uint32_t i; + uint32_t ctl; + uint32_t ival; + uint32_t hcr; + uint32_t fm; + uint32_t per; + uint32_t desca; + + /* Determine in what context we are running. */ + ctl = OREAD4(sc, OHCI_CONTROL); + if (ctl & OHCI_IR) { + /* SMM active, request change */ + DPRINTF("SMM active, request owner change\n"); + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_OCR); + for (i = 0; (i < 100) && (ctl & OHCI_IR); i++) { + usb_pause_mtx(NULL, hz / 1000); + ctl = OREAD4(sc, OHCI_CONTROL); + } + if (ctl & OHCI_IR) { + device_printf(sc->sc_bus.bdev, + "SMM does not respond, resetting\n"); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); + goto reset; + } + } else { + DPRINTF("cold started\n"); +reset: + /* controller was cold started */ + usb_pause_mtx(NULL, + USB_MS_TO_TICKS(USB_BUS_RESET_DELAY)); + } + + /* + * This reset should not be necessary according to the OHCI spec, but + * without it some controllers do not start. + */ + DPRINTF("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev)); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); + + usb_pause_mtx(NULL, + USB_MS_TO_TICKS(USB_BUS_RESET_DELAY)); + + /* we now own the host controller and the bus has been reset */ + ival = OHCI_GET_IVAL(OREAD4(sc, OHCI_FM_INTERVAL)); + + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_HCR); /* Reset HC */ + /* nominal time for a reset is 10 us */ + for (i = 0; i < 10; i++) { + DELAY(10); + hcr = OREAD4(sc, OHCI_COMMAND_STATUS) & OHCI_HCR; + if (!hcr) { + break; + } + } + if (hcr) { + device_printf(sc->sc_bus.bdev, "reset timeout\n"); + return (USB_ERR_IOERROR); + } +#ifdef USB_DEBUG + if (ohcidebug > 15) { + ohci_dumpregs(sc); + } +#endif + + if (do_suspend) { + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_SUSPEND); + return (USB_ERR_NORMAL_COMPLETION); + } + + /* The controller is now in SUSPEND state, we have 2ms to finish. */ + + /* set up HC registers */ + usbd_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res); + OWRITE4(sc, OHCI_HCCA, buf_res.physaddr); + + usbd_get_page(&sc->sc_hw.ctrl_start_pc, 0, &buf_res); + OWRITE4(sc, OHCI_CONTROL_HEAD_ED, buf_res.physaddr); + + usbd_get_page(&sc->sc_hw.bulk_start_pc, 0, &buf_res); + OWRITE4(sc, OHCI_BULK_HEAD_ED, buf_res.physaddr); + + /* disable all interrupts and then switch on all desired interrupts */ + OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); + OWRITE4(sc, OHCI_INTERRUPT_ENABLE, sc->sc_eintrs | OHCI_MIE); + /* switch on desired functional features */ + ctl = OREAD4(sc, OHCI_CONTROL); + ctl &= ~(OHCI_CBSR_MASK | OHCI_LES | OHCI_HCFS_MASK | OHCI_IR); + ctl |= OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE | + OHCI_RATIO_1_4 | OHCI_HCFS_OPERATIONAL; + /* And finally start it! */ + OWRITE4(sc, OHCI_CONTROL, ctl); + + /* + * The controller is now OPERATIONAL. Set a some final + * registers that should be set earlier, but that the + * controller ignores when in the SUSPEND state. + */ + fm = (OREAD4(sc, OHCI_FM_INTERVAL) & OHCI_FIT) ^ OHCI_FIT; + fm |= OHCI_FSMPS(ival) | ival; + OWRITE4(sc, OHCI_FM_INTERVAL, fm); + per = OHCI_PERIODIC(ival); /* 90% periodic */ + OWRITE4(sc, OHCI_PERIODIC_START, per); + + /* Fiddle the No OverCurrent Protection bit to avoid chip bug. */ + desca = OREAD4(sc, OHCI_RH_DESCRIPTOR_A); + OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca | OHCI_NOCP); + OWRITE4(sc, OHCI_RH_STATUS, OHCI_LPSC); /* Enable port power */ + usb_pause_mtx(NULL, + USB_MS_TO_TICKS(OHCI_ENABLE_POWER_DELAY)); + OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca); + + /* + * The AMD756 requires a delay before re-reading the register, + * otherwise it will occasionally report 0 ports. + */ + sc->sc_noport = 0; + for (i = 0; (i < 10) && (sc->sc_noport == 0); i++) { + usb_pause_mtx(NULL, + USB_MS_TO_TICKS(OHCI_READ_DESC_DELAY)); + sc->sc_noport = OHCI_GET_NDP(OREAD4(sc, OHCI_RH_DESCRIPTOR_A)); + } + +#ifdef USB_DEBUG + if (ohcidebug > 5) { + ohci_dumpregs(sc); + } +#endif + return (USB_ERR_NORMAL_COMPLETION); +} + +static struct ohci_ed * +ohci_init_ed(struct usb_page_cache *pc) +{ + struct usb_page_search buf_res; + struct ohci_ed *ed; + + usbd_get_page(pc, 0, &buf_res); + + ed = buf_res.buffer; + + ed->ed_self = htole32(buf_res.physaddr); + ed->ed_flags = htole32(OHCI_ED_SKIP); + ed->page_cache = pc; + + return (ed); +} + +usb_error_t +ohci_init(ohci_softc_t *sc) +{ + struct usb_page_search buf_res; + uint16_t i; + uint16_t bit; + uint16_t x; + uint16_t y; + + DPRINTF("start\n"); + + sc->sc_eintrs = OHCI_NORMAL_INTRS; + + /* + * Setup all ED's + */ + + sc->sc_ctrl_p_last = + ohci_init_ed(&sc->sc_hw.ctrl_start_pc); + + sc->sc_bulk_p_last = + ohci_init_ed(&sc->sc_hw.bulk_start_pc); + + sc->sc_isoc_p_last = + ohci_init_ed(&sc->sc_hw.isoc_start_pc); + + for (i = 0; i != OHCI_NO_EDS; i++) { + sc->sc_intr_p_last[i] = + ohci_init_ed(sc->sc_hw.intr_start_pc + i); + } + + /* + * the QHs are arranged to give poll intervals that are + * powers of 2 times 1ms + */ + bit = OHCI_NO_EDS / 2; + while (bit) { + x = bit; + while (x & bit) { + ohci_ed_t *ed_x; + ohci_ed_t *ed_y; + + y = (x ^ bit) | (bit / 2); + + /* + * the next QH has half the poll interval + */ + ed_x = sc->sc_intr_p_last[x]; + ed_y = sc->sc_intr_p_last[y]; + + ed_x->next = NULL; + ed_x->ed_next = ed_y->ed_self; + + x++; + } + bit >>= 1; + } + + if (1) { + + ohci_ed_t *ed_int; + ohci_ed_t *ed_isc; + + ed_int = sc->sc_intr_p_last[0]; + ed_isc = sc->sc_isoc_p_last; + + /* the last (1ms) QH */ + ed_int->next = ed_isc; + ed_int->ed_next = ed_isc->ed_self; + } + usbd_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res); + + sc->sc_hcca_p = buf_res.buffer; + + /* + * Fill HCCA interrupt table. The bit reversal is to get + * the tree set up properly to spread the interrupts. + */ + for (i = 0; i != OHCI_NO_INTRS; i++) { + sc->sc_hcca_p->hcca_interrupt_table[i] = + sc->sc_intr_p_last[i | (OHCI_NO_EDS / 2)]->ed_self; + } + /* flush all cache into memory */ + + usb_bus_mem_flush_all(&sc->sc_bus, &ohci_iterate_hw_softc); + + /* set up the bus struct */ + sc->sc_bus.methods = &ohci_bus_methods; + + usb_callout_init_mtx(&sc->sc_tmo_rhsc, &sc->sc_bus.bus_mtx, 0); + +#ifdef USB_DEBUG + if (ohcidebug > 15) { + for (i = 0; i != OHCI_NO_EDS; i++) { + printf("ed#%d ", i); + ohci_dump_ed(sc->sc_intr_p_last[i]); + } + printf("iso "); + ohci_dump_ed(sc->sc_isoc_p_last); + } +#endif + + sc->sc_bus.usbrev = USB_REV_1_0; + + if (ohci_controller_init(sc, 0) != 0) + return (USB_ERR_INVAL); + + /* catch any lost interrupts */ + ohci_do_poll(&sc->sc_bus); + return (USB_ERR_NORMAL_COMPLETION); +} + +/* + * shut down the controller when the system is going down + */ +void +ohci_detach(struct ohci_softc *sc) +{ + USB_BUS_LOCK(&sc->sc_bus); + + usb_callout_stop(&sc->sc_tmo_rhsc); + + OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); + + USB_BUS_UNLOCK(&sc->sc_bus); + + /* XXX let stray task complete */ + usb_pause_mtx(NULL, hz / 20); + + usb_callout_drain(&sc->sc_tmo_rhsc); +} + +static void +ohci_suspend(ohci_softc_t *sc) +{ + DPRINTF("\n"); + +#ifdef USB_DEBUG + if (ohcidebug > 2) + ohci_dumpregs(sc); +#endif + + /* reset HC and leave it suspended */ + ohci_controller_init(sc, 1); +} + +static void +ohci_resume(ohci_softc_t *sc) +{ + DPRINTF("\n"); + +#ifdef USB_DEBUG + if (ohcidebug > 2) + ohci_dumpregs(sc); +#endif + + /* some broken BIOSes never initialize the Controller chip */ + ohci_controller_init(sc, 0); + + /* catch any lost interrupts */ + ohci_do_poll(&sc->sc_bus); +} + +#ifdef USB_DEBUG +static void +ohci_dumpregs(ohci_softc_t *sc) +{ + struct ohci_hcca *hcca; + + DPRINTF("ohci_dumpregs: rev=0x%08x control=0x%08x command=0x%08x\n", + OREAD4(sc, OHCI_REVISION), + OREAD4(sc, OHCI_CONTROL), + OREAD4(sc, OHCI_COMMAND_STATUS)); + DPRINTF(" intrstat=0x%08x intre=0x%08x intrd=0x%08x\n", + OREAD4(sc, OHCI_INTERRUPT_STATUS), + OREAD4(sc, OHCI_INTERRUPT_ENABLE), + OREAD4(sc, OHCI_INTERRUPT_DISABLE)); + DPRINTF(" hcca=0x%08x percur=0x%08x ctrlhd=0x%08x\n", + OREAD4(sc, OHCI_HCCA), + OREAD4(sc, OHCI_PERIOD_CURRENT_ED), + OREAD4(sc, OHCI_CONTROL_HEAD_ED)); + DPRINTF(" ctrlcur=0x%08x bulkhd=0x%08x bulkcur=0x%08x\n", + OREAD4(sc, OHCI_CONTROL_CURRENT_ED), + OREAD4(sc, OHCI_BULK_HEAD_ED), + OREAD4(sc, OHCI_BULK_CURRENT_ED)); + DPRINTF(" done=0x%08x fmival=0x%08x fmrem=0x%08x\n", + OREAD4(sc, OHCI_DONE_HEAD), + OREAD4(sc, OHCI_FM_INTERVAL), + OREAD4(sc, OHCI_FM_REMAINING)); + DPRINTF(" fmnum=0x%08x perst=0x%08x lsthrs=0x%08x\n", + OREAD4(sc, OHCI_FM_NUMBER), + OREAD4(sc, OHCI_PERIODIC_START), + OREAD4(sc, OHCI_LS_THRESHOLD)); + DPRINTF(" desca=0x%08x descb=0x%08x stat=0x%08x\n", + OREAD4(sc, OHCI_RH_DESCRIPTOR_A), + OREAD4(sc, OHCI_RH_DESCRIPTOR_B), + OREAD4(sc, OHCI_RH_STATUS)); + DPRINTF(" port1=0x%08x port2=0x%08x\n", + OREAD4(sc, OHCI_RH_PORT_STATUS(1)), + OREAD4(sc, OHCI_RH_PORT_STATUS(2))); + + hcca = ohci_get_hcca(sc); + + DPRINTF(" HCCA: frame_number=0x%04x done_head=0x%08x\n", + le32toh(hcca->hcca_frame_number), + le32toh(hcca->hcca_done_head)); +} +static void +ohci_dump_tds(ohci_td_t *std) +{ + for (; std; std = std->obj_next) { + if (ohci_dump_td(std)) { + break; + } + } +} + +static uint8_t +ohci_dump_td(ohci_td_t *std) +{ + uint32_t td_flags; + uint8_t temp; + + usb_pc_cpu_invalidate(std->page_cache); + + td_flags = le32toh(std->td_flags); + temp = (std->td_next == 0); + + printf("TD(%p) at 0x%08x: %s%s%s%s%s delay=%d ec=%d " + "cc=%d\ncbp=0x%08x next=0x%08x be=0x%08x\n", + std, le32toh(std->td_self), + (td_flags & OHCI_TD_R) ? "-R" : "", + (td_flags & OHCI_TD_OUT) ? "-OUT" : "", + (td_flags & OHCI_TD_IN) ? "-IN" : "", + ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_1) ? "-TOG1" : "", + ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_0) ? "-TOG0" : "", + OHCI_TD_GET_DI(td_flags), + OHCI_TD_GET_EC(td_flags), + OHCI_TD_GET_CC(td_flags), + le32toh(std->td_cbp), + le32toh(std->td_next), + le32toh(std->td_be)); + + return (temp); +} + +static uint8_t +ohci_dump_itd(ohci_itd_t *sitd) +{ + uint32_t itd_flags; + uint16_t i; + uint8_t temp; + + usb_pc_cpu_invalidate(sitd->page_cache); + + itd_flags = le32toh(sitd->itd_flags); + temp = (sitd->itd_next == 0); + + printf("ITD(%p) at 0x%08x: sf=%d di=%d fc=%d cc=%d\n" + "bp0=0x%08x next=0x%08x be=0x%08x\n", + sitd, le32toh(sitd->itd_self), + OHCI_ITD_GET_SF(itd_flags), + OHCI_ITD_GET_DI(itd_flags), + OHCI_ITD_GET_FC(itd_flags), + OHCI_ITD_GET_CC(itd_flags), + le32toh(sitd->itd_bp0), + le32toh(sitd->itd_next), + le32toh(sitd->itd_be)); + for (i = 0; i < OHCI_ITD_NOFFSET; i++) { + printf("offs[%d]=0x%04x ", i, + (uint32_t)le16toh(sitd->itd_offset[i])); + } + printf("\n"); + + return (temp); +} + +static void +ohci_dump_itds(ohci_itd_t *sitd) +{ + for (; sitd; sitd = sitd->obj_next) { + if (ohci_dump_itd(sitd)) { + break; + } + } +} + +static void +ohci_dump_ed(ohci_ed_t *sed) +{ + uint32_t ed_flags; + uint32_t ed_headp; + + usb_pc_cpu_invalidate(sed->page_cache); + + ed_flags = le32toh(sed->ed_flags); + ed_headp = le32toh(sed->ed_headp); + + printf("ED(%p) at 0x%08x: addr=%d endpt=%d maxp=%d flags=%s%s%s%s%s\n" + "tailp=0x%08x headflags=%s%s headp=0x%08x nexted=0x%08x\n", + sed, le32toh(sed->ed_self), + OHCI_ED_GET_FA(ed_flags), + OHCI_ED_GET_EN(ed_flags), + OHCI_ED_GET_MAXP(ed_flags), + (ed_flags & OHCI_ED_DIR_OUT) ? "-OUT" : "", + (ed_flags & OHCI_ED_DIR_IN) ? "-IN" : "", + (ed_flags & OHCI_ED_SPEED) ? "-LOWSPEED" : "", + (ed_flags & OHCI_ED_SKIP) ? "-SKIP" : "", + (ed_flags & OHCI_ED_FORMAT_ISO) ? "-ISO" : "", + le32toh(sed->ed_tailp), + (ed_headp & OHCI_HALTED) ? "-HALTED" : "", + (ed_headp & OHCI_TOGGLECARRY) ? "-CARRY" : "", + le32toh(sed->ed_headp), + le32toh(sed->ed_next)); +} + +#endif + +static void +ohci_transfer_intr_enqueue(struct usb_xfer *xfer) +{ + /* check for early completion */ + if (ohci_check_transfer(xfer)) { + return; + } + /* put transfer on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usbd_transfer_timeout_ms(xfer, &ohci_timeout, xfer->timeout); + } +} + +#define OHCI_APPEND_QH(sed,last) (last) = _ohci_append_qh(sed,last) +static ohci_ed_t * +_ohci_append_qh(ohci_ed_t *sed, ohci_ed_t *last) +{ + DPRINTFN(11, "%p to %p\n", sed, last); + + if (sed->prev != NULL) { + /* should not happen */ + DPRINTFN(0, "ED already linked!\n"); + return (last); + } + /* (sc->sc_bus.bus_mtx) must be locked */ + + sed->next = last->next; + sed->ed_next = last->ed_next; + sed->ed_tailp = 0; + + sed->prev = last; + + usb_pc_cpu_flush(sed->page_cache); + + /* + * the last->next->prev is never followed: sed->next->prev = sed; + */ + + last->next = sed; + last->ed_next = sed->ed_self; + + usb_pc_cpu_flush(last->page_cache); + + return (sed); +} + +#define OHCI_REMOVE_QH(sed,last) (last) = _ohci_remove_qh(sed,last) +static ohci_ed_t * +_ohci_remove_qh(ohci_ed_t *sed, ohci_ed_t *last) +{ + DPRINTFN(11, "%p from %p\n", sed, last); + + /* (sc->sc_bus.bus_mtx) must be locked */ + + /* only remove if not removed from a queue */ + if (sed->prev) { + + sed->prev->next = sed->next; + sed->prev->ed_next = sed->ed_next; + + usb_pc_cpu_flush(sed->prev->page_cache); + + if (sed->next) { + sed->next->prev = sed->prev; + usb_pc_cpu_flush(sed->next->page_cache); + } + last = ((last == sed) ? sed->prev : last); + + sed->prev = 0; + + usb_pc_cpu_flush(sed->page_cache); + } + return (last); +} + +static void +ohci_isoc_done(struct usb_xfer *xfer) +{ + uint8_t nframes; + uint32_t *plen = xfer->frlengths; + volatile uint16_t *olen; + uint16_t len = 0; + ohci_itd_t *td = xfer->td_transfer_first; + + while (1) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } +#ifdef USB_DEBUG + if (ohcidebug > 5) { + DPRINTF("isoc TD\n"); + ohci_dump_itd(td); + } +#endif + usb_pc_cpu_invalidate(td->page_cache); + + nframes = td->frames; + olen = &td->itd_offset[0]; + + if (nframes > 8) { + nframes = 8; + } + while (nframes--) { + len = le16toh(*olen); + + if ((len >> 12) == OHCI_CC_NOT_ACCESSED) { + len = 0; + } else { + len &= ((1 << 12) - 1); + } + + if (len > *plen) { + len = 0;/* invalid length */ + } + *plen = len; + plen++; + olen++; + } + + if (((void *)td) == xfer->td_transfer_last) { + break; + } + td = td->obj_next; + } + + xfer->aframes = xfer->nframes; + ohci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); +} + +#ifdef USB_DEBUG +static const char *const + ohci_cc_strs[] = +{ + "NO_ERROR", + "CRC", + "BIT_STUFFING", + "DATA_TOGGLE_MISMATCH", + + "STALL", + "DEVICE_NOT_RESPONDING", + "PID_CHECK_FAILURE", + "UNEXPECTED_PID", + + "DATA_OVERRUN", + "DATA_UNDERRUN", + "BUFFER_OVERRUN", + "BUFFER_UNDERRUN", + + "reserved", + "reserved", + "NOT_ACCESSED", + "NOT_ACCESSED" +}; + +#endif + +static usb_error_t +ohci_non_isoc_done_sub(struct usb_xfer *xfer) +{ + ohci_td_t *td; + ohci_td_t *td_alt_next; + uint32_t temp; + uint32_t phy_start; + uint32_t phy_end; + uint32_t td_flags; + uint16_t cc; + + td = xfer->td_transfer_cache; + td_alt_next = td->alt_next; + td_flags = 0; + + if (xfer->aframes != xfer->nframes) { + usbd_xfer_set_frame_len(xfer, xfer->aframes, 0); + } + while (1) { + + usb_pc_cpu_invalidate(td->page_cache); + phy_start = le32toh(td->td_cbp); + td_flags = le32toh(td->td_flags); + cc = OHCI_TD_GET_CC(td_flags); + + if (phy_start) { + /* + * short transfer - compute the number of remaining + * bytes in the hardware buffer: + */ + phy_end = le32toh(td->td_be); + temp = (OHCI_PAGE(phy_start ^ phy_end) ? + (OHCI_PAGE_SIZE + 1) : 0x0001); + temp += OHCI_PAGE_OFFSET(phy_end); + temp -= OHCI_PAGE_OFFSET(phy_start); + + if (temp > td->len) { + /* guard against corruption */ + cc = OHCI_CC_STALL; + } else if (xfer->aframes != xfer->nframes) { + /* + * Sum up total transfer length + * in "frlengths[]": + */ + xfer->frlengths[xfer->aframes] += td->len - temp; + } + } else { + if (xfer->aframes != xfer->nframes) { + /* transfer was complete */ + xfer->frlengths[xfer->aframes] += td->len; + } + } + /* Check for last transfer */ + if (((void *)td) == xfer->td_transfer_last) { + td = NULL; + break; + } + /* Check transfer status */ + if (cc) { + /* the transfer is finished */ + td = NULL; + break; + } + /* Check for short transfer */ + if (phy_start) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + td = td->alt_next; + } else { + /* the transfer is finished */ + td = NULL; + } + break; + } + td = td->obj_next; + + if (td->alt_next != td_alt_next) { + /* this USB frame is complete */ + break; + } + } + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + DPRINTFN(16, "error cc=%d (%s)\n", + cc, ohci_cc_strs[cc]); + + return ((cc == 0) ? USB_ERR_NORMAL_COMPLETION : + (cc == OHCI_CC_STALL) ? USB_ERR_STALLED : USB_ERR_IOERROR); +} + +static void +ohci_non_isoc_done(struct usb_xfer *xfer) +{ + usb_error_t err = 0; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + +#ifdef USB_DEBUG + if (ohcidebug > 10) { + ohci_dump_tds(xfer->td_transfer_first); + } +#endif + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = ohci_non_isoc_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = ohci_non_isoc_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = ohci_non_isoc_done_sub(xfer); + } +done: + ohci_device_done(xfer, err); +} + +/*------------------------------------------------------------------------* + * ohci_check_transfer_sub + *------------------------------------------------------------------------*/ +static void +ohci_check_transfer_sub(struct usb_xfer *xfer) +{ + ohci_td_t *td; + ohci_ed_t *ed; + uint32_t phy_start; + uint32_t td_flags; + uint32_t td_next; + uint16_t cc; + + td = xfer->td_transfer_cache; + + while (1) { + + usb_pc_cpu_invalidate(td->page_cache); + phy_start = le32toh(td->td_cbp); + td_flags = le32toh(td->td_flags); + td_next = le32toh(td->td_next); + + /* Check for last transfer */ + if (((void *)td) == xfer->td_transfer_last) { + /* the transfer is finished */ + td = NULL; + break; + } + /* Check transfer status */ + cc = OHCI_TD_GET_CC(td_flags); + if (cc) { + /* the transfer is finished */ + td = NULL; + break; + } + /* + * Check if we reached the last packet + * or if there is a short packet: + */ + + if (((td_next & (~0xF)) == OHCI_TD_NEXT_END) || phy_start) { + /* follow alt next */ + td = td->alt_next; + break; + } + td = td->obj_next; + } + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + if (td) { + + ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + ed->ed_headp = td->td_self; + usb_pc_cpu_flush(ed->page_cache); + + DPRINTFN(13, "xfer=%p following alt next\n", xfer); + + /* + * Make sure that the OHCI re-scans the schedule by + * writing the BLF and CLF bits: + */ + + if (xfer->xroot->udev->flags.self_suspended) { + /* nothing to do */ + } else if (xfer->endpoint->methods == &ohci_device_bulk_methods) { + ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); + + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); + } else if (xfer->endpoint->methods == &ohci_device_ctrl_methods) { + ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); + + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); + } + } +} + +/*------------------------------------------------------------------------* + * ohci_check_transfer + * + * Return values: + * 0: USB transfer is not finished + * Else: USB transfer is finished + *------------------------------------------------------------------------*/ +static uint8_t +ohci_check_transfer(struct usb_xfer *xfer) +{ + ohci_ed_t *ed; + uint32_t ed_headp; + uint32_t ed_tailp; + + DPRINTFN(13, "xfer=%p checking transfer\n", xfer); + + ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + usb_pc_cpu_invalidate(ed->page_cache); + ed_headp = le32toh(ed->ed_headp); + ed_tailp = le32toh(ed->ed_tailp); + + if ((ed_headp & OHCI_HALTED) || + (((ed_headp ^ ed_tailp) & (~0xF)) == 0)) { + if (xfer->endpoint->methods == &ohci_device_isoc_methods) { + /* isochronous transfer */ + ohci_isoc_done(xfer); + } else { + if (xfer->flags_int.short_frames_ok) { + ohci_check_transfer_sub(xfer); + if (xfer->td_transfer_cache) { + /* not finished yet */ + return (0); + } + } + /* store data-toggle */ + if (ed_headp & OHCI_TOGGLECARRY) { + xfer->endpoint->toggle_next = 1; + } else { + xfer->endpoint->toggle_next = 0; + } + + /* non-isochronous transfer */ + ohci_non_isoc_done(xfer); + } + return (1); + } + DPRINTFN(13, "xfer=%p is still active\n", xfer); + return (0); +} + +static void +ohci_rhsc_enable(ohci_softc_t *sc) +{ + DPRINTFN(5, "\n"); + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + sc->sc_eintrs |= OHCI_RHSC; + OWRITE4(sc, OHCI_INTERRUPT_ENABLE, OHCI_RHSC); + + /* acknowledge any RHSC interrupt */ + OWRITE4(sc, OHCI_INTERRUPT_STATUS, OHCI_RHSC); + + ohci_root_intr(sc); +} + +static void +ohci_interrupt_poll(ohci_softc_t *sc) +{ + struct usb_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + /* + * check if transfer is transferred + */ + if (ohci_check_transfer(xfer)) { + /* queue has been modified */ + goto repeat; + } + } +} + +/*------------------------------------------------------------------------* + * ohci_interrupt - OHCI interrupt handler + * + * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler, + * hence the interrupt handler will be setup before "sc->sc_bus.bdev" + * is present ! + *------------------------------------------------------------------------*/ +void +ohci_interrupt(ohci_softc_t *sc) +{ + struct ohci_hcca *hcca; + uint32_t status; + uint32_t done; + + USB_BUS_LOCK(&sc->sc_bus); + + hcca = ohci_get_hcca(sc); + + DPRINTFN(16, "real interrupt\n"); + +#ifdef USB_DEBUG + if (ohcidebug > 15) { + ohci_dumpregs(sc); + } +#endif + + done = le32toh(hcca->hcca_done_head); + + /* + * The LSb of done is used to inform the HC Driver that an interrupt + * condition exists for both the Done list and for another event + * recorded in HcInterruptStatus. On an interrupt from the HC, the + * HC Driver checks the HccaDoneHead Value. If this value is 0, then + * the interrupt was caused by other than the HccaDoneHead update + * and the HcInterruptStatus register needs to be accessed to + * determine that exact interrupt cause. If HccaDoneHead is nonzero, + * then a Done list update interrupt is indicated and if the LSb of + * done is nonzero, then an additional interrupt event is indicated + * and HcInterruptStatus should be checked to determine its cause. + */ + if (done != 0) { + status = 0; + + if (done & ~OHCI_DONE_INTRS) { + status |= OHCI_WDH; + } + if (done & OHCI_DONE_INTRS) { + status |= OREAD4(sc, OHCI_INTERRUPT_STATUS); + } + hcca->hcca_done_head = 0; + + usb_pc_cpu_flush(&sc->sc_hw.hcca_pc); + } else { + status = OREAD4(sc, OHCI_INTERRUPT_STATUS) & ~OHCI_WDH; + } + + status &= ~OHCI_MIE; + if (status == 0) { + /* + * nothing to be done (PCI shared + * interrupt) + */ + goto done; + } + OWRITE4(sc, OHCI_INTERRUPT_STATUS, status); /* Acknowledge */ + + status &= sc->sc_eintrs; + if (status == 0) { + goto done; + } + if (status & (OHCI_SO | OHCI_RD | OHCI_UE | OHCI_RHSC)) { +#if 0 + if (status & OHCI_SO) { + /* XXX do what */ + } +#endif + if (status & OHCI_RD) { + printf("%s: resume detect\n", __FUNCTION__); + /* XXX process resume detect */ + } + if (status & OHCI_UE) { + printf("%s: unrecoverable error, " + "controller halted\n", __FUNCTION__); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); + /* XXX what else */ + } + if (status & OHCI_RHSC) { + /* + * Disable RHSC interrupt for now, because it will be + * on until the port has been reset. + */ + sc->sc_eintrs &= ~OHCI_RHSC; + OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_RHSC); + + ohci_root_intr(sc); + + /* do not allow RHSC interrupts > 1 per second */ + usb_callout_reset(&sc->sc_tmo_rhsc, hz, + (void *)&ohci_rhsc_enable, sc); + } + } + status &= ~(OHCI_RHSC | OHCI_WDH | OHCI_SO); + if (status != 0) { + /* Block unprocessed interrupts. XXX */ + OWRITE4(sc, OHCI_INTERRUPT_DISABLE, status); + sc->sc_eintrs &= ~status; + printf("%s: blocking intrs 0x%x\n", + __FUNCTION__, status); + } + /* poll all the USB transfers */ + ohci_interrupt_poll(sc); + +done: + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/* + * called when a request does not complete + */ +static void +ohci_timeout(void *arg) +{ + struct usb_xfer *xfer = arg; + + DPRINTF("xfer=%p\n", xfer); + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* transfer is transferred */ + ohci_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +ohci_do_poll(struct usb_bus *bus) +{ + struct ohci_softc *sc = OHCI_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + ohci_interrupt_poll(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +ohci_setup_standard_chain_sub(struct ohci_std_temp *temp) +{ + struct usb_page_search buf_res; + ohci_td_t *td; + ohci_td_t *td_next; + ohci_td_t *td_alt_next; + uint32_t buf_offset; + uint32_t average; + uint32_t len_old; + uint8_t shortpkt_old; + uint8_t precompute; + + td_alt_next = NULL; + buf_offset = 0; + shortpkt_old = temp->shortpkt; + len_old = temp->len; + precompute = 1; + + /* software is used to detect short incoming transfers */ + + if ((temp->td_flags & htole32(OHCI_TD_DP_MASK)) == htole32(OHCI_TD_IN)) { + temp->td_flags |= htole32(OHCI_TD_R); + } else { + temp->td_flags &= ~htole32(OHCI_TD_R); + } + +restart: + + td = temp->td; + td_next = temp->td_next; + + while (1) { + + if (temp->len == 0) { + + if (temp->shortpkt) { + break; + } + /* send a Zero Length Packet, ZLP, last */ + + temp->shortpkt = 1; + average = 0; + + } else { + + average = temp->average; + + if (temp->len < average) { + if (temp->len % temp->max_frame_size) { + temp->shortpkt = 1; + } + average = temp->len; + } + } + + if (td_next == NULL) { + panic("%s: out of OHCI transfer descriptors!", __FUNCTION__); + } + /* get next TD */ + + td = td_next; + td_next = td->obj_next; + + /* check if we are pre-computing */ + + if (precompute) { + + /* update remaining length */ + + temp->len -= average; + + continue; + } + /* fill out current TD */ + td->td_flags = temp->td_flags; + + /* the next TD uses TOGGLE_CARRY */ + temp->td_flags &= ~htole32(OHCI_TD_TOGGLE_MASK); + + if (average == 0) { + /* + * The buffer start and end phys addresses should be + * 0x0 for a zero length packet. + */ + td->td_cbp = 0; + td->td_be = 0; + td->len = 0; + + } else { + + usbd_get_page(temp->pc, buf_offset, &buf_res); + td->td_cbp = htole32(buf_res.physaddr); + buf_offset += (average - 1); + + usbd_get_page(temp->pc, buf_offset, &buf_res); + td->td_be = htole32(buf_res.physaddr); + buf_offset++; + + td->len = average; + + /* update remaining length */ + + temp->len -= average; + } + + if ((td_next == td_alt_next) && temp->setup_alt_next) { + /* we need to receive these frames one by one ! */ + td->td_flags &= htole32(~OHCI_TD_INTR_MASK); + td->td_flags |= htole32(OHCI_TD_SET_DI(1)); + td->td_next = htole32(OHCI_TD_NEXT_END); + } else { + if (td_next) { + /* link the current TD with the next one */ + td->td_next = td_next->td_self; + } + } + + td->alt_next = td_alt_next; + + usb_pc_cpu_flush(td->page_cache); + } + + if (precompute) { + precompute = 0; + + /* setup alt next pointer, if any */ + if (temp->last_frame) { + /* no alternate next */ + td_alt_next = NULL; + } else { + /* we use this field internally */ + td_alt_next = td_next; + } + + /* restore */ + temp->shortpkt = shortpkt_old; + temp->len = len_old; + goto restart; + } + temp->td = td; + temp->td_next = td_next; +} + +static void +ohci_setup_standard_chain(struct usb_xfer *xfer, ohci_ed_t **ed_last) +{ + struct ohci_std_temp temp; + struct usb_pipe_methods *methods; + ohci_ed_t *ed; + ohci_td_t *td; + uint32_t ed_flags; + uint32_t x; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpointno), + xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); + + temp.average = xfer->max_hc_frame_size; + temp.max_frame_size = xfer->max_frame_size; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + temp.td = NULL; + temp.td_next = td; + temp.last_frame = 0; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + + methods = xfer->endpoint->methods; + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.td_flags = htole32(OHCI_TD_SETUP | OHCI_TD_NOCC | + OHCI_TD_TOGGLE_0 | OHCI_TD_NOINTR); + + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.shortpkt = temp.len ? 1 : 0; + /* check for last frame */ + if (xfer->nframes == 1) { + /* no STATUS stage yet, SETUP is last */ + if (xfer->flags_int.control_act) { + temp.last_frame = 1; + temp.setup_alt_next = 0; + } + } + ohci_setup_standard_chain_sub(&temp); + + /* + * XXX assume that the setup message is + * contained within one USB packet: + */ + xfer->endpoint->toggle_next = 1; + } + x = 1; + } else { + x = 0; + } + temp.td_flags = htole32(OHCI_TD_NOCC | OHCI_TD_NOINTR); + + /* set data toggle */ + + if (xfer->endpoint->toggle_next) { + temp.td_flags |= htole32(OHCI_TD_TOGGLE_1); + } else { + temp.td_flags |= htole32(OHCI_TD_TOGGLE_0); + } + + /* set endpoint direction */ + + if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) { + temp.td_flags |= htole32(OHCI_TD_IN); + } else { + temp.td_flags |= htole32(OHCI_TD_OUT); + } + + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + temp.pc = xfer->frbuffers + x; + + x++; + + if (x == xfer->nframes) { + if (xfer->flags_int.control_xfr) { + /* no STATUS stage yet, DATA is last */ + if (xfer->flags_int.control_act) { + temp.last_frame = 1; + temp.setup_alt_next = 0; + } + } else { + temp.last_frame = 1; + temp.setup_alt_next = 0; + } + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.shortpkt = 0; + + } else { + + /* regular data transfer */ + + temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + ohci_setup_standard_chain_sub(&temp); + } + + /* check if we should append a status stage */ + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + /* + * Send a DATA1 message and invert the current endpoint + * direction. + */ + + /* set endpoint direction and data toggle */ + + if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) { + temp.td_flags = htole32(OHCI_TD_OUT | + OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1)); + } else { + temp.td_flags = htole32(OHCI_TD_IN | + OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1)); + } + + temp.len = 0; + temp.pc = NULL; + temp.shortpkt = 0; + temp.last_frame = 1; + temp.setup_alt_next = 0; + + ohci_setup_standard_chain_sub(&temp); + } + td = temp.td; + + /* Ensure that last TD is terminating: */ + td->td_next = htole32(OHCI_TD_NEXT_END); + td->td_flags &= ~htole32(OHCI_TD_INTR_MASK); + td->td_flags |= htole32(OHCI_TD_SET_DI(1)); + + usb_pc_cpu_flush(td->page_cache); + + /* must have at least one frame! */ + + xfer->td_transfer_last = td; + +#ifdef USB_DEBUG + if (ohcidebug > 8) { + DPRINTF("nexttog=%d; data before transfer:\n", + xfer->endpoint->toggle_next); + ohci_dump_tds(xfer->td_transfer_first); + } +#endif + + ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + ed_flags = (OHCI_ED_SET_FA(xfer->address) | + OHCI_ED_SET_EN(UE_GET_ADDR(xfer->endpointno)) | + OHCI_ED_SET_MAXP(xfer->max_frame_size)); + + ed_flags |= (OHCI_ED_FORMAT_GEN | OHCI_ED_DIR_TD); + + if (xfer->xroot->udev->speed == USB_SPEED_LOW) { + ed_flags |= OHCI_ED_SPEED; + } + ed->ed_flags = htole32(ed_flags); + + td = xfer->td_transfer_first; + + ed->ed_headp = td->td_self; + + if (xfer->xroot->udev->flags.self_suspended == 0) { + /* the append function will flush the endpoint descriptor */ + OHCI_APPEND_QH(ed, *ed_last); + + if (methods == &ohci_device_bulk_methods) { + ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); + + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); + } + if (methods == &ohci_device_ctrl_methods) { + ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); + + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); + } + } else { + usb_pc_cpu_flush(ed->page_cache); + } +} + +static void +ohci_root_intr(ohci_softc_t *sc) +{ + uint32_t hstatus; + uint16_t i; + uint16_t m; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* clear any old interrupt data */ + memset(sc->sc_hub_idata, 0, sizeof(sc->sc_hub_idata)); + + hstatus = OREAD4(sc, OHCI_RH_STATUS); + DPRINTF("sc=%p hstatus=0x%08x\n", + sc, hstatus); + + /* set bits */ + m = (sc->sc_noport + 1); + if (m > (8 * sizeof(sc->sc_hub_idata))) { + m = (8 * sizeof(sc->sc_hub_idata)); + } + for (i = 1; i < m; i++) { + /* pick out CHANGE bits from the status register */ + if (OREAD4(sc, OHCI_RH_PORT_STATUS(i)) >> 16) { + sc->sc_hub_idata[i / 8] |= 1 << (i % 8); + DPRINTF("port %d changed\n", i); + } + } + + uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, + sizeof(sc->sc_hub_idata)); +} + +/* NOTE: "done" can be run two times in a row, + * from close and from interrupt + */ +static void +ohci_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_pipe_methods *methods = xfer->endpoint->methods; + ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); + ohci_ed_t *ed; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + + DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); + + ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; + if (ed) { + usb_pc_cpu_invalidate(ed->page_cache); + } + if (methods == &ohci_device_bulk_methods) { + OHCI_REMOVE_QH(ed, sc->sc_bulk_p_last); + } + if (methods == &ohci_device_ctrl_methods) { + OHCI_REMOVE_QH(ed, sc->sc_ctrl_p_last); + } + if (methods == &ohci_device_intr_methods) { + OHCI_REMOVE_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]); + } + if (methods == &ohci_device_isoc_methods) { + OHCI_REMOVE_QH(ed, sc->sc_isoc_p_last); + } + xfer->td_transfer_first = NULL; + xfer->td_transfer_last = NULL; + + /* dequeue transfer and start next transfer */ + usbd_transfer_done(xfer, error); +} + +/*------------------------------------------------------------------------* + * ohci bulk support + *------------------------------------------------------------------------*/ +static void +ohci_device_bulk_open(struct usb_xfer *xfer) +{ + return; +} + +static void +ohci_device_bulk_close(struct usb_xfer *xfer) +{ + ohci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +ohci_device_bulk_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +ohci_device_bulk_start(struct usb_xfer *xfer) +{ + ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); + + /* setup TD's and QH */ + ohci_setup_standard_chain(xfer, &sc->sc_bulk_p_last); + + /* put transfer on interrupt queue */ + ohci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods ohci_device_bulk_methods = +{ + .open = ohci_device_bulk_open, + .close = ohci_device_bulk_close, + .enter = ohci_device_bulk_enter, + .start = ohci_device_bulk_start, +}; + +/*------------------------------------------------------------------------* + * ohci control support + *------------------------------------------------------------------------*/ +static void +ohci_device_ctrl_open(struct usb_xfer *xfer) +{ + return; +} + +static void +ohci_device_ctrl_close(struct usb_xfer *xfer) +{ + ohci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +ohci_device_ctrl_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +ohci_device_ctrl_start(struct usb_xfer *xfer) +{ + ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); + + /* setup TD's and QH */ + ohci_setup_standard_chain(xfer, &sc->sc_ctrl_p_last); + + /* put transfer on interrupt queue */ + ohci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods ohci_device_ctrl_methods = +{ + .open = ohci_device_ctrl_open, + .close = ohci_device_ctrl_close, + .enter = ohci_device_ctrl_enter, + .start = ohci_device_ctrl_start, +}; + +/*------------------------------------------------------------------------* + * ohci interrupt support + *------------------------------------------------------------------------*/ +static void +ohci_device_intr_open(struct usb_xfer *xfer) +{ + ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); + uint16_t best; + uint16_t bit; + uint16_t x; + + best = 0; + bit = OHCI_NO_EDS / 2; + while (bit) { + if (xfer->interval >= bit) { + x = bit; + best = bit; + while (x & bit) { + if (sc->sc_intr_stat[x] < + sc->sc_intr_stat[best]) { + best = x; + } + x++; + } + break; + } + bit >>= 1; + } + + sc->sc_intr_stat[best]++; + xfer->qh_pos = best; + + DPRINTFN(3, "best=%d interval=%d\n", + best, xfer->interval); +} + +static void +ohci_device_intr_close(struct usb_xfer *xfer) +{ + ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); + + sc->sc_intr_stat[xfer->qh_pos]--; + + ohci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +ohci_device_intr_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +ohci_device_intr_start(struct usb_xfer *xfer) +{ + ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); + + /* setup TD's and QH */ + ohci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]); + + /* put transfer on interrupt queue */ + ohci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods ohci_device_intr_methods = +{ + .open = ohci_device_intr_open, + .close = ohci_device_intr_close, + .enter = ohci_device_intr_enter, + .start = ohci_device_intr_start, +}; + +/*------------------------------------------------------------------------* + * ohci isochronous support + *------------------------------------------------------------------------*/ +static void +ohci_device_isoc_open(struct usb_xfer *xfer) +{ + return; +} + +static void +ohci_device_isoc_close(struct usb_xfer *xfer) +{ + /**/ + ohci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +ohci_device_isoc_enter(struct usb_xfer *xfer) +{ + struct usb_page_search buf_res; + ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); + struct ohci_hcca *hcca; + uint32_t buf_offset; + uint32_t nframes; + uint32_t ed_flags; + uint32_t *plen; + uint16_t itd_offset[OHCI_ITD_NOFFSET]; + uint16_t length; + uint8_t ncur; + ohci_itd_t *td; + ohci_itd_t *td_last = NULL; + ohci_ed_t *ed; + + hcca = ohci_get_hcca(sc); + + nframes = le32toh(hcca->hcca_frame_number); + + DPRINTFN(6, "xfer=%p isoc_next=%u nframes=%u hcca_fn=%u\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes, nframes); + + if ((xfer->endpoint->is_synced == 0) || + (((nframes - xfer->endpoint->isoc_next) & 0xFFFF) < xfer->nframes) || + (((xfer->endpoint->isoc_next - nframes) & 0xFFFF) >= 128)) { + /* + * If there is data underflow or the pipe queue is empty we + * schedule the transfer a few frames ahead of the current + * frame position. Else two isochronous transfers might + * overlap. + */ + xfer->endpoint->isoc_next = (nframes + 3) & 0xFFFF; + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + buf_offset = ((xfer->endpoint->isoc_next - nframes) & 0xFFFF); + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + (usb_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset + + xfer->nframes); + + /* get the real number of frames */ + + nframes = xfer->nframes; + + buf_offset = 0; + + plen = xfer->frlengths; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + + xfer->td_transfer_first = td; + + ncur = 0; + length = 0; + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + itd_offset[ncur] = length; + buf_offset += *plen; + length += *plen; + plen++; + ncur++; + + if ( /* check if the ITD is full */ + (ncur == OHCI_ITD_NOFFSET) || + /* check if we have put more than 4K into the ITD */ + (length & 0xF000) || + /* check if it is the last frame */ + (nframes == 0)) { + + /* fill current ITD */ + td->itd_flags = htole32( + OHCI_ITD_NOCC | + OHCI_ITD_SET_SF(xfer->endpoint->isoc_next) | + OHCI_ITD_NOINTR | + OHCI_ITD_SET_FC(ncur)); + + td->frames = ncur; + xfer->endpoint->isoc_next += ncur; + + if (length == 0) { + /* all zero */ + td->itd_bp0 = 0; + td->itd_be = ~0; + + while (ncur--) { + td->itd_offset[ncur] = + htole16(OHCI_ITD_MK_OFFS(0)); + } + } else { + usbd_get_page(xfer->frbuffers, buf_offset - length, &buf_res); + length = OHCI_PAGE_MASK(buf_res.physaddr); + buf_res.physaddr = + OHCI_PAGE(buf_res.physaddr); + td->itd_bp0 = htole32(buf_res.physaddr); + usbd_get_page(xfer->frbuffers, buf_offset - 1, &buf_res); + td->itd_be = htole32(buf_res.physaddr); + + while (ncur--) { + itd_offset[ncur] += length; + itd_offset[ncur] = + OHCI_ITD_MK_OFFS(itd_offset[ncur]); + td->itd_offset[ncur] = + htole16(itd_offset[ncur]); + } + } + ncur = 0; + length = 0; + td_last = td; + td = td->obj_next; + + if (td) { + /* link the last TD with the next one */ + td_last->itd_next = td->itd_self; + } + usb_pc_cpu_flush(td_last->page_cache); + } + } + + /* update the last TD */ + td_last->itd_flags &= ~htole32(OHCI_ITD_NOINTR); + td_last->itd_flags |= htole32(OHCI_ITD_SET_DI(0)); + td_last->itd_next = 0; + + usb_pc_cpu_flush(td_last->page_cache); + + xfer->td_transfer_last = td_last; + +#ifdef USB_DEBUG + if (ohcidebug > 8) { + DPRINTF("data before transfer:\n"); + ohci_dump_itds(xfer->td_transfer_first); + } +#endif + ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) + ed_flags = (OHCI_ED_DIR_IN | OHCI_ED_FORMAT_ISO); + else + ed_flags = (OHCI_ED_DIR_OUT | OHCI_ED_FORMAT_ISO); + + ed_flags |= (OHCI_ED_SET_FA(xfer->address) | + OHCI_ED_SET_EN(UE_GET_ADDR(xfer->endpointno)) | + OHCI_ED_SET_MAXP(xfer->max_frame_size)); + + if (xfer->xroot->udev->speed == USB_SPEED_LOW) { + ed_flags |= OHCI_ED_SPEED; + } + ed->ed_flags = htole32(ed_flags); + + td = xfer->td_transfer_first; + + ed->ed_headp = td->itd_self; + + /* isochronous transfers are not affected by suspend / resume */ + /* the append function will flush the endpoint descriptor */ + + OHCI_APPEND_QH(ed, sc->sc_isoc_p_last); +} + +static void +ohci_device_isoc_start(struct usb_xfer *xfer) +{ + /* put transfer on interrupt queue */ + ohci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods ohci_device_isoc_methods = +{ + .open = ohci_device_isoc_open, + .close = ohci_device_isoc_close, + .enter = ohci_device_isoc_enter, + .start = ohci_device_isoc_start, +}; + +/*------------------------------------------------------------------------* + * ohci root control support + *------------------------------------------------------------------------* + * Simulate a hardware hub by handling all the necessary requests. + *------------------------------------------------------------------------*/ + +static const +struct usb_device_descriptor ohci_devd = +{ + sizeof(struct usb_device_descriptor), + UDESC_DEVICE, /* type */ + {0x00, 0x01}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_FSHUB, /* protocol */ + 64, /* max packet */ + {0}, {0}, {0x00, 0x01}, /* device id */ + 1, 2, 0, /* string indicies */ + 1 /* # of configurations */ +}; + +static const +struct ohci_config_desc ohci_confd = +{ + .confd = { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(ohci_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0, /* max power */ + }, + .ifcd = { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = 0, + }, + .endpd = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = UE_DIR_IN | OHCI_INTR_ENDPT, + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 32,/* max packet (255 ports) */ + .bInterval = 255, + }, +}; + +static const +struct usb_hub_descriptor ohci_hubd = +{ + 0, /* dynamic length */ + UDESC_HUB, + 0, + {0, 0}, + 0, + 0, + {0}, +}; + +static usb_error_t +ohci_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + ohci_softc_t *sc = OHCI_BUS2SC(udev->bus); + const void *ptr; + const char *str_ptr; + uint32_t port; + uint32_t v; + uint16_t len; + uint16_t value; + uint16_t index; + uint8_t l; + usb_error_t err; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* buffer reset */ + ptr = (const void *)&sc->sc_hub_desc.temp; + len = 0; + err = 0; + + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " + "wValue=0x%04x wIndex=0x%04x\n", + req->bmRequestType, req->bRequest, + UGETW(req->wLength), value, index); + +#define C(x,y) ((x) | ((y) << 8)) + switch (C(req->bRequest, req->bmRequestType)) { + case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): + case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): + case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): + /* + * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops + * for the integrated root hub. + */ + break; + case C(UR_GET_CONFIG, UT_READ_DEVICE): + len = 1; + sc->sc_hub_desc.temp[0] = sc->sc_conf; + break; + case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): + switch (value >> 8) { + case UDESC_DEVICE: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(ohci_devd); + ptr = (const void *)&ohci_devd; + break; + + case UDESC_CONFIG: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(ohci_confd); + ptr = (const void *)&ohci_confd; + break; + + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + str_ptr = "\001"; + break; + + case 1: /* Vendor */ + str_ptr = sc->sc_vendor; + break; + + case 2: /* Product */ + str_ptr = "OHCI root HUB"; + break; + + default: + str_ptr = ""; + break; + } + + len = usb_make_str_desc( + sc->sc_hub_desc.temp, + sizeof(sc->sc_hub_desc.temp), + str_ptr); + break; + + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_INTERFACE, UT_READ_INTERFACE): + len = 1; + sc->sc_hub_desc.temp[0] = 0; + break; + case C(UR_GET_STATUS, UT_READ_DEVICE): + len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED); + break; + case C(UR_GET_STATUS, UT_READ_INTERFACE): + case C(UR_GET_STATUS, UT_READ_ENDPOINT): + len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, 0); + break; + case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): + if (value >= OHCI_MAX_DEVICES) { + err = USB_ERR_IOERROR; + goto done; + } + sc->sc_addr = value; + break; + case C(UR_SET_CONFIG, UT_WRITE_DEVICE): + if ((value != 0) && (value != 1)) { + err = USB_ERR_IOERROR; + goto done; + } + sc->sc_conf = value; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_DEVICE): + case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): + case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): + err = USB_ERR_IOERROR; + goto done; + case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + break; + case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): + break; + /* Hub requests */ + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): + DPRINTFN(9, "UR_CLEAR_PORT_FEATURE " + "port=%d feature=%d\n", + index, value); + if ((index < 1) || + (index > sc->sc_noport)) { + err = USB_ERR_IOERROR; + goto done; + } + port = OHCI_RH_PORT_STATUS(index); + switch (value) { + case UHF_PORT_ENABLE: + OWRITE4(sc, port, UPS_CURRENT_CONNECT_STATUS); + break; + case UHF_PORT_SUSPEND: + OWRITE4(sc, port, UPS_OVERCURRENT_INDICATOR); + break; + case UHF_PORT_POWER: + /* Yes, writing to the LOW_SPEED bit clears power. */ + OWRITE4(sc, port, UPS_LOW_SPEED); + break; + case UHF_C_PORT_CONNECTION: + OWRITE4(sc, port, UPS_C_CONNECT_STATUS << 16); + break; + case UHF_C_PORT_ENABLE: + OWRITE4(sc, port, UPS_C_PORT_ENABLED << 16); + break; + case UHF_C_PORT_SUSPEND: + OWRITE4(sc, port, UPS_C_SUSPEND << 16); + break; + case UHF_C_PORT_OVER_CURRENT: + OWRITE4(sc, port, UPS_C_OVERCURRENT_INDICATOR << 16); + break; + case UHF_C_PORT_RESET: + OWRITE4(sc, port, UPS_C_PORT_RESET << 16); + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + switch (value) { + case UHF_C_PORT_CONNECTION: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_SUSPEND: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + /* enable RHSC interrupt if condition is cleared. */ + if ((OREAD4(sc, port) >> 16) == 0) + ohci_rhsc_enable(sc); + break; + default: + break; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + v = OREAD4(sc, OHCI_RH_DESCRIPTOR_A); + + sc->sc_hub_desc.hubd = ohci_hubd; + sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport; + USETW(sc->sc_hub_desc.hubd.wHubCharacteristics, + (v & OHCI_NPS ? UHD_PWR_NO_SWITCH : + v & OHCI_PSM ? UHD_PWR_GANGED : UHD_PWR_INDIVIDUAL) + /* XXX overcurrent */ + ); + sc->sc_hub_desc.hubd.bPwrOn2PwrGood = OHCI_GET_POTPGT(v); + v = OREAD4(sc, OHCI_RH_DESCRIPTOR_B); + + for (l = 0; l < sc->sc_noport; l++) { + if (v & 1) { + sc->sc_hub_desc.hubd.DeviceRemovable[l / 8] |= (1 << (l % 8)); + } + v >>= 1; + } + sc->sc_hub_desc.hubd.bDescLength = + 8 + ((sc->sc_noport + 7) / 8); + len = sc->sc_hub_desc.hubd.bDescLength; + break; + + case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): + len = 16; + memset(sc->sc_hub_desc.temp, 0, 16); + break; + case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): + DPRINTFN(9, "get port status i=%d\n", + index); + if ((index < 1) || + (index > sc->sc_noport)) { + err = USB_ERR_IOERROR; + goto done; + } + v = OREAD4(sc, OHCI_RH_PORT_STATUS(index)); + DPRINTFN(9, "port status=0x%04x\n", v); + USETW(sc->sc_hub_desc.ps.wPortStatus, v); + USETW(sc->sc_hub_desc.ps.wPortChange, v >> 16); + len = sizeof(sc->sc_hub_desc.ps); + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): + err = USB_ERR_IOERROR; + goto done; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): + if ((index < 1) || + (index > sc->sc_noport)) { + err = USB_ERR_IOERROR; + goto done; + } + port = OHCI_RH_PORT_STATUS(index); + switch (value) { + case UHF_PORT_ENABLE: + OWRITE4(sc, port, UPS_PORT_ENABLED); + break; + case UHF_PORT_SUSPEND: + OWRITE4(sc, port, UPS_SUSPEND); + break; + case UHF_PORT_RESET: + DPRINTFN(6, "reset port %d\n", index); + OWRITE4(sc, port, UPS_RESET); + for (v = 0;; v++) { + if (v < 12) { + usb_pause_mtx(&sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(USB_PORT_ROOT_RESET_DELAY)); + + if ((OREAD4(sc, port) & UPS_RESET) == 0) { + break; + } + } else { + err = USB_ERR_TIMEOUT; + goto done; + } + } + DPRINTFN(9, "ohci port %d reset, status = 0x%04x\n", + index, OREAD4(sc, port)); + break; + case UHF_PORT_POWER: + DPRINTFN(3, "set port power %d\n", index); + OWRITE4(sc, port, UPS_PORT_POWER); + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + default: + err = USB_ERR_IOERROR; + goto done; + } +done: + *plength = len; + *pptr = ptr; + return (err); +} + +static void +ohci_xfer_setup(struct usb_setup_params *parm) +{ + struct usb_page_search page_info; + struct usb_page_cache *pc; + ohci_softc_t *sc; + struct usb_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t nitd; + uint32_t nqh; + uint32_t n; + + sc = OHCI_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + parm->hc_max_packet_size = 0x500; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = OHCI_PAGE_SIZE; + + /* + * calculate ntd and nqh + */ + if (parm->methods == &ohci_device_ctrl_methods) { + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nitd = 0; + ntd = ((2 * xfer->nframes) + 1 /* STATUS */ + + (xfer->max_data_length / xfer->max_hc_frame_size)); + nqh = 1; + + } else if (parm->methods == &ohci_device_bulk_methods) { + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nitd = 0; + ntd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_hc_frame_size)); + nqh = 1; + + } else if (parm->methods == &ohci_device_intr_methods) { + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nitd = 0; + ntd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_hc_frame_size)); + nqh = 1; + + } else if (parm->methods == &ohci_device_isoc_methods) { + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nitd = ((xfer->max_data_length / OHCI_PAGE_SIZE) + + ((xfer->nframes + OHCI_ITD_NOFFSET - 1) / OHCI_ITD_NOFFSET) + + 1 /* EXTRA */ ); + ntd = 0; + nqh = 1; + + } else { + + usbd_transfer_setup_sub(parm); + + nitd = 0; + ntd = 0; + nqh = 0; + } + +alloc_dma_set: + + if (parm->err) { + return; + } + last_obj = NULL; + + if (usbd_transfer_setup_sub_malloc( + parm, &pc, sizeof(ohci_td_t), + OHCI_TD_ALIGN, ntd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != ntd; n++) { + ohci_td_t *td; + + usbd_get_page(pc + n, 0, &page_info); + + td = page_info.buffer; + + /* init TD */ + td->td_self = htole32(page_info.physaddr); + td->obj_next = last_obj; + td->page_cache = pc + n; + + last_obj = td; + + usb_pc_cpu_flush(pc + n); + } + } + if (usbd_transfer_setup_sub_malloc( + parm, &pc, sizeof(ohci_itd_t), + OHCI_ITD_ALIGN, nitd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nitd; n++) { + ohci_itd_t *itd; + + usbd_get_page(pc + n, 0, &page_info); + + itd = page_info.buffer; + + /* init TD */ + itd->itd_self = htole32(page_info.physaddr); + itd->obj_next = last_obj; + itd->page_cache = pc + n; + + last_obj = itd; + + usb_pc_cpu_flush(pc + n); + } + } + xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj; + + last_obj = NULL; + + if (usbd_transfer_setup_sub_malloc( + parm, &pc, sizeof(ohci_ed_t), + OHCI_ED_ALIGN, nqh)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nqh; n++) { + ohci_ed_t *ed; + + usbd_get_page(pc + n, 0, &page_info); + + ed = page_info.buffer; + + /* init QH */ + ed->ed_self = htole32(page_info.physaddr); + ed->obj_next = last_obj; + ed->page_cache = pc + n; + + last_obj = ed; + + usb_pc_cpu_flush(pc + n); + } + } + xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj; + + if (!xfer->flags_int.curr_dma_set) { + xfer->flags_int.curr_dma_set = 1; + goto alloc_dma_set; + } +} + +static void +ohci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *ep) +{ + ohci_softc_t *sc = OHCI_BUS2SC(udev->bus); + + DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", + ep, udev->address, + edesc->bEndpointAddress, udev->flags.usb_mode, + sc->sc_addr); + + if (udev->flags.usb_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + if (udev->device_index != sc->sc_addr) { + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + ep->methods = &ohci_device_ctrl_methods; + break; + case UE_INTERRUPT: + ep->methods = &ohci_device_intr_methods; + break; + case UE_ISOCHRONOUS: + if (udev->speed == USB_SPEED_FULL) { + ep->methods = &ohci_device_isoc_methods; + } + break; + case UE_BULK: + ep->methods = &ohci_device_bulk_methods; + break; + default: + /* do nothing */ + break; + } + } +} + +static void +ohci_xfer_unsetup(struct usb_xfer *xfer) +{ + return; +} + +static void +ohci_get_dma_delay(struct usb_device *udev, uint32_t *pus) +{ + /* + * Wait until hardware has finished any possible use of the + * transfer descriptor(s) and QH + */ + *pus = (1125); /* microseconds */ +} + +static void +ohci_device_resume(struct usb_device *udev) +{ + struct ohci_softc *sc = OHCI_BUS2SC(udev->bus); + struct usb_xfer *xfer; + struct usb_pipe_methods *methods; + ohci_ed_t *ed; + + DPRINTF("\n"); + + USB_BUS_LOCK(udev->bus); + + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + + if (xfer->xroot->udev == udev) { + + methods = xfer->endpoint->methods; + ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + if (methods == &ohci_device_bulk_methods) { + OHCI_APPEND_QH(ed, sc->sc_bulk_p_last); + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); + } + if (methods == &ohci_device_ctrl_methods) { + OHCI_APPEND_QH(ed, sc->sc_ctrl_p_last); + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); + } + if (methods == &ohci_device_intr_methods) { + OHCI_APPEND_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]); + } + } + } + + USB_BUS_UNLOCK(udev->bus); + + return; +} + +static void +ohci_device_suspend(struct usb_device *udev) +{ + struct ohci_softc *sc = OHCI_BUS2SC(udev->bus); + struct usb_xfer *xfer; + struct usb_pipe_methods *methods; + ohci_ed_t *ed; + + DPRINTF("\n"); + + USB_BUS_LOCK(udev->bus); + + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + + if (xfer->xroot->udev == udev) { + + methods = xfer->endpoint->methods; + ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + if (methods == &ohci_device_bulk_methods) { + OHCI_REMOVE_QH(ed, sc->sc_bulk_p_last); + } + if (methods == &ohci_device_ctrl_methods) { + OHCI_REMOVE_QH(ed, sc->sc_ctrl_p_last); + } + if (methods == &ohci_device_intr_methods) { + OHCI_REMOVE_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]); + } + } + } + + USB_BUS_UNLOCK(udev->bus); + + return; +} + +static void +ohci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) +{ + struct ohci_softc *sc = OHCI_BUS2SC(bus); + + switch (state) { + case USB_HW_POWER_SUSPEND: + case USB_HW_POWER_SHUTDOWN: + ohci_suspend(sc); + break; + case USB_HW_POWER_RESUME: + ohci_resume(sc); + break; + default: + break; + } +} + +static void +ohci_set_hw_power(struct usb_bus *bus) +{ + struct ohci_softc *sc = OHCI_BUS2SC(bus); + uint32_t temp; + uint32_t flags; + + DPRINTF("\n"); + + USB_BUS_LOCK(bus); + + flags = bus->hw_power_state; + + temp = OREAD4(sc, OHCI_CONTROL); + temp &= ~(OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE); + + if (flags & USB_HW_POWER_CONTROL) + temp |= OHCI_CLE; + + if (flags & USB_HW_POWER_BULK) + temp |= OHCI_BLE; + + if (flags & USB_HW_POWER_INTERRUPT) + temp |= OHCI_PLE; + + if (flags & USB_HW_POWER_ISOC) + temp |= OHCI_IE | OHCI_PLE; + + OWRITE4(sc, OHCI_CONTROL, temp); + + USB_BUS_UNLOCK(bus); + + return; +} + +struct usb_bus_methods ohci_bus_methods = +{ + .endpoint_init = ohci_ep_init, + .xfer_setup = ohci_xfer_setup, + .xfer_unsetup = ohci_xfer_unsetup, + .get_dma_delay = ohci_get_dma_delay, + .device_resume = ohci_device_resume, + .device_suspend = ohci_device_suspend, + .set_hw_power = ohci_set_hw_power, + .set_hw_power_sleep = ohci_set_hw_power_sleep, + .roothub_exec = ohci_roothub_exec, + .xfer_poll = ohci_do_poll, +}; diff --git a/sys/bus/u4b/controller/ohci.h b/sys/bus/u4b/controller/ohci.h new file mode 100644 index 0000000000..9d8ac7dc83 --- /dev/null +++ b/sys/bus/u4b/controller/ohci.h @@ -0,0 +1,263 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _OHCI_H_ +#define _OHCI_H_ + +#define OHCI_MAX_DEVICES MIN(USB_MAX_DEVICES, 128) + +#define OHCI_NO_INTRS 32 +#define OHCI_HCCA_SIZE 256 + +/* Structures alignment (bytes) */ +#define OHCI_HCCA_ALIGN 256 +#define OHCI_ED_ALIGN 16 +#define OHCI_TD_ALIGN 16 +#define OHCI_ITD_ALIGN 32 + +#define OHCI_PAGE_SIZE 0x1000 +#define OHCI_PAGE(x) ((x) &~ 0xfff) +#define OHCI_PAGE_OFFSET(x) ((x) & 0xfff) +#define OHCI_PAGE_MASK(x) ((x) & 0xfff) + +#if ((USB_PAGE_SIZE < OHCI_ED_ALIGN) || (OHCI_ED_ALIGN == 0) || \ + (USB_PAGE_SIZE < OHCI_TD_ALIGN) || (OHCI_TD_ALIGN == 0) || \ + (USB_PAGE_SIZE < OHCI_ITD_ALIGN) || (OHCI_ITD_ALIGN == 0) || \ + (USB_PAGE_SIZE < OHCI_PAGE_SIZE) || (OHCI_PAGE_SIZE == 0)) +#error "Invalid USB page size!" +#endif + +#define OHCI_VIRTUAL_FRAMELIST_COUNT 128/* dummy */ + +#if (OHCI_VIRTUAL_FRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER) +#error "maximum number of full-speed isochronous frames is higher than supported!" +#endif + +struct ohci_hcca { + volatile uint32_t hcca_interrupt_table[OHCI_NO_INTRS]; + volatile uint32_t hcca_frame_number; + volatile uint32_t hcca_done_head; +#define OHCI_DONE_INTRS 1 +} __aligned(OHCI_HCCA_ALIGN); + +typedef struct ohci_hcca ohci_hcca_t; + +struct ohci_ed { + volatile uint32_t ed_flags; +#define OHCI_ED_GET_FA(s) ((s) & 0x7f) +#define OHCI_ED_ADDRMASK 0x0000007f +#define OHCI_ED_SET_FA(s) (s) +#define OHCI_ED_GET_EN(s) (((s) >> 7) & 0xf) +#define OHCI_ED_SET_EN(s) ((s) << 7) +#define OHCI_ED_DIR_MASK 0x00001800 +#define OHCI_ED_DIR_TD 0x00000000 +#define OHCI_ED_DIR_OUT 0x00000800 +#define OHCI_ED_DIR_IN 0x00001000 +#define OHCI_ED_SPEED 0x00002000 +#define OHCI_ED_SKIP 0x00004000 +#define OHCI_ED_FORMAT_GEN 0x00000000 +#define OHCI_ED_FORMAT_ISO 0x00008000 +#define OHCI_ED_GET_MAXP(s) (((s) >> 16) & 0x07ff) +#define OHCI_ED_SET_MAXP(s) ((s) << 16) +#define OHCI_ED_MAXPMASK (0x7ff << 16) + volatile uint32_t ed_tailp; + volatile uint32_t ed_headp; +#define OHCI_HALTED 0x00000001 +#define OHCI_TOGGLECARRY 0x00000002 +#define OHCI_HEADMASK 0xfffffffc + volatile uint32_t ed_next; +/* + * Extra information needed: + */ + struct ohci_ed *next; + struct ohci_ed *prev; + struct ohci_ed *obj_next; + struct usb_page_cache *page_cache; + uint32_t ed_self; +} __aligned(OHCI_ED_ALIGN); + +typedef struct ohci_ed ohci_ed_t; + +struct ohci_td { + volatile uint32_t td_flags; +#define OHCI_TD_R 0x00040000 /* Buffer Rounding */ +#define OHCI_TD_DP_MASK 0x00180000 /* Direction / PID */ +#define OHCI_TD_SETUP 0x00000000 +#define OHCI_TD_OUT 0x00080000 +#define OHCI_TD_IN 0x00100000 +#define OHCI_TD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */ +#define OHCI_TD_SET_DI(x) ((x) << 21) +#define OHCI_TD_NOINTR 0x00e00000 +#define OHCI_TD_INTR_MASK 0x00e00000 +#define OHCI_TD_TOGGLE_CARRY 0x00000000 +#define OHCI_TD_TOGGLE_0 0x02000000 +#define OHCI_TD_TOGGLE_1 0x03000000 +#define OHCI_TD_TOGGLE_MASK 0x03000000 +#define OHCI_TD_GET_EC(x) (((x) >> 26) & 3) /* Error Count */ +#define OHCI_TD_GET_CC(x) ((x) >> 28) /* Condition Code */ +#define OHCI_TD_SET_CC(x) ((x) << 28) +#define OHCI_TD_NOCC 0xf0000000 + volatile uint32_t td_cbp; /* Current Buffer Pointer */ + volatile uint32_t td_next; /* Next TD */ +#define OHCI_TD_NEXT_END 0 + volatile uint32_t td_be; /* Buffer End */ +/* + * Extra information needed: + */ + struct ohci_td *obj_next; + struct ohci_td *alt_next; + struct usb_page_cache *page_cache; + uint32_t td_self; + uint16_t len; +} __aligned(OHCI_TD_ALIGN); + +typedef struct ohci_td ohci_td_t; + +struct ohci_itd { + volatile uint32_t itd_flags; +#define OHCI_ITD_GET_SF(x) ((x) & 0x0000ffff) +#define OHCI_ITD_SET_SF(x) ((x) & 0xffff) +#define OHCI_ITD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */ +#define OHCI_ITD_SET_DI(x) ((x) << 21) +#define OHCI_ITD_NOINTR 0x00e00000 +#define OHCI_ITD_GET_FC(x) ((((x) >> 24) & 7)+1) /* Frame Count */ +#define OHCI_ITD_SET_FC(x) (((x)-1) << 24) +#define OHCI_ITD_GET_CC(x) ((x) >> 28) /* Condition Code */ +#define OHCI_ITD_NOCC 0xf0000000 +#define OHCI_ITD_NOFFSET 8 + volatile uint32_t itd_bp0; /* Buffer Page 0 */ + volatile uint32_t itd_next; /* Next ITD */ + volatile uint32_t itd_be; /* Buffer End */ + volatile uint16_t itd_offset[OHCI_ITD_NOFFSET]; /* Buffer offsets and + * Status */ +#define OHCI_ITD_PAGE_SELECT 0x00001000 +#define OHCI_ITD_MK_OFFS(len) (0xe000 | ((len) & 0x1fff)) +#define OHCI_ITD_PSW_LENGTH(x) ((x) & 0xfff) /* Transfer length */ +#define OHCI_ITD_PSW_GET_CC(x) ((x) >> 12) /* Condition Code */ +/* + * Extra information needed: + */ + struct ohci_itd *obj_next; + struct usb_page_cache *page_cache; + uint32_t itd_self; + uint8_t frames; +} __aligned(OHCI_ITD_ALIGN); + +typedef struct ohci_itd ohci_itd_t; + +#define OHCI_CC_NO_ERROR 0 +#define OHCI_CC_CRC 1 +#define OHCI_CC_BIT_STUFFING 2 +#define OHCI_CC_DATA_TOGGLE_MISMATCH 3 +#define OHCI_CC_STALL 4 +#define OHCI_CC_DEVICE_NOT_RESPONDING 5 +#define OHCI_CC_PID_CHECK_FAILURE 6 +#define OHCI_CC_UNEXPECTED_PID 7 +#define OHCI_CC_DATA_OVERRUN 8 +#define OHCI_CC_DATA_UNDERRUN 9 +#define OHCI_CC_BUFFER_OVERRUN 12 +#define OHCI_CC_BUFFER_UNDERRUN 13 +#define OHCI_CC_NOT_ACCESSED 15 + +/* Some delay needed when changing certain registers. */ +#define OHCI_ENABLE_POWER_DELAY 5 +#define OHCI_READ_DESC_DELAY 5 + +#define OHCI_NO_EDS (2*OHCI_NO_INTRS) + +struct ohci_hw_softc { + struct usb_page_cache hcca_pc; + struct usb_page_cache ctrl_start_pc; + struct usb_page_cache bulk_start_pc; + struct usb_page_cache isoc_start_pc; + struct usb_page_cache intr_start_pc[OHCI_NO_EDS]; + + struct usb_page hcca_pg; + struct usb_page ctrl_start_pg; + struct usb_page bulk_start_pg; + struct usb_page isoc_start_pg; + struct usb_page intr_start_pg[OHCI_NO_EDS]; +}; + +struct ohci_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union ohci_hub_desc { + struct usb_status stat; + struct usb_port_status ps; + struct usb_hub_descriptor hubd; + uint8_t temp[128]; +}; + +typedef struct ohci_softc { + struct ohci_hw_softc sc_hw; + struct usb_bus sc_bus; /* base device */ + struct usb_callout sc_tmo_rhsc; + union ohci_hub_desc sc_hub_desc; + + struct usb_device *sc_devices[OHCI_MAX_DEVICES]; + struct resource *sc_io_res; + struct resource *sc_irq_res; + struct ohci_hcca *sc_hcca_p; + struct ohci_ed *sc_ctrl_p_last; + struct ohci_ed *sc_bulk_p_last; + struct ohci_ed *sc_isoc_p_last; + struct ohci_ed *sc_intr_p_last[OHCI_NO_EDS]; + void *sc_intr_hdl; + device_t sc_dev; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + uint32_t sc_eintrs; /* enabled interrupts */ + + uint16_t sc_intr_stat[OHCI_NO_EDS]; + uint16_t sc_id_vendor; + + uint8_t sc_noport; + uint8_t sc_addr; /* device address */ + uint8_t sc_conf; /* device configuration */ + uint8_t sc_hub_idata[32]; + + char sc_vendor[16]; + +} ohci_softc_t; + +usb_bus_mem_cb_t ohci_iterate_hw_softc; + +usb_error_t ohci_init(ohci_softc_t *sc); +void ohci_detach(struct ohci_softc *sc); +void ohci_interrupt(ohci_softc_t *sc); + +#endif /* _OHCI_H_ */ diff --git a/sys/bus/u4b/controller/ohci_atmelarm.c b/sys/bus/u4b/controller/ohci_atmelarm.c new file mode 100644 index 0000000000..643b4d17bc --- /dev/null +++ b/sys/bus/u4b/controller/ohci_atmelarm.c @@ -0,0 +1,240 @@ +/*- + * Copyright (c) 2006 M. Warner Losh. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#define MEM_RID 0 + +static device_probe_t ohci_atmelarm_probe; +static device_attach_t ohci_atmelarm_attach; +static device_detach_t ohci_atmelarm_detach; + +struct at91_ohci_softc { + struct ohci_softc sc_ohci; /* must be first */ + struct at91_pmc_clock *iclk; + struct at91_pmc_clock *fclk; +}; + +static int +ohci_atmelarm_probe(device_t dev) +{ + device_set_desc(dev, "AT91 integrated OHCI controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +ohci_atmelarm_attach(device_t dev) +{ + struct at91_ohci_softc *sc = device_get_softc(dev); + int err; + int rid; + + /* initialise some bus fields */ + sc->sc_ohci.sc_bus.parent = dev; + sc->sc_ohci.sc_bus.devices = sc->sc_ohci.sc_devices; + sc->sc_ohci.sc_bus.devices_max = OHCI_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_ohci.sc_bus, + USB_GET_DMA_TAG(dev), &ohci_iterate_hw_softc)) { + return (ENOMEM); + } + sc->iclk = at91_pmc_clock_ref("ohci_clk"); + sc->fclk = at91_pmc_clock_ref("uhpck"); + + sc->sc_ohci.sc_dev = dev; + + rid = MEM_RID; + sc->sc_ohci.sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &rid, RF_ACTIVE); + + if (!(sc->sc_ohci.sc_io_res)) { + err = ENOMEM; + goto error; + } + sc->sc_ohci.sc_io_tag = rman_get_bustag(sc->sc_ohci.sc_io_res); + sc->sc_ohci.sc_io_hdl = rman_get_bushandle(sc->sc_ohci.sc_io_res); + sc->sc_ohci.sc_io_size = rman_get_size(sc->sc_ohci.sc_io_res); + + rid = 0; + sc->sc_ohci.sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_ACTIVE); + if (!(sc->sc_ohci.sc_irq_res)) { + goto error; + } + sc->sc_ohci.sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (!(sc->sc_ohci.sc_bus.bdev)) { + goto error; + } + device_set_ivars(sc->sc_ohci.sc_bus.bdev, &sc->sc_ohci.sc_bus); + + strlcpy(sc->sc_ohci.sc_vendor, "Atmel", sizeof(sc->sc_ohci.sc_vendor)); + +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(dev, sc->sc_ohci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)ohci_interrupt, sc, &sc->sc_ohci.sc_intr_hdl); +#else + err = bus_setup_intr(dev, sc->sc_ohci.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (driver_intr_t *)ohci_interrupt, sc, &sc->sc_ohci.sc_intr_hdl); +#endif + if (err) { + sc->sc_ohci.sc_intr_hdl = NULL; + goto error; + } + /* + * turn on the clocks from the AT91's point of view. Keep the unit in reset. + */ + at91_pmc_clock_enable(sc->iclk); + at91_pmc_clock_enable(sc->fclk); + bus_space_write_4(sc->sc_ohci.sc_io_tag, sc->sc_ohci.sc_io_hdl, + OHCI_CONTROL, 0); + + err = ohci_init(&sc->sc_ohci); + if (!err) { + err = device_probe_and_attach(sc->sc_ohci.sc_bus.bdev); + } + if (err) { + goto error; + } + return (0); + +error: + ohci_atmelarm_detach(dev); + return (ENXIO); +} + +static int +ohci_atmelarm_detach(device_t dev) +{ + struct at91_ohci_softc *sc = device_get_softc(dev); + device_t bdev; + int err; + + if (sc->sc_ohci.sc_bus.bdev) { + bdev = sc->sc_ohci.sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(dev); + + /* + * Put the controller into reset, then disable clocks and do + * the MI tear down. We have to disable the clocks/hardware + * after we do the rest of the teardown. We also disable the + * clocks in the opposite order we acquire them, but that + * doesn't seem to be absolutely necessary. We free up the + * clocks after we disable them, so the system could, in + * theory, reuse them. + */ + bus_space_write_4(sc->sc_ohci.sc_io_tag, sc->sc_ohci.sc_io_hdl, + OHCI_CONTROL, 0); + + at91_pmc_clock_disable(sc->fclk); + at91_pmc_clock_disable(sc->iclk); + at91_pmc_clock_deref(sc->fclk); + at91_pmc_clock_deref(sc->iclk); + + if (sc->sc_ohci.sc_irq_res && sc->sc_ohci.sc_intr_hdl) { + /* + * only call ohci_detach() after ohci_init() + */ + ohci_detach(&sc->sc_ohci); + + err = bus_teardown_intr(dev, sc->sc_ohci.sc_irq_res, sc->sc_ohci.sc_intr_hdl); + sc->sc_ohci.sc_intr_hdl = NULL; + } + if (sc->sc_ohci.sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_ohci.sc_irq_res); + sc->sc_ohci.sc_irq_res = NULL; + } + if (sc->sc_ohci.sc_io_res) { + bus_release_resource(dev, SYS_RES_MEMORY, MEM_RID, + sc->sc_ohci.sc_io_res); + sc->sc_ohci.sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_ohci.sc_bus, &ohci_iterate_hw_softc); + + return (0); +} + +static device_method_t ohci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ohci_atmelarm_probe), + DEVMETHOD(device_attach, ohci_atmelarm_attach), + DEVMETHOD(device_detach, ohci_atmelarm_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD_END +}; + +static driver_t ohci_driver = { + .name = "ohci", + .methods = ohci_methods, + .size = sizeof(struct at91_ohci_softc), +}; + +static devclass_t ohci_devclass; + +DRIVER_MODULE(ohci, atmelarm, ohci_driver, ohci_devclass, 0, 0); +MODULE_DEPEND(ohci, usb, 1, 1, 1); diff --git a/sys/bus/u4b/controller/ohci_pci.c b/sys/bus/u4b/controller/ohci_pci.c new file mode 100644 index 0000000000..864376b920 --- /dev/null +++ b/sys/bus/u4b/controller/ohci_pci.c @@ -0,0 +1,389 @@ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (augustss@carlstedt.se) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * USB Open Host Controller driver. + * + * OHCI spec: http://www.intel.com/design/usb/ohci11d.pdf + */ + +/* The low level controller code for OHCI has been split into + * PCI probes and OHCI specific code. This was done to facilitate the + * sharing of code between *BSD's + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "usb_if.h" + +#define PCI_OHCI_VENDORID_ACERLABS 0x10b9 +#define PCI_OHCI_VENDORID_AMD 0x1022 +#define PCI_OHCI_VENDORID_APPLE 0x106b +#define PCI_OHCI_VENDORID_ATI 0x1002 +#define PCI_OHCI_VENDORID_CMDTECH 0x1095 +#define PCI_OHCI_VENDORID_NEC 0x1033 +#define PCI_OHCI_VENDORID_NVIDIA 0x12D2 +#define PCI_OHCI_VENDORID_NVIDIA2 0x10DE +#define PCI_OHCI_VENDORID_OPTI 0x1045 +#define PCI_OHCI_VENDORID_SIS 0x1039 +#define PCI_OHCI_VENDORID_SUN 0x108e + +#define PCI_OHCI_BASE_REG 0x10 + +static device_probe_t ohci_pci_probe; +static device_attach_t ohci_pci_attach; +static device_detach_t ohci_pci_detach; +static usb_take_controller_t ohci_pci_take_controller; + +static int +ohci_pci_take_controller(device_t self) +{ + uint32_t reg; + uint32_t int_line; + + if (pci_get_powerstate(self) != PCI_POWERSTATE_D0) { + device_printf(self, "chip is in D%d mode " + "-- setting to D0\n", pci_get_powerstate(self)); + reg = pci_read_config(self, PCI_CBMEM, 4); + int_line = pci_read_config(self, PCIR_INTLINE, 4); + pci_set_powerstate(self, PCI_POWERSTATE_D0); + pci_write_config(self, PCI_CBMEM, reg, 4); + pci_write_config(self, PCIR_INTLINE, int_line, 4); + } + return (0); +} + +static const char * +ohci_pci_match(device_t self) +{ + uint32_t device_id = pci_get_devid(self); + + switch (device_id) { + case 0x523710b9: + return ("AcerLabs M5237 (Aladdin-V) USB controller"); + + case 0x740c1022: + return ("AMD-756 USB Controller"); + + case 0x74141022: + return ("AMD-766 USB Controller"); + + case 0x43741002: + return "ATI SB400 USB Controller"; + case 0x43751002: + return "ATI SB400 USB Controller"; + + case 0x06701095: + return ("CMD Tech 670 (USB0670) USB controller"); + + case 0x06731095: + return ("CMD Tech 673 (USB0673) USB controller"); + + case 0xc8611045: + return ("OPTi 82C861 (FireLink) USB controller"); + + case 0x00351033: + return ("NEC uPD 9210 USB controller"); + + case 0x00d710de: + return ("nVidia nForce3 USB Controller"); + + case 0x036c10de: + return ("nVidia nForce MCP55 USB Controller"); + case 0x03f110de: + return ("nVidia nForce MCP61 USB Controller"); + case 0x0aa510de: + return ("nVidia nForce MCP79 USB Controller"); + case 0x0aa710de: + return ("nVidia nForce MCP79 USB Controller"); + case 0x0aa810de: + return ("nVidia nForce MCP79 USB Controller"); + + case 0x70011039: + return ("SiS 5571 USB controller"); + + case 0x1103108e: + return "Sun PCIO-2 USB controller"; + + case 0x0019106b: + return ("Apple KeyLargo USB controller"); + + default: + break; + } + if ((pci_get_class(self) == PCIC_SERIALBUS) && + (pci_get_subclass(self) == PCIS_SERIALBUS_USB) && + (pci_get_progif(self) == PCI_INTERFACE_OHCI)) { + return ("OHCI (generic) USB controller"); + } + return (NULL); +} + +static int +ohci_pci_probe(device_t self) +{ + const char *desc = ohci_pci_match(self); + + if (desc) { + device_set_desc(self, desc); + return (0); + } else { + return (ENXIO); + } +} + +static int +ohci_pci_attach(device_t self) +{ + ohci_softc_t *sc = device_get_softc(self); + int rid; + int err; + + /* initialise some bus fields */ + sc->sc_bus.parent = self; + sc->sc_bus.devices = sc->sc_devices; + sc->sc_bus.devices_max = OHCI_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), + &ohci_iterate_hw_softc)) { + return (ENOMEM); + } + sc->sc_dev = self; + + pci_enable_busmaster(self); + + /* + * Some Sun PCIO-2 USB controllers have their intpin register + * bogusly set to 0, although it should be 4. Correct that. + */ + if (pci_get_devid(self) == 0x1103108e && pci_get_intpin(self) == 0) + pci_set_intpin(self, 4); + + rid = PCI_CBMEM; + sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (!sc->sc_io_res) { + device_printf(self, "Could not map memory\n"); + goto error; + } + sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res); + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + goto error; + } + sc->sc_bus.bdev = device_add_child(self, "usbus", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + /* + * ohci_pci_match will never return NULL if ohci_pci_probe + * succeeded + */ + device_set_desc(sc->sc_bus.bdev, ohci_pci_match(self)); + switch (pci_get_vendor(self)) { + case PCI_OHCI_VENDORID_ACERLABS: + sprintf(sc->sc_vendor, "AcerLabs"); + break; + case PCI_OHCI_VENDORID_AMD: + sprintf(sc->sc_vendor, "AMD"); + break; + case PCI_OHCI_VENDORID_APPLE: + sprintf(sc->sc_vendor, "Apple"); + break; + case PCI_OHCI_VENDORID_ATI: + sprintf(sc->sc_vendor, "ATI"); + break; + case PCI_OHCI_VENDORID_CMDTECH: + sprintf(sc->sc_vendor, "CMDTECH"); + break; + case PCI_OHCI_VENDORID_NEC: + sprintf(sc->sc_vendor, "NEC"); + break; + case PCI_OHCI_VENDORID_NVIDIA: + case PCI_OHCI_VENDORID_NVIDIA2: + sprintf(sc->sc_vendor, "nVidia"); + break; + case PCI_OHCI_VENDORID_OPTI: + sprintf(sc->sc_vendor, "OPTi"); + break; + case PCI_OHCI_VENDORID_SIS: + sprintf(sc->sc_vendor, "SiS"); + break; + case PCI_OHCI_VENDORID_SUN: + sprintf(sc->sc_vendor, "SUN"); + break; + default: + if (bootverbose) { + device_printf(self, "(New OHCI DeviceId=0x%08x)\n", + pci_get_devid(self)); + } + sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); + } + + /* sc->sc_bus.usbrev; set by ohci_init() */ + +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)ohci_interrupt, sc, &sc->sc_intr_hdl); +#else + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (driver_intr_t *)ohci_interrupt, sc, &sc->sc_intr_hdl); +#endif + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->sc_intr_hdl = NULL; + goto error; + } + err = ohci_init(sc); + if (!err) { + err = device_probe_and_attach(sc->sc_bus.bdev); + } + if (err) { + device_printf(self, "USB init failed\n"); + goto error; + } + return (0); + +error: + ohci_pci_detach(self); + return (ENXIO); +} + +static int +ohci_pci_detach(device_t self) +{ + ohci_softc_t *sc = device_get_softc(self); + device_t bdev; + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(self, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(self); + + pci_disable_busmaster(self); + + if (sc->sc_irq_res && sc->sc_intr_hdl) { + /* + * only call ohci_detach() after ohci_init() + */ + ohci_detach(sc); + + int err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); + + if (err) { + /* XXX or should we panic? */ + device_printf(self, "Could not tear down irq, %d\n", + err); + } + sc->sc_intr_hdl = NULL; + } + if (sc->sc_irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(self, SYS_RES_MEMORY, PCI_CBMEM, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_bus, &ohci_iterate_hw_softc); + + return (0); +} + +static device_method_t ohci_pci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ohci_pci_probe), + DEVMETHOD(device_attach, ohci_pci_attach), + DEVMETHOD(device_detach, ohci_pci_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(usb_take_controller, ohci_pci_take_controller), + + DEVMETHOD_END +}; + +static driver_t ohci_driver = { + .name = "ohci", + .methods = ohci_pci_methods, + .size = sizeof(struct ohci_softc), +}; + +static devclass_t ohci_devclass; + +DRIVER_MODULE(ohci, pci, ohci_driver, ohci_devclass, 0, 0); +MODULE_DEPEND(ohci, usb, 1, 1, 1); diff --git a/sys/bus/u4b/controller/ohci_s3c24x0.c b/sys/bus/u4b/controller/ohci_s3c24x0.c new file mode 100644 index 0000000000..225a2d201e --- /dev/null +++ b/sys/bus/u4b/controller/ohci_s3c24x0.c @@ -0,0 +1,217 @@ +/*- + * Copyright (c) 2006 M. Warner Losh. All rights reserved. + * Copyright (c) 2009 Andrew Turner. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +static device_probe_t ohci_s3c24x0_probe; +static device_attach_t ohci_s3c24x0_attach; +static device_detach_t ohci_s3c24x0_detach; + +static int +ohci_s3c24x0_probe(device_t dev) +{ + device_set_desc(dev, "S3C24x0 integrated OHCI controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +ohci_s3c24x0_attach(device_t dev) +{ + struct ohci_softc *sc = device_get_softc(dev); + int err; + int rid; + + /* initialise some bus fields */ + sc->sc_bus.parent = dev; + sc->sc_bus.devices = sc->sc_devices; + sc->sc_bus.devices_max = OHCI_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(dev), + &ohci_iterate_hw_softc)) { + return (ENOMEM); + } + + sc->sc_dev = dev; + + rid = 0; + sc->sc_io_res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, + &rid, RF_ACTIVE); + + if (!(sc->sc_io_res)) { + err = ENOMEM; + goto error; + } + sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res); + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_ACTIVE); + if (!(sc->sc_irq_res)) { + goto error; + } + sc->sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (!(sc->sc_bus.bdev)) { + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + strlcpy(sc->sc_vendor, "Samsung", sizeof(sc->sc_vendor)); + + err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (void *)ohci_interrupt, sc, &sc->sc_intr_hdl); + if (err) { + sc->sc_intr_hdl = NULL; + goto error; + } + + bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, + OHCI_CONTROL, 0); + + err = ohci_init(sc); + if (!err) { + err = device_probe_and_attach(sc->sc_bus.bdev); + } + if (err) { + goto error; + } + return (0); + +error: + ohci_s3c24x0_detach(dev); + return (ENXIO); +} + +static int +ohci_s3c24x0_detach(device_t dev) +{ + struct ohci_softc *sc = device_get_softc(dev); + device_t bdev; + int err; + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(dev); + + /* + * Put the controller into reset, then disable clocks and do + * the MI tear down. We have to disable the clocks/hardware + * after we do the rest of the teardown. We also disable the + * clocks in the opposite order we acquire them, but that + * doesn't seem to be absolutely necessary. We free up the + * clocks after we disable them, so the system could, in + * theory, reuse them. + */ + bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, + OHCI_CONTROL, 0); + + if (sc->sc_irq_res && sc->sc_intr_hdl) { + /* + * only call ohci_detach() after ohci_init() + */ + ohci_detach(sc); + + err = bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_hdl); + sc->sc_intr_hdl = NULL; + } + if (sc->sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(dev, SYS_RES_MEMORY, 0, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_bus, &ohci_iterate_hw_softc); + + return (0); +} + +static device_method_t ohci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ohci_s3c24x0_probe), + DEVMETHOD(device_attach, ohci_s3c24x0_attach), + DEVMETHOD(device_detach, ohci_s3c24x0_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD_END +}; + +static driver_t ohci_driver = { + .name = "ohci", + .methods = ohci_methods, + .size = sizeof(struct ohci_softc), +}; + +static devclass_t ohci_devclass; + +DRIVER_MODULE(ohci, s3c24x0, ohci_driver, ohci_devclass, 0, 0); +MODULE_DEPEND(ohci, usb, 1, 1, 1); diff --git a/sys/bus/u4b/controller/ohcireg.h b/sys/bus/u4b/controller/ohcireg.h new file mode 100644 index 0000000000..7f14875c62 --- /dev/null +++ b/sys/bus/u4b/controller/ohcireg.h @@ -0,0 +1,124 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _OHCIREG_H_ +#define _OHCIREG_H_ + +/* PCI config registers */ +#define PCI_CBMEM 0x10 /* configuration base memory */ +#define PCI_INTERFACE_OHCI 0x10 + +/* OHCI registers */ +#define OHCI_REVISION 0x00 /* OHCI revision */ +#define OHCI_REV_LO(rev) ((rev) & 0xf) +#define OHCI_REV_HI(rev) (((rev)>>4) & 0xf) +#define OHCI_REV_LEGACY(rev) ((rev) & 0x100) +#define OHCI_CONTROL 0x04 +#define OHCI_CBSR_MASK 0x00000003 /* Control/Bulk Service Ratio */ +#define OHCI_RATIO_1_1 0x00000000 +#define OHCI_RATIO_1_2 0x00000001 +#define OHCI_RATIO_1_3 0x00000002 +#define OHCI_RATIO_1_4 0x00000003 +#define OHCI_PLE 0x00000004 /* Periodic List Enable */ +#define OHCI_IE 0x00000008 /* Isochronous Enable */ +#define OHCI_CLE 0x00000010 /* Control List Enable */ +#define OHCI_BLE 0x00000020 /* Bulk List Enable */ +#define OHCI_HCFS_MASK 0x000000c0 /* HostControllerFunctionalStat + * e */ +#define OHCI_HCFS_RESET 0x00000000 +#define OHCI_HCFS_RESUME 0x00000040 +#define OHCI_HCFS_OPERATIONAL 0x00000080 +#define OHCI_HCFS_SUSPEND 0x000000c0 +#define OHCI_IR 0x00000100 /* Interrupt Routing */ +#define OHCI_RWC 0x00000200 /* Remote Wakeup Connected */ +#define OHCI_RWE 0x00000400 /* Remote Wakeup Enabled */ +#define OHCI_COMMAND_STATUS 0x08 +#define OHCI_HCR 0x00000001 /* Host Controller Reset */ +#define OHCI_CLF 0x00000002 /* Control List Filled */ +#define OHCI_BLF 0x00000004 /* Bulk List Filled */ +#define OHCI_OCR 0x00000008 /* Ownership Change Request */ +#define OHCI_SOC_MASK 0x00030000 /* Scheduling Overrun Count */ +#define OHCI_INTERRUPT_STATUS 0x0c +#define OHCI_SO 0x00000001 /* Scheduling Overrun */ +#define OHCI_WDH 0x00000002 /* Writeback Done Head */ +#define OHCI_SF 0x00000004 /* Start of Frame */ +#define OHCI_RD 0x00000008 /* Resume Detected */ +#define OHCI_UE 0x00000010 /* Unrecoverable Error */ +#define OHCI_FNO 0x00000020 /* Frame Number Overflow */ +#define OHCI_RHSC 0x00000040 /* Root Hub Status Change */ +#define OHCI_OC 0x40000000 /* Ownership Change */ +#define OHCI_MIE 0x80000000 /* Master Interrupt Enable */ +#define OHCI_INTERRUPT_ENABLE 0x10 +#define OHCI_INTERRUPT_DISABLE 0x14 +#define OHCI_HCCA 0x18 +#define OHCI_PERIOD_CURRENT_ED 0x1c +#define OHCI_CONTROL_HEAD_ED 0x20 +#define OHCI_CONTROL_CURRENT_ED 0x24 +#define OHCI_BULK_HEAD_ED 0x28 +#define OHCI_BULK_CURRENT_ED 0x2c +#define OHCI_DONE_HEAD 0x30 +#define OHCI_FM_INTERVAL 0x34 +#define OHCI_GET_IVAL(s) ((s) & 0x3fff) +#define OHCI_GET_FSMPS(s) (((s) >> 16) & 0x7fff) +#define OHCI_FIT 0x80000000 +#define OHCI_FM_REMAINING 0x38 +#define OHCI_FM_NUMBER 0x3c +#define OHCI_PERIODIC_START 0x40 +#define OHCI_LS_THRESHOLD 0x44 +#define OHCI_RH_DESCRIPTOR_A 0x48 +#define OHCI_GET_NDP(s) ((s) & 0xff) +#define OHCI_PSM 0x0100 /* Power Switching Mode */ +#define OHCI_NPS 0x0200 /* No Power Switching */ +#define OHCI_DT 0x0400 /* Device Type */ +#define OHCI_OCPM 0x0800 /* Overcurrent Protection Mode */ +#define OHCI_NOCP 0x1000 /* No Overcurrent Protection */ +#define OHCI_GET_POTPGT(s) ((s) >> 24) +#define OHCI_RH_DESCRIPTOR_B 0x4c +#define OHCI_RH_STATUS 0x50 +#define OHCI_LPS 0x00000001 /* Local Power Status */ +#define OHCI_OCI 0x00000002 /* OverCurrent Indicator */ +#define OHCI_DRWE 0x00008000 /* Device Remote Wakeup Enable */ +#define OHCI_LPSC 0x00010000 /* Local Power Status Change */ +#define OHCI_CCIC 0x00020000 /* OverCurrent Indicator + * Change */ +#define OHCI_CRWE 0x80000000 /* Clear Remote Wakeup Enable */ +#define OHCI_RH_PORT_STATUS(n) (0x50 + ((n)*4)) /* 1 based indexing */ + +#define OHCI_LES (OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE) +#define OHCI_ALL_INTRS (OHCI_SO | OHCI_WDH | OHCI_SF | \ + OHCI_RD | OHCI_UE | OHCI_FNO | \ + OHCI_RHSC | OHCI_OC) +#define OHCI_NORMAL_INTRS (OHCI_WDH | OHCI_RD | OHCI_UE | OHCI_RHSC) + +#define OHCI_FSMPS(i) (((i-210)*6/7) << 16) +#define OHCI_PERIODIC(i) ((i)*9/10) + +#endif /* _OHCIREG_H_ */ diff --git a/sys/bus/u4b/controller/uhci.c b/sys/bus/u4b/controller/uhci.c new file mode 100644 index 0000000000..d9091c98e4 --- /dev/null +++ b/sys/bus/u4b/controller/uhci.c @@ -0,0 +1,3232 @@ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * USB Universal Host Controller driver. + * Handles e.g. PIIX3 and PIIX4. + * + * UHCI spec: http://developer.intel.com/design/USB/UHCI11D.htm + * USB spec: http://www.usb.org/developers/docs/usbspec.zip + * PIIXn spec: ftp://download.intel.com/design/intarch/datashts/29055002.pdf + * ftp://download.intel.com/design/intarch/datashts/29056201.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define USB_DEBUG_VAR uhcidebug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define alt_next next +#define UHCI_BUS2SC(bus) \ + ((uhci_softc_t *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((uhci_softc_t *)0)->sc_bus)))) + +#ifdef USB_DEBUG +static int uhcidebug = 0; +static int uhcinoloop = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uhci, CTLFLAG_RW, 0, "USB uhci"); +SYSCTL_INT(_hw_usb_uhci, OID_AUTO, debug, CTLFLAG_RW, + &uhcidebug, 0, "uhci debug level"); +SYSCTL_INT(_hw_usb_uhci, OID_AUTO, loop, CTLFLAG_RW, + &uhcinoloop, 0, "uhci noloop"); + +TUNABLE_INT("hw.usb.uhci.debug", &uhcidebug); +TUNABLE_INT("hw.usb.uhci.loop", &uhcinoloop); + +static void uhci_dumpregs(uhci_softc_t *sc); +static void uhci_dump_tds(uhci_td_t *td); + +#endif + +#define UBARR(sc) bus_space_barrier((sc)->sc_io_tag, (sc)->sc_io_hdl, 0, (sc)->sc_io_size, \ + BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE) +#define UWRITE1(sc, r, x) \ + do { UBARR(sc); bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \ + } while (/*CONSTCOND*/0) +#define UWRITE2(sc, r, x) \ + do { UBARR(sc); bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \ + } while (/*CONSTCOND*/0) +#define UWRITE4(sc, r, x) \ + do { UBARR(sc); bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \ + } while (/*CONSTCOND*/0) +#define UREAD1(sc, r) (UBARR(sc), bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) +#define UREAD2(sc, r) (UBARR(sc), bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) +#define UREAD4(sc, r) (UBARR(sc), bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) + +#define UHCICMD(sc, cmd) UWRITE2(sc, UHCI_CMD, cmd) +#define UHCISTS(sc) UREAD2(sc, UHCI_STS) + +#define UHCI_RESET_TIMEOUT 100 /* ms, reset timeout */ + +#define UHCI_INTR_ENDPT 1 + +struct uhci_mem_layout { + + struct usb_page_search buf_res; + struct usb_page_search fix_res; + + struct usb_page_cache *buf_pc; + struct usb_page_cache *fix_pc; + + uint32_t buf_offset; + + uint16_t max_frame_size; +}; + +struct uhci_std_temp { + + struct uhci_mem_layout ml; + uhci_td_t *td; + uhci_td_t *td_next; + uint32_t average; + uint32_t td_status; + uint32_t td_token; + uint32_t len; + uint16_t max_frame_size; + uint8_t shortpkt; + uint8_t setup_alt_next; + uint8_t last_frame; +}; + +extern struct usb_bus_methods uhci_bus_methods; +extern struct usb_pipe_methods uhci_device_bulk_methods; +extern struct usb_pipe_methods uhci_device_ctrl_methods; +extern struct usb_pipe_methods uhci_device_intr_methods; +extern struct usb_pipe_methods uhci_device_isoc_methods; + +static uint8_t uhci_restart(uhci_softc_t *sc); +static void uhci_do_poll(struct usb_bus *); +static void uhci_device_done(struct usb_xfer *, usb_error_t); +static void uhci_transfer_intr_enqueue(struct usb_xfer *); +static void uhci_timeout(void *); +static uint8_t uhci_check_transfer(struct usb_xfer *); +static void uhci_root_intr(uhci_softc_t *sc); + +void +uhci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb) +{ + struct uhci_softc *sc = UHCI_BUS2SC(bus); + uint32_t i; + + cb(bus, &sc->sc_hw.pframes_pc, &sc->sc_hw.pframes_pg, + sizeof(uint32_t) * UHCI_FRAMELIST_COUNT, UHCI_FRAMELIST_ALIGN); + + cb(bus, &sc->sc_hw.ls_ctl_start_pc, &sc->sc_hw.ls_ctl_start_pg, + sizeof(uhci_qh_t), UHCI_QH_ALIGN); + + cb(bus, &sc->sc_hw.fs_ctl_start_pc, &sc->sc_hw.fs_ctl_start_pg, + sizeof(uhci_qh_t), UHCI_QH_ALIGN); + + cb(bus, &sc->sc_hw.bulk_start_pc, &sc->sc_hw.bulk_start_pg, + sizeof(uhci_qh_t), UHCI_QH_ALIGN); + + cb(bus, &sc->sc_hw.last_qh_pc, &sc->sc_hw.last_qh_pg, + sizeof(uhci_qh_t), UHCI_QH_ALIGN); + + cb(bus, &sc->sc_hw.last_td_pc, &sc->sc_hw.last_td_pg, + sizeof(uhci_td_t), UHCI_TD_ALIGN); + + for (i = 0; i != UHCI_VFRAMELIST_COUNT; i++) { + cb(bus, sc->sc_hw.isoc_start_pc + i, + sc->sc_hw.isoc_start_pg + i, + sizeof(uhci_td_t), UHCI_TD_ALIGN); + } + + for (i = 0; i != UHCI_IFRAMELIST_COUNT; i++) { + cb(bus, sc->sc_hw.intr_start_pc + i, + sc->sc_hw.intr_start_pg + i, + sizeof(uhci_qh_t), UHCI_QH_ALIGN); + } +} + +static void +uhci_mem_layout_init(struct uhci_mem_layout *ml, struct usb_xfer *xfer) +{ + ml->buf_pc = xfer->frbuffers + 0; + ml->fix_pc = xfer->buf_fixup; + + ml->buf_offset = 0; + + ml->max_frame_size = xfer->max_frame_size; +} + +static void +uhci_mem_layout_fixup(struct uhci_mem_layout *ml, struct uhci_td *td) +{ + usbd_get_page(ml->buf_pc, ml->buf_offset, &ml->buf_res); + + if (ml->buf_res.length < td->len) { + + /* need to do a fixup */ + + usbd_get_page(ml->fix_pc, 0, &ml->fix_res); + + td->td_buffer = htole32(ml->fix_res.physaddr); + + /* + * The UHCI driver cannot handle + * page crossings, so a fixup is + * needed: + * + * +----+----+ - - - + * | YYY|Y | + * +----+----+ - - - + * \ \ + * \ \ + * +----+ + * |YYYY| (fixup) + * +----+ + */ + + if ((td->td_token & htole32(UHCI_TD_PID)) == + htole32(UHCI_TD_PID_IN)) { + td->fix_pc = ml->fix_pc; + usb_pc_cpu_invalidate(ml->fix_pc); + + } else { + td->fix_pc = NULL; + + /* copy data to fixup location */ + + usbd_copy_out(ml->buf_pc, ml->buf_offset, + ml->fix_res.buffer, td->len); + + usb_pc_cpu_flush(ml->fix_pc); + } + + /* prepare next fixup */ + + ml->fix_pc++; + + } else { + + td->td_buffer = htole32(ml->buf_res.physaddr); + td->fix_pc = NULL; + } + + /* prepare next data location */ + + ml->buf_offset += td->len; +} + +/* + * Return values: + * 0: Success + * Else: Failure + */ +static uint8_t +uhci_restart(uhci_softc_t *sc) +{ + struct usb_page_search buf_res; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + if (UREAD2(sc, UHCI_CMD) & UHCI_CMD_RS) { + DPRINTFN(2, "Already started\n"); + return (0); + } + + DPRINTFN(2, "Restarting\n"); + + usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); + + /* Reload fresh base address */ + UWRITE4(sc, UHCI_FLBASEADDR, buf_res.physaddr); + + /* + * Assume 64 byte packets at frame end and start HC controller: + */ + UHCICMD(sc, (UHCI_CMD_MAXP | UHCI_CMD_RS)); + + /* wait 10 milliseconds */ + + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); + + /* check that controller has started */ + + if (UREAD2(sc, UHCI_STS) & UHCI_STS_HCH) { + DPRINTFN(2, "Failed\n"); + return (1); + } + return (0); +} + +void +uhci_reset(uhci_softc_t *sc) +{ + uint16_t n; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + DPRINTF("resetting the HC\n"); + + /* disable interrupts */ + + UWRITE2(sc, UHCI_INTR, 0); + + /* global reset */ + + UHCICMD(sc, UHCI_CMD_GRESET); + + /* wait */ + + usb_pause_mtx(&sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(USB_BUS_RESET_DELAY)); + + /* terminate all transfers */ + + UHCICMD(sc, UHCI_CMD_HCRESET); + + /* the reset bit goes low when the controller is done */ + + n = UHCI_RESET_TIMEOUT; + while (n--) { + /* wait one millisecond */ + + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000); + + if (!(UREAD2(sc, UHCI_CMD) & UHCI_CMD_HCRESET)) { + goto done_1; + } + } + + device_printf(sc->sc_bus.bdev, + "controller did not reset\n"); + +done_1: + + n = 10; + while (n--) { + /* wait one millisecond */ + + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000); + + /* check if HC is stopped */ + if (UREAD2(sc, UHCI_STS) & UHCI_STS_HCH) { + goto done_2; + } + } + + device_printf(sc->sc_bus.bdev, + "controller did not stop\n"); + +done_2: + + /* reset frame number */ + UWRITE2(sc, UHCI_FRNUM, 0); + /* set default SOF value */ + UWRITE1(sc, UHCI_SOF, 0x40); + + USB_BUS_UNLOCK(&sc->sc_bus); + + /* stop root interrupt */ + usb_callout_drain(&sc->sc_root_intr); + + USB_BUS_LOCK(&sc->sc_bus); +} + +static void +uhci_start(uhci_softc_t *sc) +{ + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + DPRINTFN(2, "enabling\n"); + + /* enable interrupts */ + + UWRITE2(sc, UHCI_INTR, + (UHCI_INTR_TOCRCIE | + UHCI_INTR_RIE | + UHCI_INTR_IOCE | + UHCI_INTR_SPIE)); + + if (uhci_restart(sc)) { + device_printf(sc->sc_bus.bdev, + "cannot start HC controller\n"); + } + + /* start root interrupt */ + uhci_root_intr(sc); +} + +static struct uhci_qh * +uhci_init_qh(struct usb_page_cache *pc) +{ + struct usb_page_search buf_res; + struct uhci_qh *qh; + + usbd_get_page(pc, 0, &buf_res); + + qh = buf_res.buffer; + + qh->qh_self = + htole32(buf_res.physaddr) | + htole32(UHCI_PTR_QH); + + qh->page_cache = pc; + + return (qh); +} + +static struct uhci_td * +uhci_init_td(struct usb_page_cache *pc) +{ + struct usb_page_search buf_res; + struct uhci_td *td; + + usbd_get_page(pc, 0, &buf_res); + + td = buf_res.buffer; + + td->td_self = + htole32(buf_res.physaddr) | + htole32(UHCI_PTR_TD); + + td->page_cache = pc; + + return (td); +} + +usb_error_t +uhci_init(uhci_softc_t *sc) +{ + uint16_t bit; + uint16_t x; + uint16_t y; + + DPRINTF("start\n"); + + usb_callout_init_mtx(&sc->sc_root_intr, &sc->sc_bus.bus_mtx, 0); + +#ifdef USB_DEBUG + if (uhcidebug > 2) { + uhci_dumpregs(sc); + } +#endif + /* + * Setup QH's + */ + sc->sc_ls_ctl_p_last = + uhci_init_qh(&sc->sc_hw.ls_ctl_start_pc); + + sc->sc_fs_ctl_p_last = + uhci_init_qh(&sc->sc_hw.fs_ctl_start_pc); + + sc->sc_bulk_p_last = + uhci_init_qh(&sc->sc_hw.bulk_start_pc); +#if 0 + sc->sc_reclaim_qh_p = + sc->sc_fs_ctl_p_last; +#else + /* setup reclaim looping point */ + sc->sc_reclaim_qh_p = + sc->sc_bulk_p_last; +#endif + + sc->sc_last_qh_p = + uhci_init_qh(&sc->sc_hw.last_qh_pc); + + sc->sc_last_td_p = + uhci_init_td(&sc->sc_hw.last_td_pc); + + for (x = 0; x != UHCI_VFRAMELIST_COUNT; x++) { + sc->sc_isoc_p_last[x] = + uhci_init_td(sc->sc_hw.isoc_start_pc + x); + } + + for (x = 0; x != UHCI_IFRAMELIST_COUNT; x++) { + sc->sc_intr_p_last[x] = + uhci_init_qh(sc->sc_hw.intr_start_pc + x); + } + + /* + * the QHs are arranged to give poll intervals that are + * powers of 2 times 1ms + */ + bit = UHCI_IFRAMELIST_COUNT / 2; + while (bit) { + x = bit; + while (x & bit) { + uhci_qh_t *qh_x; + uhci_qh_t *qh_y; + + y = (x ^ bit) | (bit / 2); + + /* + * the next QH has half the poll interval + */ + qh_x = sc->sc_intr_p_last[x]; + qh_y = sc->sc_intr_p_last[y]; + + qh_x->h_next = NULL; + qh_x->qh_h_next = qh_y->qh_self; + qh_x->e_next = NULL; + qh_x->qh_e_next = htole32(UHCI_PTR_T); + x++; + } + bit >>= 1; + } + + if (1) { + uhci_qh_t *qh_ls; + uhci_qh_t *qh_intr; + + qh_ls = sc->sc_ls_ctl_p_last; + qh_intr = sc->sc_intr_p_last[0]; + + /* start QH for interrupt traffic */ + qh_intr->h_next = qh_ls; + qh_intr->qh_h_next = qh_ls->qh_self; + qh_intr->e_next = 0; + qh_intr->qh_e_next = htole32(UHCI_PTR_T); + } + for (x = 0; x != UHCI_VFRAMELIST_COUNT; x++) { + + uhci_td_t *td_x; + uhci_qh_t *qh_intr; + + td_x = sc->sc_isoc_p_last[x]; + qh_intr = sc->sc_intr_p_last[x | (UHCI_IFRAMELIST_COUNT / 2)]; + + /* start TD for isochronous traffic */ + td_x->next = NULL; + td_x->td_next = qh_intr->qh_self; + td_x->td_status = htole32(UHCI_TD_IOS); + td_x->td_token = htole32(0); + td_x->td_buffer = htole32(0); + } + + if (1) { + uhci_qh_t *qh_ls; + uhci_qh_t *qh_fs; + + qh_ls = sc->sc_ls_ctl_p_last; + qh_fs = sc->sc_fs_ctl_p_last; + + /* start QH where low speed control traffic will be queued */ + qh_ls->h_next = qh_fs; + qh_ls->qh_h_next = qh_fs->qh_self; + qh_ls->e_next = 0; + qh_ls->qh_e_next = htole32(UHCI_PTR_T); + } + if (1) { + uhci_qh_t *qh_ctl; + uhci_qh_t *qh_blk; + uhci_qh_t *qh_lst; + uhci_td_t *td_lst; + + qh_ctl = sc->sc_fs_ctl_p_last; + qh_blk = sc->sc_bulk_p_last; + + /* start QH where full speed control traffic will be queued */ + qh_ctl->h_next = qh_blk; + qh_ctl->qh_h_next = qh_blk->qh_self; + qh_ctl->e_next = 0; + qh_ctl->qh_e_next = htole32(UHCI_PTR_T); + + qh_lst = sc->sc_last_qh_p; + + /* start QH where bulk traffic will be queued */ + qh_blk->h_next = qh_lst; + qh_blk->qh_h_next = qh_lst->qh_self; + qh_blk->e_next = 0; + qh_blk->qh_e_next = htole32(UHCI_PTR_T); + + td_lst = sc->sc_last_td_p; + + /* end QH which is used for looping the QHs */ + qh_lst->h_next = 0; + qh_lst->qh_h_next = htole32(UHCI_PTR_T); /* end of QH chain */ + qh_lst->e_next = td_lst; + qh_lst->qh_e_next = td_lst->td_self; + + /* + * end TD which hangs from the last QH, to avoid a bug in the PIIX + * that makes it run berserk otherwise + */ + td_lst->next = 0; + td_lst->td_next = htole32(UHCI_PTR_T); + td_lst->td_status = htole32(0); /* inactive */ + td_lst->td_token = htole32(0); + td_lst->td_buffer = htole32(0); + } + if (1) { + struct usb_page_search buf_res; + uint32_t *pframes; + + usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); + + pframes = buf_res.buffer; + + + /* + * Setup UHCI framelist + * + * Execution order: + * + * pframes -> full speed isochronous -> interrupt QH's -> low + * speed control -> full speed control -> bulk transfers + * + */ + + for (x = 0; x != UHCI_FRAMELIST_COUNT; x++) { + pframes[x] = + sc->sc_isoc_p_last[x % UHCI_VFRAMELIST_COUNT]->td_self; + } + } + /* flush all cache into memory */ + + usb_bus_mem_flush_all(&sc->sc_bus, &uhci_iterate_hw_softc); + + /* set up the bus struct */ + sc->sc_bus.methods = &uhci_bus_methods; + + USB_BUS_LOCK(&sc->sc_bus); + /* reset the controller */ + uhci_reset(sc); + + /* start the controller */ + uhci_start(sc); + USB_BUS_UNLOCK(&sc->sc_bus); + + /* catch lost interrupts */ + uhci_do_poll(&sc->sc_bus); + + return (0); +} + +static void +uhci_suspend(uhci_softc_t *sc) +{ +#ifdef USB_DEBUG + if (uhcidebug > 2) { + uhci_dumpregs(sc); + } +#endif + + USB_BUS_LOCK(&sc->sc_bus); + + /* stop the controller */ + + uhci_reset(sc); + + /* enter global suspend */ + + UHCICMD(sc, UHCI_CMD_EGSM); + + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +uhci_resume(uhci_softc_t *sc) +{ + USB_BUS_LOCK(&sc->sc_bus); + + /* reset the controller */ + + uhci_reset(sc); + + /* force global resume */ + + UHCICMD(sc, UHCI_CMD_FGR); + + /* and start traffic again */ + + uhci_start(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); + +#ifdef USB_DEBUG + if (uhcidebug > 2) + uhci_dumpregs(sc); +#endif + + /* catch lost interrupts */ + uhci_do_poll(&sc->sc_bus); +} + +#ifdef USB_DEBUG +static void +uhci_dumpregs(uhci_softc_t *sc) +{ + DPRINTFN(0, "%s regs: cmd=%04x, sts=%04x, intr=%04x, frnum=%04x, " + "flbase=%08x, sof=%04x, portsc1=%04x, portsc2=%04x\n", + device_get_nameunit(sc->sc_bus.bdev), + UREAD2(sc, UHCI_CMD), + UREAD2(sc, UHCI_STS), + UREAD2(sc, UHCI_INTR), + UREAD2(sc, UHCI_FRNUM), + UREAD4(sc, UHCI_FLBASEADDR), + UREAD1(sc, UHCI_SOF), + UREAD2(sc, UHCI_PORTSC1), + UREAD2(sc, UHCI_PORTSC2)); +} + +static uint8_t +uhci_dump_td(uhci_td_t *p) +{ + uint32_t td_next; + uint32_t td_status; + uint32_t td_token; + uint8_t temp; + + usb_pc_cpu_invalidate(p->page_cache); + + td_next = le32toh(p->td_next); + td_status = le32toh(p->td_status); + td_token = le32toh(p->td_token); + + /* + * Check whether the link pointer in this TD marks the link pointer + * as end of queue: + */ + temp = ((td_next & UHCI_PTR_T) || (td_next == 0)); + + printf("TD(%p) at 0x%08x = link=0x%08x status=0x%08x " + "token=0x%08x buffer=0x%08x\n", + p, + le32toh(p->td_self), + td_next, + td_status, + td_token, + le32toh(p->td_buffer)); + + printf("TD(%p) td_next=%s%s%s td_status=%s%s%s%s%s%s%s%s%s%s%s, errcnt=%d, actlen=%d pid=%02x," + "addr=%d,endpt=%d,D=%d,maxlen=%d\n", + p, + (td_next & 1) ? "-T" : "", + (td_next & 2) ? "-Q" : "", + (td_next & 4) ? "-VF" : "", + (td_status & UHCI_TD_BITSTUFF) ? "-BITSTUFF" : "", + (td_status & UHCI_TD_CRCTO) ? "-CRCTO" : "", + (td_status & UHCI_TD_NAK) ? "-NAK" : "", + (td_status & UHCI_TD_BABBLE) ? "-BABBLE" : "", + (td_status & UHCI_TD_DBUFFER) ? "-DBUFFER" : "", + (td_status & UHCI_TD_STALLED) ? "-STALLED" : "", + (td_status & UHCI_TD_ACTIVE) ? "-ACTIVE" : "", + (td_status & UHCI_TD_IOC) ? "-IOC" : "", + (td_status & UHCI_TD_IOS) ? "-IOS" : "", + (td_status & UHCI_TD_LS) ? "-LS" : "", + (td_status & UHCI_TD_SPD) ? "-SPD" : "", + UHCI_TD_GET_ERRCNT(td_status), + UHCI_TD_GET_ACTLEN(td_status), + UHCI_TD_GET_PID(td_token), + UHCI_TD_GET_DEVADDR(td_token), + UHCI_TD_GET_ENDPT(td_token), + UHCI_TD_GET_DT(td_token), + UHCI_TD_GET_MAXLEN(td_token)); + + return (temp); +} + +static uint8_t +uhci_dump_qh(uhci_qh_t *sqh) +{ + uint8_t temp; + uint32_t qh_h_next; + uint32_t qh_e_next; + + usb_pc_cpu_invalidate(sqh->page_cache); + + qh_h_next = le32toh(sqh->qh_h_next); + qh_e_next = le32toh(sqh->qh_e_next); + + DPRINTFN(0, "QH(%p) at 0x%08x: h_next=0x%08x e_next=0x%08x\n", sqh, + le32toh(sqh->qh_self), qh_h_next, qh_e_next); + + temp = ((((sqh->h_next != NULL) && !(qh_h_next & UHCI_PTR_T)) ? 1 : 0) | + (((sqh->e_next != NULL) && !(qh_e_next & UHCI_PTR_T)) ? 2 : 0)); + + return (temp); +} + +static void +uhci_dump_all(uhci_softc_t *sc) +{ + uhci_dumpregs(sc); + uhci_dump_qh(sc->sc_ls_ctl_p_last); + uhci_dump_qh(sc->sc_fs_ctl_p_last); + uhci_dump_qh(sc->sc_bulk_p_last); + uhci_dump_qh(sc->sc_last_qh_p); +} + +static void +uhci_dump_tds(uhci_td_t *td) +{ + for (; + td != NULL; + td = td->obj_next) { + if (uhci_dump_td(td)) { + break; + } + } +} + +#endif + +/* + * Let the last QH loop back to the full speed control transfer QH. + * This is what intel calls "bandwidth reclamation" and improves + * USB performance a lot for some devices. + * If we are already looping, just count it. + */ +static void +uhci_add_loop(uhci_softc_t *sc) +{ + struct uhci_qh *qh_lst; + struct uhci_qh *qh_rec; + +#ifdef USB_DEBUG + if (uhcinoloop) { + return; + } +#endif + if (++(sc->sc_loops) == 1) { + DPRINTFN(6, "add\n"); + + qh_lst = sc->sc_last_qh_p; + qh_rec = sc->sc_reclaim_qh_p; + + /* NOTE: we don't loop back the soft pointer */ + + qh_lst->qh_h_next = qh_rec->qh_self; + usb_pc_cpu_flush(qh_lst->page_cache); + } +} + +static void +uhci_rem_loop(uhci_softc_t *sc) +{ + struct uhci_qh *qh_lst; + +#ifdef USB_DEBUG + if (uhcinoloop) { + return; + } +#endif + if (--(sc->sc_loops) == 0) { + DPRINTFN(6, "remove\n"); + + qh_lst = sc->sc_last_qh_p; + qh_lst->qh_h_next = htole32(UHCI_PTR_T); + usb_pc_cpu_flush(qh_lst->page_cache); + } +} + +static void +uhci_transfer_intr_enqueue(struct usb_xfer *xfer) +{ + /* check for early completion */ + if (uhci_check_transfer(xfer)) { + return; + } + /* put transfer on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usbd_transfer_timeout_ms(xfer, &uhci_timeout, xfer->timeout); + } +} + +#define UHCI_APPEND_TD(std,last) (last) = _uhci_append_td(std,last) +static uhci_td_t * +_uhci_append_td(uhci_td_t *std, uhci_td_t *last) +{ + DPRINTFN(11, "%p to %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->next = last->next; + std->td_next = last->td_next; + + std->prev = last; + + usb_pc_cpu_flush(std->page_cache); + + /* + * the last->next->prev is never followed: std->next->prev = std; + */ + last->next = std; + last->td_next = std->td_self; + + usb_pc_cpu_flush(last->page_cache); + + return (std); +} + +#define UHCI_APPEND_QH(sqh,last) (last) = _uhci_append_qh(sqh,last) +static uhci_qh_t * +_uhci_append_qh(uhci_qh_t *sqh, uhci_qh_t *last) +{ + DPRINTFN(11, "%p to %p\n", sqh, last); + + if (sqh->h_prev != NULL) { + /* should not happen */ + DPRINTFN(0, "QH already linked!\n"); + return (last); + } + /* (sc->sc_bus.mtx) must be locked */ + + sqh->h_next = last->h_next; + sqh->qh_h_next = last->qh_h_next; + + sqh->h_prev = last; + + usb_pc_cpu_flush(sqh->page_cache); + + /* + * The "last->h_next->h_prev" is never followed: + * + * "sqh->h_next->h_prev" = sqh; + */ + + last->h_next = sqh; + last->qh_h_next = sqh->qh_self; + + usb_pc_cpu_flush(last->page_cache); + + return (sqh); +} + +/**/ + +#define UHCI_REMOVE_TD(std,last) (last) = _uhci_remove_td(std,last) +static uhci_td_t * +_uhci_remove_td(uhci_td_t *std, uhci_td_t *last) +{ + DPRINTFN(11, "%p from %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->prev->next = std->next; + std->prev->td_next = std->td_next; + + usb_pc_cpu_flush(std->prev->page_cache); + + if (std->next) { + std->next->prev = std->prev; + usb_pc_cpu_flush(std->next->page_cache); + } + return ((last == std) ? std->prev : last); +} + +#define UHCI_REMOVE_QH(sqh,last) (last) = _uhci_remove_qh(sqh,last) +static uhci_qh_t * +_uhci_remove_qh(uhci_qh_t *sqh, uhci_qh_t *last) +{ + DPRINTFN(11, "%p from %p\n", sqh, last); + + /* (sc->sc_bus.mtx) must be locked */ + + /* only remove if not removed from a queue */ + if (sqh->h_prev) { + + sqh->h_prev->h_next = sqh->h_next; + sqh->h_prev->qh_h_next = sqh->qh_h_next; + + usb_pc_cpu_flush(sqh->h_prev->page_cache); + + if (sqh->h_next) { + sqh->h_next->h_prev = sqh->h_prev; + usb_pc_cpu_flush(sqh->h_next->page_cache); + } + last = ((last == sqh) ? sqh->h_prev : last); + + sqh->h_prev = 0; + + usb_pc_cpu_flush(sqh->page_cache); + } + return (last); +} + +static void +uhci_isoc_done(uhci_softc_t *sc, struct usb_xfer *xfer) +{ + struct usb_page_search res; + uint32_t nframes = xfer->nframes; + uint32_t status; + uint32_t offset = 0; + uint32_t *plen = xfer->frlengths; + uint16_t len = 0; + uhci_td_t *td = xfer->td_transfer_first; + uhci_td_t **pp_last = &sc->sc_isoc_p_last[xfer->qh_pos]; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + + /* sync any DMA memory before doing fixups */ + + usb_bdma_post_sync(xfer); + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_p_last[0]; + } +#ifdef USB_DEBUG + if (uhcidebug > 5) { + DPRINTF("isoc TD\n"); + uhci_dump_td(td); + } +#endif + usb_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->td_status); + + len = UHCI_TD_GET_ACTLEN(status); + + if (len > *plen) { + len = *plen; + } + if (td->fix_pc) { + + usbd_get_page(td->fix_pc, 0, &res); + + /* copy data from fixup location to real location */ + + usb_pc_cpu_invalidate(td->fix_pc); + + usbd_copy_in(xfer->frbuffers, offset, + res.buffer, len); + } + offset += *plen; + + *plen = len; + + /* remove TD from schedule */ + UHCI_REMOVE_TD(td, *pp_last); + + pp_last++; + plen++; + td = td->obj_next; + } + + xfer->aframes = xfer->nframes; +} + +static usb_error_t +uhci_non_isoc_done_sub(struct usb_xfer *xfer) +{ + struct usb_page_search res; + uhci_td_t *td; + uhci_td_t *td_alt_next; + uint32_t status; + uint32_t token; + uint16_t len; + + td = xfer->td_transfer_cache; + td_alt_next = td->alt_next; + + if (xfer->aframes != xfer->nframes) { + usbd_xfer_set_frame_len(xfer, xfer->aframes, 0); + } + while (1) { + + usb_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->td_status); + token = le32toh(td->td_token); + + /* + * Verify the status and add + * up the actual length: + */ + + len = UHCI_TD_GET_ACTLEN(status); + if (len > td->len) { + /* should not happen */ + DPRINTF("Invalid status length, " + "0x%04x/0x%04x bytes\n", len, td->len); + status |= UHCI_TD_STALLED; + + } else if ((xfer->aframes != xfer->nframes) && (len > 0)) { + + if (td->fix_pc) { + + usbd_get_page(td->fix_pc, 0, &res); + + /* + * copy data from fixup location to real + * location + */ + + usb_pc_cpu_invalidate(td->fix_pc); + + usbd_copy_in(xfer->frbuffers + xfer->aframes, + xfer->frlengths[xfer->aframes], res.buffer, len); + } + /* update actual length */ + + xfer->frlengths[xfer->aframes] += len; + } + /* Check for last transfer */ + if (((void *)td) == xfer->td_transfer_last) { + td = NULL; + break; + } + if (status & UHCI_TD_STALLED) { + /* the transfer is finished */ + td = NULL; + break; + } + /* Check for short transfer */ + if (len != td->len) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + td = td->alt_next; + } else { + /* the transfer is finished */ + td = NULL; + } + break; + } + td = td->obj_next; + + if (td->alt_next != td_alt_next) { + /* this USB frame is complete */ + break; + } + } + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + /* update data toggle */ + + xfer->endpoint->toggle_next = (token & UHCI_TD_SET_DT(1)) ? 0 : 1; + +#ifdef USB_DEBUG + if (status & UHCI_TD_ERROR) { + DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x " + "status=%s%s%s%s%s%s%s%s%s%s%s\n", + xfer->address, xfer->endpointno, xfer->aframes, + (status & UHCI_TD_BITSTUFF) ? "[BITSTUFF]" : "", + (status & UHCI_TD_CRCTO) ? "[CRCTO]" : "", + (status & UHCI_TD_NAK) ? "[NAK]" : "", + (status & UHCI_TD_BABBLE) ? "[BABBLE]" : "", + (status & UHCI_TD_DBUFFER) ? "[DBUFFER]" : "", + (status & UHCI_TD_STALLED) ? "[STALLED]" : "", + (status & UHCI_TD_ACTIVE) ? "[ACTIVE]" : "[NOT_ACTIVE]", + (status & UHCI_TD_IOC) ? "[IOC]" : "", + (status & UHCI_TD_IOS) ? "[IOS]" : "", + (status & UHCI_TD_LS) ? "[LS]" : "", + (status & UHCI_TD_SPD) ? "[SPD]" : ""); + } +#endif + return (status & UHCI_TD_STALLED) ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION; +} + +static void +uhci_non_isoc_done(struct usb_xfer *xfer) +{ + usb_error_t err = 0; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + +#ifdef USB_DEBUG + if (uhcidebug > 10) { + uhci_dump_tds(xfer->td_transfer_first); + } +#endif + + /* sync any DMA memory before doing fixups */ + + usb_bdma_post_sync(xfer); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + err = uhci_non_isoc_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = uhci_non_isoc_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = uhci_non_isoc_done_sub(xfer); + } +done: + uhci_device_done(xfer, err); +} + +/*------------------------------------------------------------------------* + * uhci_check_transfer_sub + * + * The main purpose of this function is to update the data-toggle + * in case it is wrong. + *------------------------------------------------------------------------*/ +static void +uhci_check_transfer_sub(struct usb_xfer *xfer) +{ + uhci_qh_t *qh; + uhci_td_t *td; + uhci_td_t *td_alt_next; + + uint32_t td_token; + uint32_t td_self; + + td = xfer->td_transfer_cache; + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + td_token = td->obj_next->td_token; + td = td->alt_next; + xfer->td_transfer_cache = td; + td_self = td->td_self; + td_alt_next = td->alt_next; + + if (xfer->flags_int.control_xfr) + goto skip; /* don't touch the DT value! */ + + if (!((td->td_token ^ td_token) & htole32(UHCI_TD_SET_DT(1)))) + goto skip; /* data toggle has correct value */ + + /* + * The data toggle is wrong and we need to toggle it ! + */ + while (1) { + + td->td_token ^= htole32(UHCI_TD_SET_DT(1)); + usb_pc_cpu_flush(td->page_cache); + + if (td == xfer->td_transfer_last) { + /* last transfer */ + break; + } + td = td->obj_next; + + if (td->alt_next != td_alt_next) { + /* next frame */ + break; + } + } +skip: + + /* update the QH */ + qh->qh_e_next = td_self; + usb_pc_cpu_flush(qh->page_cache); + + DPRINTFN(13, "xfer=%p following alt next\n", xfer); +} + +/*------------------------------------------------------------------------* + * uhci_check_transfer + * + * Return values: + * 0: USB transfer is not finished + * Else: USB transfer is finished + *------------------------------------------------------------------------*/ +static uint8_t +uhci_check_transfer(struct usb_xfer *xfer) +{ + uint32_t status; + uint32_t token; + uhci_td_t *td; + + DPRINTFN(16, "xfer=%p checking transfer\n", xfer); + + if (xfer->endpoint->methods == &uhci_device_isoc_methods) { + /* isochronous transfer */ + + td = xfer->td_transfer_last; + + usb_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->td_status); + + /* check also if the first is complete */ + + td = xfer->td_transfer_first; + + usb_pc_cpu_invalidate(td->page_cache); + status |= le32toh(td->td_status); + + if (!(status & UHCI_TD_ACTIVE)) { + uhci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); + goto transferred; + } + } else { + /* non-isochronous transfer */ + + /* + * check whether there is an error somewhere + * in the middle, or whether there was a short + * packet (SPD and not ACTIVE) + */ + td = xfer->td_transfer_cache; + + while (1) { + usb_pc_cpu_invalidate(td->page_cache); + status = le32toh(td->td_status); + token = le32toh(td->td_token); + + /* + * if there is an active TD the transfer isn't done + */ + if (status & UHCI_TD_ACTIVE) { + /* update cache */ + xfer->td_transfer_cache = td; + goto done; + } + /* + * last transfer descriptor makes the transfer done + */ + if (((void *)td) == xfer->td_transfer_last) { + break; + } + /* + * any kind of error makes the transfer done + */ + if (status & UHCI_TD_STALLED) { + break; + } + /* + * check if we reached the last packet + * or if there is a short packet: + */ + if ((td->td_next == htole32(UHCI_PTR_T)) || + (UHCI_TD_GET_ACTLEN(status) < td->len)) { + + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + /* update cache */ + xfer->td_transfer_cache = td; + uhci_check_transfer_sub(xfer); + goto done; + } + } + /* transfer is done */ + break; + } + td = td->obj_next; + } + uhci_non_isoc_done(xfer); + goto transferred; + } + +done: + DPRINTFN(13, "xfer=%p is still active\n", xfer); + return (0); + +transferred: + return (1); +} + +static void +uhci_interrupt_poll(uhci_softc_t *sc) +{ + struct usb_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + /* + * check if transfer is transferred + */ + if (uhci_check_transfer(xfer)) { + /* queue has been modified */ + goto repeat; + } + } +} + +/*------------------------------------------------------------------------* + * uhci_interrupt - UHCI interrupt handler + * + * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler, + * hence the interrupt handler will be setup before "sc->sc_bus.bdev" + * is present ! + *------------------------------------------------------------------------*/ +void +uhci_interrupt(uhci_softc_t *sc) +{ + uint32_t status; + + USB_BUS_LOCK(&sc->sc_bus); + + DPRINTFN(16, "real interrupt\n"); + +#ifdef USB_DEBUG + if (uhcidebug > 15) { + uhci_dumpregs(sc); + } +#endif + status = UREAD2(sc, UHCI_STS) & UHCI_STS_ALLINTRS; + if (status == 0) { + /* the interrupt was not for us */ + goto done; + } + if (status & (UHCI_STS_RD | UHCI_STS_HSE | + UHCI_STS_HCPE | UHCI_STS_HCH)) { + + if (status & UHCI_STS_RD) { +#ifdef USB_DEBUG + printf("%s: resume detect\n", + __FUNCTION__); +#endif + } + if (status & UHCI_STS_HSE) { + printf("%s: host system error\n", + __FUNCTION__); + } + if (status & UHCI_STS_HCPE) { + printf("%s: host controller process error\n", + __FUNCTION__); + } + if (status & UHCI_STS_HCH) { + /* no acknowledge needed */ + DPRINTF("%s: host controller halted\n", + __FUNCTION__); +#ifdef USB_DEBUG + if (uhcidebug > 0) { + uhci_dump_all(sc); + } +#endif + } + } + /* get acknowledge bits */ + status &= (UHCI_STS_USBINT | + UHCI_STS_USBEI | + UHCI_STS_RD | + UHCI_STS_HSE | + UHCI_STS_HCPE); + + if (status == 0) { + /* nothing to acknowledge */ + goto done; + } + /* acknowledge interrupts */ + UWRITE2(sc, UHCI_STS, status); + + /* poll all the USB transfers */ + uhci_interrupt_poll(sc); + +done: + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/* + * called when a request does not complete + */ +static void +uhci_timeout(void *arg) +{ + struct usb_xfer *xfer = arg; + + DPRINTF("xfer=%p\n", xfer); + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* transfer is transferred */ + uhci_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +uhci_do_poll(struct usb_bus *bus) +{ + struct uhci_softc *sc = UHCI_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + uhci_interrupt_poll(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +uhci_setup_standard_chain_sub(struct uhci_std_temp *temp) +{ + uhci_td_t *td; + uhci_td_t *td_next; + uhci_td_t *td_alt_next; + uint32_t average; + uint32_t len_old; + uint8_t shortpkt_old; + uint8_t precompute; + + td_alt_next = NULL; + shortpkt_old = temp->shortpkt; + len_old = temp->len; + precompute = 1; + + /* software is used to detect short incoming transfers */ + + if ((temp->td_token & htole32(UHCI_TD_PID)) == htole32(UHCI_TD_PID_IN)) { + temp->td_status |= htole32(UHCI_TD_SPD); + } else { + temp->td_status &= ~htole32(UHCI_TD_SPD); + } + + temp->ml.buf_offset = 0; + +restart: + + temp->td_token &= ~htole32(UHCI_TD_SET_MAXLEN(0)); + temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(temp->average)); + + td = temp->td; + td_next = temp->td_next; + + while (1) { + + if (temp->len == 0) { + + if (temp->shortpkt) { + break; + } + /* send a Zero Length Packet, ZLP, last */ + + temp->shortpkt = 1; + temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(0)); + average = 0; + + } else { + + average = temp->average; + + if (temp->len < average) { + temp->shortpkt = 1; + temp->td_token &= ~htole32(UHCI_TD_SET_MAXLEN(0)); + temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(temp->len)); + average = temp->len; + } + } + + if (td_next == NULL) { + panic("%s: out of UHCI transfer descriptors!", __FUNCTION__); + } + /* get next TD */ + + td = td_next; + td_next = td->obj_next; + + /* check if we are pre-computing */ + + if (precompute) { + + /* update remaining length */ + + temp->len -= average; + + continue; + } + /* fill out current TD */ + + td->td_status = temp->td_status; + td->td_token = temp->td_token; + + /* update data toggle */ + + temp->td_token ^= htole32(UHCI_TD_SET_DT(1)); + + if (average == 0) { + + td->len = 0; + td->td_buffer = 0; + td->fix_pc = NULL; + + } else { + + /* update remaining length */ + + temp->len -= average; + + td->len = average; + + /* fill out buffer pointer and do fixup, if any */ + + uhci_mem_layout_fixup(&temp->ml, td); + } + + td->alt_next = td_alt_next; + + if ((td_next == td_alt_next) && temp->setup_alt_next) { + /* we need to receive these frames one by one ! */ + td->td_status |= htole32(UHCI_TD_IOC); + td->td_next = htole32(UHCI_PTR_T); + } else { + if (td_next) { + /* link the current TD with the next one */ + td->td_next = td_next->td_self; + } + } + + usb_pc_cpu_flush(td->page_cache); + } + + if (precompute) { + precompute = 0; + + /* setup alt next pointer, if any */ + if (temp->last_frame) { + td_alt_next = NULL; + } else { + /* we use this field internally */ + td_alt_next = td_next; + } + + /* restore */ + temp->shortpkt = shortpkt_old; + temp->len = len_old; + goto restart; + } + temp->td = td; + temp->td_next = td_next; +} + +static uhci_td_t * +uhci_setup_standard_chain(struct usb_xfer *xfer) +{ + struct uhci_std_temp temp; + uhci_td_t *td; + uint32_t x; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpointno), + xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); + + temp.average = xfer->max_frame_size; + temp.max_frame_size = xfer->max_frame_size; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + temp.td = NULL; + temp.td_next = td; + temp.last_frame = 0; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + + uhci_mem_layout_init(&temp.ml, xfer); + + temp.td_status = + htole32(UHCI_TD_ZERO_ACTLEN(UHCI_TD_SET_ERRCNT(3) | + UHCI_TD_ACTIVE)); + + if (xfer->xroot->udev->speed == USB_SPEED_LOW) { + temp.td_status |= htole32(UHCI_TD_LS); + } + temp.td_token = + htole32(UHCI_TD_SET_ENDPT(xfer->endpointno) | + UHCI_TD_SET_DEVADDR(xfer->address)); + + if (xfer->endpoint->toggle_next) { + /* DATA1 is next */ + temp.td_token |= htole32(UHCI_TD_SET_DT(1)); + } + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) | + UHCI_TD_SET_ENDPT(0xF)); + temp.td_token |= htole32(UHCI_TD_PID_SETUP | + UHCI_TD_SET_DT(0)); + + temp.len = xfer->frlengths[0]; + temp.ml.buf_pc = xfer->frbuffers + 0; + temp.shortpkt = temp.len ? 1 : 0; + /* check for last frame */ + if (xfer->nframes == 1) { + /* no STATUS stage yet, SETUP is last */ + if (xfer->flags_int.control_act) { + temp.last_frame = 1; + temp.setup_alt_next = 0; + } + } + uhci_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + temp.ml.buf_pc = xfer->frbuffers + x; + + x++; + + if (x == xfer->nframes) { + if (xfer->flags_int.control_xfr) { + /* no STATUS stage yet, DATA is last */ + if (xfer->flags_int.control_act) { + temp.last_frame = 1; + temp.setup_alt_next = 0; + } + } else { + temp.last_frame = 1; + temp.setup_alt_next = 0; + } + } + /* + * Keep previous data toggle, + * device address and endpoint number: + */ + + temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) | + UHCI_TD_SET_ENDPT(0xF) | + UHCI_TD_SET_DT(1)); + + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.shortpkt = 0; + + } else { + + /* regular data transfer */ + + temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + /* set endpoint direction */ + + temp.td_token |= + (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ? + htole32(UHCI_TD_PID_IN) : + htole32(UHCI_TD_PID_OUT); + + uhci_setup_standard_chain_sub(&temp); + } + + /* check if we should append a status stage */ + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + /* + * send a DATA1 message and reverse the current endpoint + * direction + */ + + temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) | + UHCI_TD_SET_ENDPT(0xF) | + UHCI_TD_SET_DT(1)); + temp.td_token |= + (UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT) ? + htole32(UHCI_TD_PID_IN | UHCI_TD_SET_DT(1)) : + htole32(UHCI_TD_PID_OUT | UHCI_TD_SET_DT(1)); + + temp.len = 0; + temp.ml.buf_pc = NULL; + temp.shortpkt = 0; + temp.last_frame = 1; + temp.setup_alt_next = 0; + + uhci_setup_standard_chain_sub(&temp); + } + td = temp.td; + + /* Ensure that last TD is terminating: */ + td->td_next = htole32(UHCI_PTR_T); + + /* set interrupt bit */ + + td->td_status |= htole32(UHCI_TD_IOC); + + usb_pc_cpu_flush(td->page_cache); + + /* must have at least one frame! */ + + xfer->td_transfer_last = td; + +#ifdef USB_DEBUG + if (uhcidebug > 8) { + DPRINTF("nexttog=%d; data before transfer:\n", + xfer->endpoint->toggle_next); + uhci_dump_tds(xfer->td_transfer_first); + } +#endif + return (xfer->td_transfer_first); +} + +/* NOTE: "done" can be run two times in a row, + * from close and from interrupt + */ + +static void +uhci_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_pipe_methods *methods = xfer->endpoint->methods; + uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); + uhci_qh_t *qh; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); + + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + if (qh) { + usb_pc_cpu_invalidate(qh->page_cache); + } + if (xfer->flags_int.bandwidth_reclaimed) { + xfer->flags_int.bandwidth_reclaimed = 0; + uhci_rem_loop(sc); + } + if (methods == &uhci_device_bulk_methods) { + UHCI_REMOVE_QH(qh, sc->sc_bulk_p_last); + } + if (methods == &uhci_device_ctrl_methods) { + if (xfer->xroot->udev->speed == USB_SPEED_LOW) { + UHCI_REMOVE_QH(qh, sc->sc_ls_ctl_p_last); + } else { + UHCI_REMOVE_QH(qh, sc->sc_fs_ctl_p_last); + } + } + if (methods == &uhci_device_intr_methods) { + UHCI_REMOVE_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]); + } + /* + * Only finish isochronous transfers once + * which will update "xfer->frlengths". + */ + if (xfer->td_transfer_first && + xfer->td_transfer_last) { + if (methods == &uhci_device_isoc_methods) { + uhci_isoc_done(sc, xfer); + } + xfer->td_transfer_first = NULL; + xfer->td_transfer_last = NULL; + } + /* dequeue transfer and start next transfer */ + usbd_transfer_done(xfer, error); +} + +/*------------------------------------------------------------------------* + * uhci bulk support + *------------------------------------------------------------------------*/ +static void +uhci_device_bulk_open(struct usb_xfer *xfer) +{ + return; +} + +static void +uhci_device_bulk_close(struct usb_xfer *xfer) +{ + uhci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +uhci_device_bulk_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +uhci_device_bulk_start(struct usb_xfer *xfer) +{ + uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); + uhci_td_t *td; + uhci_qh_t *qh; + + /* setup TD's */ + td = uhci_setup_standard_chain(xfer); + + /* setup QH */ + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + qh->e_next = td; + qh->qh_e_next = td->td_self; + + if (xfer->xroot->udev->flags.self_suspended == 0) { + UHCI_APPEND_QH(qh, sc->sc_bulk_p_last); + uhci_add_loop(sc); + xfer->flags_int.bandwidth_reclaimed = 1; + } else { + usb_pc_cpu_flush(qh->page_cache); + } + + /* put transfer on interrupt queue */ + uhci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods uhci_device_bulk_methods = +{ + .open = uhci_device_bulk_open, + .close = uhci_device_bulk_close, + .enter = uhci_device_bulk_enter, + .start = uhci_device_bulk_start, +}; + +/*------------------------------------------------------------------------* + * uhci control support + *------------------------------------------------------------------------*/ +static void +uhci_device_ctrl_open(struct usb_xfer *xfer) +{ + return; +} + +static void +uhci_device_ctrl_close(struct usb_xfer *xfer) +{ + uhci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +uhci_device_ctrl_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +uhci_device_ctrl_start(struct usb_xfer *xfer) +{ + uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); + uhci_qh_t *qh; + uhci_td_t *td; + + /* setup TD's */ + td = uhci_setup_standard_chain(xfer); + + /* setup QH */ + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + qh->e_next = td; + qh->qh_e_next = td->td_self; + + /* + * NOTE: some devices choke on bandwidth- reclamation for control + * transfers + */ + if (xfer->xroot->udev->flags.self_suspended == 0) { + if (xfer->xroot->udev->speed == USB_SPEED_LOW) { + UHCI_APPEND_QH(qh, sc->sc_ls_ctl_p_last); + } else { + UHCI_APPEND_QH(qh, sc->sc_fs_ctl_p_last); + } + } else { + usb_pc_cpu_flush(qh->page_cache); + } + /* put transfer on interrupt queue */ + uhci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods uhci_device_ctrl_methods = +{ + .open = uhci_device_ctrl_open, + .close = uhci_device_ctrl_close, + .enter = uhci_device_ctrl_enter, + .start = uhci_device_ctrl_start, +}; + +/*------------------------------------------------------------------------* + * uhci interrupt support + *------------------------------------------------------------------------*/ +static void +uhci_device_intr_open(struct usb_xfer *xfer) +{ + uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); + uint16_t best; + uint16_t bit; + uint16_t x; + + best = 0; + bit = UHCI_IFRAMELIST_COUNT / 2; + while (bit) { + if (xfer->interval >= bit) { + x = bit; + best = bit; + while (x & bit) { + if (sc->sc_intr_stat[x] < + sc->sc_intr_stat[best]) { + best = x; + } + x++; + } + break; + } + bit >>= 1; + } + + sc->sc_intr_stat[best]++; + xfer->qh_pos = best; + + DPRINTFN(3, "best=%d interval=%d\n", + best, xfer->interval); +} + +static void +uhci_device_intr_close(struct usb_xfer *xfer) +{ + uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); + + sc->sc_intr_stat[xfer->qh_pos]--; + + uhci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +uhci_device_intr_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +uhci_device_intr_start(struct usb_xfer *xfer) +{ + uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); + uhci_qh_t *qh; + uhci_td_t *td; + + /* setup TD's */ + td = uhci_setup_standard_chain(xfer); + + /* setup QH */ + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + qh->e_next = td; + qh->qh_e_next = td->td_self; + + if (xfer->xroot->udev->flags.self_suspended == 0) { + /* enter QHs into the controller data structures */ + UHCI_APPEND_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]); + } else { + usb_pc_cpu_flush(qh->page_cache); + } + + /* put transfer on interrupt queue */ + uhci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods uhci_device_intr_methods = +{ + .open = uhci_device_intr_open, + .close = uhci_device_intr_close, + .enter = uhci_device_intr_enter, + .start = uhci_device_intr_start, +}; + +/*------------------------------------------------------------------------* + * uhci isochronous support + *------------------------------------------------------------------------*/ +static void +uhci_device_isoc_open(struct usb_xfer *xfer) +{ + uhci_td_t *td; + uint32_t td_token; + uint8_t ds; + + td_token = + (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ? + UHCI_TD_IN(0, xfer->endpointno, xfer->address, 0) : + UHCI_TD_OUT(0, xfer->endpointno, xfer->address, 0); + + td_token = htole32(td_token); + + /* initialize all TD's */ + + for (ds = 0; ds != 2; ds++) { + + for (td = xfer->td_start[ds]; td; td = td->obj_next) { + + /* mark TD as inactive */ + td->td_status = htole32(UHCI_TD_IOS); + td->td_token = td_token; + + usb_pc_cpu_flush(td->page_cache); + } + } +} + +static void +uhci_device_isoc_close(struct usb_xfer *xfer) +{ + uhci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +uhci_device_isoc_enter(struct usb_xfer *xfer) +{ + struct uhci_mem_layout ml; + uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus); + uint32_t nframes; + uint32_t temp; + uint32_t *plen; + +#ifdef USB_DEBUG + uint8_t once = 1; + +#endif + uhci_td_t *td; + uhci_td_t *td_last = NULL; + uhci_td_t **pp_last; + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes); + + nframes = UREAD2(sc, UHCI_FRNUM); + + temp = (nframes - xfer->endpoint->isoc_next) & + (UHCI_VFRAMELIST_COUNT - 1); + + if ((xfer->endpoint->is_synced == 0) || + (temp < xfer->nframes)) { + /* + * If there is data underflow or the pipe queue is empty we + * schedule the transfer a few frames ahead of the current + * frame position. Else two isochronous transfers might + * overlap. + */ + xfer->endpoint->isoc_next = (nframes + 3) & (UHCI_VFRAMELIST_COUNT - 1); + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + temp = (xfer->endpoint->isoc_next - nframes) & + (UHCI_VFRAMELIST_COUNT - 1); + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + + xfer->nframes; + + /* get the real number of frames */ + + nframes = xfer->nframes; + + uhci_mem_layout_init(&ml, xfer); + + plen = xfer->frlengths; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + xfer->td_transfer_first = td; + + pp_last = &sc->sc_isoc_p_last[xfer->endpoint->isoc_next]; + + /* store starting position */ + + xfer->qh_pos = xfer->endpoint->isoc_next; + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_p_last[0]; + } + if (*plen > xfer->max_frame_size) { +#ifdef USB_DEBUG + if (once) { + once = 0; + printf("%s: frame length(%d) exceeds %d " + "bytes (frame truncated)\n", + __FUNCTION__, *plen, + xfer->max_frame_size); + } +#endif + *plen = xfer->max_frame_size; + } + /* reuse td_token from last transfer */ + + td->td_token &= htole32(~UHCI_TD_MAXLEN_MASK); + td->td_token |= htole32(UHCI_TD_SET_MAXLEN(*plen)); + + td->len = *plen; + + if (td->len == 0) { + /* + * Do not call "uhci_mem_layout_fixup()" when the + * length is zero! + */ + td->td_buffer = 0; + td->fix_pc = NULL; + + } else { + + /* fill out buffer pointer and do fixup, if any */ + + uhci_mem_layout_fixup(&ml, td); + + } + + /* update status */ + if (nframes == 0) { + td->td_status = htole32 + (UHCI_TD_ZERO_ACTLEN + (UHCI_TD_SET_ERRCNT(0) | + UHCI_TD_ACTIVE | + UHCI_TD_IOS | + UHCI_TD_IOC)); + } else { + td->td_status = htole32 + (UHCI_TD_ZERO_ACTLEN + (UHCI_TD_SET_ERRCNT(0) | + UHCI_TD_ACTIVE | + UHCI_TD_IOS)); + } + + usb_pc_cpu_flush(td->page_cache); + +#ifdef USB_DEBUG + if (uhcidebug > 5) { + DPRINTF("TD %d\n", nframes); + uhci_dump_td(td); + } +#endif + /* insert TD into schedule */ + UHCI_APPEND_TD(td, *pp_last); + pp_last++; + + plen++; + td_last = td; + td = td->obj_next; + } + + xfer->td_transfer_last = td_last; + + /* update isoc_next */ + xfer->endpoint->isoc_next = (pp_last - &sc->sc_isoc_p_last[0]) & + (UHCI_VFRAMELIST_COUNT - 1); +} + +static void +uhci_device_isoc_start(struct usb_xfer *xfer) +{ + /* put transfer on interrupt queue */ + uhci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods uhci_device_isoc_methods = +{ + .open = uhci_device_isoc_open, + .close = uhci_device_isoc_close, + .enter = uhci_device_isoc_enter, + .start = uhci_device_isoc_start, +}; + +/*------------------------------------------------------------------------* + * uhci root control support + *------------------------------------------------------------------------* + * Simulate a hardware hub by handling all the necessary requests. + *------------------------------------------------------------------------*/ + +static const +struct usb_device_descriptor uhci_devd = +{ + sizeof(struct usb_device_descriptor), + UDESC_DEVICE, /* type */ + {0x00, 0x01}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_FSHUB, /* protocol */ + 64, /* max packet */ + {0}, {0}, {0x00, 0x01}, /* device id */ + 1, 2, 0, /* string indicies */ + 1 /* # of configurations */ +}; + +static const struct uhci_config_desc uhci_confd = { + .confd = { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(uhci_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0 /* max power */ + }, + .ifcd = { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = UIPROTO_FSHUB, + }, + .endpd = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = UE_DIR_IN | UHCI_INTR_ENDPT, + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, /* max packet (63 ports) */ + .bInterval = 255, + }, +}; + +static const +struct usb_hub_descriptor_min uhci_hubd_piix = +{ + sizeof(uhci_hubd_piix), + UDESC_HUB, + 2, + {UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL, 0}, + 50, /* power on to power good */ + 0, + {0x00}, /* both ports are removable */ +}; + +/* + * The USB hub protocol requires that SET_FEATURE(PORT_RESET) also + * enables the port, and also states that SET_FEATURE(PORT_ENABLE) + * should not be used by the USB subsystem. As we cannot issue a + * SET_FEATURE(PORT_ENABLE) externally, we must ensure that the port + * will be enabled as part of the reset. + * + * On the VT83C572, the port cannot be successfully enabled until the + * outstanding "port enable change" and "connection status change" + * events have been reset. + */ +static usb_error_t +uhci_portreset(uhci_softc_t *sc, uint16_t index) +{ + uint16_t port; + uint16_t x; + uint8_t lim; + + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else + return (USB_ERR_IOERROR); + + /* + * Before we do anything, turn on SOF messages on the USB + * BUS. Some USB devices do not cope without them! + */ + uhci_restart(sc); + + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_PR); + + usb_pause_mtx(&sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(USB_PORT_ROOT_RESET_DELAY)); + + DPRINTFN(4, "uhci port %d reset, status0 = 0x%04x\n", + index, UREAD2(sc, port)); + + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x & ~UHCI_PORTSC_PR); + + + mtx_unlock(&sc->sc_bus.bus_mtx); + + /* + * This delay needs to be exactly 100us, else some USB devices + * fail to attach! + */ + DELAY(100); + + mtx_lock(&sc->sc_bus.bus_mtx); + + DPRINTFN(4, "uhci port %d reset, status1 = 0x%04x\n", + index, UREAD2(sc, port)); + + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_PE); + + for (lim = 0; lim < 12; lim++) { + + usb_pause_mtx(&sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(USB_PORT_RESET_DELAY)); + + x = UREAD2(sc, port); + + DPRINTFN(4, "uhci port %d iteration %u, status = 0x%04x\n", + index, lim, x); + + if (!(x & UHCI_PORTSC_CCS)) { + /* + * No device is connected (or was disconnected + * during reset). Consider the port reset. + * The delay must be long enough to ensure on + * the initial iteration that the device + * connection will have been registered. 50ms + * appears to be sufficient, but 20ms is not. + */ + DPRINTFN(4, "uhci port %d loop %u, device detached\n", + index, lim); + goto done; + } + if (x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC)) { + /* + * Port enabled changed and/or connection + * status changed were set. Reset either or + * both raised flags (by writing a 1 to that + * bit), and wait again for state to settle. + */ + UWRITE2(sc, port, URWMASK(x) | + (x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC))); + continue; + } + if (x & UHCI_PORTSC_PE) { + /* port is enabled */ + goto done; + } + UWRITE2(sc, port, URWMASK(x) | UHCI_PORTSC_PE); + } + + DPRINTFN(2, "uhci port %d reset timed out\n", index); + return (USB_ERR_TIMEOUT); + +done: + DPRINTFN(4, "uhci port %d reset, status2 = 0x%04x\n", + index, UREAD2(sc, port)); + + sc->sc_isreset = 1; + return (USB_ERR_NORMAL_COMPLETION); +} + +static usb_error_t +uhci_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + uhci_softc_t *sc = UHCI_BUS2SC(udev->bus); + const void *ptr; + const char *str_ptr; + uint16_t x; + uint16_t port; + uint16_t value; + uint16_t index; + uint16_t status; + uint16_t change; + uint16_t len; + usb_error_t err; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* buffer reset */ + ptr = (const void *)&sc->sc_hub_desc.temp; + len = 0; + err = 0; + + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " + "wValue=0x%04x wIndex=0x%04x\n", + req->bmRequestType, req->bRequest, + UGETW(req->wLength), value, index); + +#define C(x,y) ((x) | ((y) << 8)) + switch (C(req->bRequest, req->bmRequestType)) { + case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): + case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): + case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): + /* + * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops + * for the integrated root hub. + */ + break; + case C(UR_GET_CONFIG, UT_READ_DEVICE): + len = 1; + sc->sc_hub_desc.temp[0] = sc->sc_conf; + break; + case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): + switch (value >> 8) { + case UDESC_DEVICE: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(uhci_devd); + ptr = (const void *)&uhci_devd; + break; + + case UDESC_CONFIG: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(uhci_confd); + ptr = (const void *)&uhci_confd; + break; + + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + str_ptr = "\001"; + break; + + case 1: /* Vendor */ + str_ptr = sc->sc_vendor; + break; + + case 2: /* Product */ + str_ptr = "UHCI root HUB"; + break; + + default: + str_ptr = ""; + break; + } + + len = usb_make_str_desc + (sc->sc_hub_desc.temp, + sizeof(sc->sc_hub_desc.temp), + str_ptr); + break; + + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_INTERFACE, UT_READ_INTERFACE): + len = 1; + sc->sc_hub_desc.temp[0] = 0; + break; + case C(UR_GET_STATUS, UT_READ_DEVICE): + len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED); + break; + case C(UR_GET_STATUS, UT_READ_INTERFACE): + case C(UR_GET_STATUS, UT_READ_ENDPOINT): + len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, 0); + break; + case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): + if (value >= UHCI_MAX_DEVICES) { + err = USB_ERR_IOERROR; + goto done; + } + sc->sc_addr = value; + break; + case C(UR_SET_CONFIG, UT_WRITE_DEVICE): + if ((value != 0) && (value != 1)) { + err = USB_ERR_IOERROR; + goto done; + } + sc->sc_conf = value; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_DEVICE): + case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): + case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): + err = USB_ERR_IOERROR; + goto done; + case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + break; + case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): + break; + /* Hub requests */ + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): + DPRINTFN(4, "UR_CLEAR_PORT_FEATURE " + "port=%d feature=%d\n", + index, value); + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else { + err = USB_ERR_IOERROR; + goto done; + } + switch (value) { + case UHF_PORT_ENABLE: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x & ~UHCI_PORTSC_PE); + break; + case UHF_PORT_SUSPEND: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x & ~(UHCI_PORTSC_SUSP)); + break; + case UHF_PORT_RESET: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x & ~UHCI_PORTSC_PR); + break; + case UHF_C_PORT_CONNECTION: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_CSC); + break; + case UHF_C_PORT_ENABLE: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_POEDC); + break; + case UHF_C_PORT_OVER_CURRENT: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_OCIC); + break; + case UHF_C_PORT_RESET: + sc->sc_isreset = 0; + err = USB_ERR_NORMAL_COMPLETION; + goto done; + case UHF_C_PORT_SUSPEND: + sc->sc_isresumed &= ~(1 << index); + break; + case UHF_PORT_CONNECTION: + case UHF_PORT_OVER_CURRENT: + case UHF_PORT_POWER: + case UHF_PORT_LOW_SPEED: + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_BUS_STATE, UT_READ_CLASS_OTHER): + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else { + err = USB_ERR_IOERROR; + goto done; + } + len = 1; + sc->sc_hub_desc.temp[0] = + ((UREAD2(sc, port) & UHCI_PORTSC_LS) >> + UHCI_PORTSC_LS_SHIFT); + break; + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(uhci_hubd_piix); + ptr = (const void *)&uhci_hubd_piix; + break; + case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): + len = 16; + memset(sc->sc_hub_desc.temp, 0, 16); + break; + case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else { + err = USB_ERR_IOERROR; + goto done; + } + x = UREAD2(sc, port); + status = change = 0; + if (x & UHCI_PORTSC_CCS) + status |= UPS_CURRENT_CONNECT_STATUS; + if (x & UHCI_PORTSC_CSC) + change |= UPS_C_CONNECT_STATUS; + if (x & UHCI_PORTSC_PE) + status |= UPS_PORT_ENABLED; + if (x & UHCI_PORTSC_POEDC) + change |= UPS_C_PORT_ENABLED; + if (x & UHCI_PORTSC_OCI) + status |= UPS_OVERCURRENT_INDICATOR; + if (x & UHCI_PORTSC_OCIC) + change |= UPS_C_OVERCURRENT_INDICATOR; + if (x & UHCI_PORTSC_LSDA) + status |= UPS_LOW_SPEED; + if ((x & UHCI_PORTSC_PE) && (x & UHCI_PORTSC_RD)) { + /* need to do a write back */ + UWRITE2(sc, port, URWMASK(x)); + + /* wait 20ms for resume sequence to complete */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50); + + /* clear suspend and resume detect */ + UWRITE2(sc, port, URWMASK(x) & ~(UHCI_PORTSC_RD | + UHCI_PORTSC_SUSP)); + + /* wait a little bit */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 500); + + sc->sc_isresumed |= (1 << index); + + } else if (x & UHCI_PORTSC_SUSP) { + status |= UPS_SUSPEND; + } + status |= UPS_PORT_POWER; + if (sc->sc_isresumed & (1 << index)) + change |= UPS_C_SUSPEND; + if (sc->sc_isreset) + change |= UPS_C_PORT_RESET; + USETW(sc->sc_hub_desc.ps.wPortStatus, status); + USETW(sc->sc_hub_desc.ps.wPortChange, change); + len = sizeof(sc->sc_hub_desc.ps); + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): + err = USB_ERR_IOERROR; + goto done; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else { + err = USB_ERR_IOERROR; + goto done; + } + switch (value) { + case UHF_PORT_ENABLE: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_PE); + break; + case UHF_PORT_SUSPEND: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_SUSP); + break; + case UHF_PORT_RESET: + err = uhci_portreset(sc, index); + goto done; + case UHF_PORT_POWER: + /* pretend we turned on power */ + err = USB_ERR_NORMAL_COMPLETION; + goto done; + case UHF_C_PORT_CONNECTION: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_PORT_CONNECTION: + case UHF_PORT_OVER_CURRENT: + case UHF_PORT_LOW_SPEED: + case UHF_C_PORT_SUSPEND: + case UHF_C_PORT_RESET: + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + default: + err = USB_ERR_IOERROR; + goto done; + } +done: + *plength = len; + *pptr = ptr; + return (err); +} + +/* + * This routine is executed periodically and simulates interrupts from + * the root controller interrupt pipe for port status change: + */ +static void +uhci_root_intr(uhci_softc_t *sc) +{ + DPRINTFN(21, "\n"); + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + sc->sc_hub_idata[0] = 0; + + if (UREAD2(sc, UHCI_PORTSC1) & (UHCI_PORTSC_CSC | + UHCI_PORTSC_OCIC | UHCI_PORTSC_RD)) { + sc->sc_hub_idata[0] |= 1 << 1; + } + if (UREAD2(sc, UHCI_PORTSC2) & (UHCI_PORTSC_CSC | + UHCI_PORTSC_OCIC | UHCI_PORTSC_RD)) { + sc->sc_hub_idata[0] |= 1 << 2; + } + + /* restart timer */ + usb_callout_reset(&sc->sc_root_intr, hz, + (void *)&uhci_root_intr, sc); + + if (sc->sc_hub_idata[0] != 0) { + uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, + sizeof(sc->sc_hub_idata)); + } +} + +static void +uhci_xfer_setup(struct usb_setup_params *parm) +{ + struct usb_page_search page_info; + struct usb_page_cache *pc; + uhci_softc_t *sc; + struct usb_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t nqh; + uint32_t nfixup; + uint32_t n; + uint16_t align; + + sc = UHCI_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + parm->hc_max_packet_size = 0x500; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x500; + + /* + * compute ntd and nqh + */ + if (parm->methods == &uhci_device_ctrl_methods) { + xfer->flags_int.bdma_enable = 1; + xfer->flags_int.bdma_no_post_sync = 1; + + usbd_transfer_setup_sub(parm); + + /* see EHCI HC driver for proof of "ntd" formula */ + + nqh = 1; + ntd = ((2 * xfer->nframes) + 1 /* STATUS */ + + (xfer->max_data_length / xfer->max_frame_size)); + + } else if (parm->methods == &uhci_device_bulk_methods) { + xfer->flags_int.bdma_enable = 1; + xfer->flags_int.bdma_no_post_sync = 1; + + usbd_transfer_setup_sub(parm); + + nqh = 1; + ntd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_frame_size)); + + } else if (parm->methods == &uhci_device_intr_methods) { + xfer->flags_int.bdma_enable = 1; + xfer->flags_int.bdma_no_post_sync = 1; + + usbd_transfer_setup_sub(parm); + + nqh = 1; + ntd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_frame_size)); + + } else if (parm->methods == &uhci_device_isoc_methods) { + xfer->flags_int.bdma_enable = 1; + xfer->flags_int.bdma_no_post_sync = 1; + + usbd_transfer_setup_sub(parm); + + nqh = 0; + ntd = xfer->nframes; + + } else { + + usbd_transfer_setup_sub(parm); + + nqh = 0; + ntd = 0; + } + + if (parm->err) { + return; + } + /* + * NOTE: the UHCI controller requires that + * every packet must be contiguous on + * the same USB memory page ! + */ + nfixup = (parm->bufsize / USB_PAGE_SIZE) + 1; + + /* + * Compute a suitable power of two alignment + * for our "max_frame_size" fixup buffer(s): + */ + align = xfer->max_frame_size; + n = 0; + while (align) { + align >>= 1; + n++; + } + + /* check for power of two */ + if (!(xfer->max_frame_size & + (xfer->max_frame_size - 1))) { + n--; + } + /* + * We don't allow alignments of + * less than 8 bytes: + * + * NOTE: Allocating using an aligment + * of 1 byte has special meaning! + */ + if (n < 3) { + n = 3; + } + align = (1 << n); + + if (usbd_transfer_setup_sub_malloc( + parm, &pc, xfer->max_frame_size, + align, nfixup)) { + parm->err = USB_ERR_NOMEM; + return; + } + xfer->buf_fixup = pc; + +alloc_dma_set: + + if (parm->err) { + return; + } + last_obj = NULL; + + if (usbd_transfer_setup_sub_malloc( + parm, &pc, sizeof(uhci_td_t), + UHCI_TD_ALIGN, ntd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != ntd; n++) { + uhci_td_t *td; + + usbd_get_page(pc + n, 0, &page_info); + + td = page_info.buffer; + + /* init TD */ + if ((parm->methods == &uhci_device_bulk_methods) || + (parm->methods == &uhci_device_ctrl_methods) || + (parm->methods == &uhci_device_intr_methods)) { + /* set depth first bit */ + td->td_self = htole32(page_info.physaddr | + UHCI_PTR_TD | UHCI_PTR_VF); + } else { + td->td_self = htole32(page_info.physaddr | + UHCI_PTR_TD); + } + + td->obj_next = last_obj; + td->page_cache = pc + n; + + last_obj = td; + + usb_pc_cpu_flush(pc + n); + } + } + xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj; + + last_obj = NULL; + + if (usbd_transfer_setup_sub_malloc( + parm, &pc, sizeof(uhci_qh_t), + UHCI_QH_ALIGN, nqh)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nqh; n++) { + uhci_qh_t *qh; + + usbd_get_page(pc + n, 0, &page_info); + + qh = page_info.buffer; + + /* init QH */ + qh->qh_self = htole32(page_info.physaddr | UHCI_PTR_QH); + qh->obj_next = last_obj; + qh->page_cache = pc + n; + + last_obj = qh; + + usb_pc_cpu_flush(pc + n); + } + } + xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj; + + if (!xfer->flags_int.curr_dma_set) { + xfer->flags_int.curr_dma_set = 1; + goto alloc_dma_set; + } +} + +static void +uhci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *ep) +{ + uhci_softc_t *sc = UHCI_BUS2SC(udev->bus); + + DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", + ep, udev->address, + edesc->bEndpointAddress, udev->flags.usb_mode, + sc->sc_addr); + + if (udev->flags.usb_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + if (udev->device_index != sc->sc_addr) { + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + ep->methods = &uhci_device_ctrl_methods; + break; + case UE_INTERRUPT: + ep->methods = &uhci_device_intr_methods; + break; + case UE_ISOCHRONOUS: + if (udev->speed == USB_SPEED_FULL) { + ep->methods = &uhci_device_isoc_methods; + } + break; + case UE_BULK: + ep->methods = &uhci_device_bulk_methods; + break; + default: + /* do nothing */ + break; + } + } +} + +static void +uhci_xfer_unsetup(struct usb_xfer *xfer) +{ + return; +} + +static void +uhci_get_dma_delay(struct usb_device *udev, uint32_t *pus) +{ + /* + * Wait until hardware has finished any possible use of the + * transfer descriptor(s) and QH + */ + *pus = (1125); /* microseconds */ +} + +static void +uhci_device_resume(struct usb_device *udev) +{ + struct uhci_softc *sc = UHCI_BUS2SC(udev->bus); + struct usb_xfer *xfer; + struct usb_pipe_methods *methods; + uhci_qh_t *qh; + + DPRINTF("\n"); + + USB_BUS_LOCK(udev->bus); + + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + + if (xfer->xroot->udev == udev) { + + methods = xfer->endpoint->methods; + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + if (methods == &uhci_device_bulk_methods) { + UHCI_APPEND_QH(qh, sc->sc_bulk_p_last); + uhci_add_loop(sc); + xfer->flags_int.bandwidth_reclaimed = 1; + } + if (methods == &uhci_device_ctrl_methods) { + if (xfer->xroot->udev->speed == USB_SPEED_LOW) { + UHCI_APPEND_QH(qh, sc->sc_ls_ctl_p_last); + } else { + UHCI_APPEND_QH(qh, sc->sc_fs_ctl_p_last); + } + } + if (methods == &uhci_device_intr_methods) { + UHCI_APPEND_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]); + } + } + } + + USB_BUS_UNLOCK(udev->bus); + + return; +} + +static void +uhci_device_suspend(struct usb_device *udev) +{ + struct uhci_softc *sc = UHCI_BUS2SC(udev->bus); + struct usb_xfer *xfer; + struct usb_pipe_methods *methods; + uhci_qh_t *qh; + + DPRINTF("\n"); + + USB_BUS_LOCK(udev->bus); + + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + + if (xfer->xroot->udev == udev) { + + methods = xfer->endpoint->methods; + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + if (xfer->flags_int.bandwidth_reclaimed) { + xfer->flags_int.bandwidth_reclaimed = 0; + uhci_rem_loop(sc); + } + if (methods == &uhci_device_bulk_methods) { + UHCI_REMOVE_QH(qh, sc->sc_bulk_p_last); + } + if (methods == &uhci_device_ctrl_methods) { + if (xfer->xroot->udev->speed == USB_SPEED_LOW) { + UHCI_REMOVE_QH(qh, sc->sc_ls_ctl_p_last); + } else { + UHCI_REMOVE_QH(qh, sc->sc_fs_ctl_p_last); + } + } + if (methods == &uhci_device_intr_methods) { + UHCI_REMOVE_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]); + } + } + } + + USB_BUS_UNLOCK(udev->bus); + + return; +} + +static void +uhci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) +{ + struct uhci_softc *sc = UHCI_BUS2SC(bus); + + switch (state) { + case USB_HW_POWER_SUSPEND: + case USB_HW_POWER_SHUTDOWN: + uhci_suspend(sc); + break; + case USB_HW_POWER_RESUME: + uhci_resume(sc); + break; + default: + break; + } +} + +static void +uhci_set_hw_power(struct usb_bus *bus) +{ + struct uhci_softc *sc = UHCI_BUS2SC(bus); + uint32_t flags; + + DPRINTF("\n"); + + USB_BUS_LOCK(bus); + + flags = bus->hw_power_state; + + /* + * WARNING: Some FULL speed USB devices require periodic SOF + * messages! If any USB devices are connected through the + * UHCI, power save will be disabled! + */ + if (flags & (USB_HW_POWER_CONTROL | + USB_HW_POWER_NON_ROOT_HUB | + USB_HW_POWER_BULK | + USB_HW_POWER_INTERRUPT | + USB_HW_POWER_ISOC)) { + DPRINTF("Some USB transfer is " + "active on unit %u.\n", + device_get_unit(sc->sc_bus.bdev)); + uhci_restart(sc); + } else { + DPRINTF("Power save on unit %u.\n", + device_get_unit(sc->sc_bus.bdev)); + UHCICMD(sc, UHCI_CMD_MAXP); + } + + USB_BUS_UNLOCK(bus); + + return; +} + + +struct usb_bus_methods uhci_bus_methods = +{ + .endpoint_init = uhci_ep_init, + .xfer_setup = uhci_xfer_setup, + .xfer_unsetup = uhci_xfer_unsetup, + .get_dma_delay = uhci_get_dma_delay, + .device_resume = uhci_device_resume, + .device_suspend = uhci_device_suspend, + .set_hw_power = uhci_set_hw_power, + .set_hw_power_sleep = uhci_set_hw_power_sleep, + .roothub_exec = uhci_roothub_exec, + .xfer_poll = uhci_do_poll, +}; diff --git a/sys/bus/u4b/controller/uhci.h b/sys/bus/u4b/controller/uhci.h new file mode 100644 index 0000000000..8a6ce44703 --- /dev/null +++ b/sys/bus/u4b/controller/uhci.h @@ -0,0 +1,249 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _UHCI_H_ +#define _UHCI_H_ + +#define UHCI_MAX_DEVICES MIN(USB_MAX_DEVICES, 128) + +#define UHCI_FRAMELIST_COUNT 1024 /* units */ +#define UHCI_FRAMELIST_ALIGN 4096 /* bytes */ + +/* Structures alignment (bytes) */ +#define UHCI_TD_ALIGN 16 +#define UHCI_QH_ALIGN 16 + +#if ((USB_PAGE_SIZE < UHCI_TD_ALIGN) || (UHCI_TD_ALIGN == 0) || \ + (USB_PAGE_SIZE < UHCI_QH_ALIGN) || (UHCI_QH_ALIGN == 0)) +#error "Invalid USB page size!" +#endif + +typedef uint32_t uhci_physaddr_t; + +#define UHCI_PTR_T 0x00000001 +#define UHCI_PTR_TD 0x00000000 +#define UHCI_PTR_QH 0x00000002 +#define UHCI_PTR_VF 0x00000004 + +/* + * The Queue Heads (QH) and Transfer Descriptors (TD) are accessed by + * both the CPU and the USB-controller which run concurrently. Great + * care must be taken. When the data-structures are linked into the + * USB controller's frame list, the USB-controller "owns" the + * td_status and qh_elink fields, which will not be written by the + * CPU. + * + */ + +struct uhci_td { +/* + * Data used by the UHCI controller. + * volatile is used in order to mantain struct members ordering. + */ + volatile uint32_t td_next; + volatile uint32_t td_status; +#define UHCI_TD_GET_ACTLEN(s) (((s) + 1) & 0x3ff) +#define UHCI_TD_ZERO_ACTLEN(t) ((t) | 0x3ff) +#define UHCI_TD_BITSTUFF 0x00020000 +#define UHCI_TD_CRCTO 0x00040000 +#define UHCI_TD_NAK 0x00080000 +#define UHCI_TD_BABBLE 0x00100000 +#define UHCI_TD_DBUFFER 0x00200000 +#define UHCI_TD_STALLED 0x00400000 +#define UHCI_TD_ACTIVE 0x00800000 +#define UHCI_TD_IOC 0x01000000 +#define UHCI_TD_IOS 0x02000000 +#define UHCI_TD_LS 0x04000000 +#define UHCI_TD_GET_ERRCNT(s) (((s) >> 27) & 3) +#define UHCI_TD_SET_ERRCNT(n) ((n) << 27) +#define UHCI_TD_SPD 0x20000000 + volatile uint32_t td_token; +#define UHCI_TD_PID 0x000000ff +#define UHCI_TD_PID_IN 0x00000069 +#define UHCI_TD_PID_OUT 0x000000e1 +#define UHCI_TD_PID_SETUP 0x0000002d +#define UHCI_TD_GET_PID(s) ((s) & 0xff) +#define UHCI_TD_SET_DEVADDR(a) ((a) << 8) +#define UHCI_TD_GET_DEVADDR(s) (((s) >> 8) & 0x7f) +#define UHCI_TD_SET_ENDPT(e) (((e) & 0xf) << 15) +#define UHCI_TD_GET_ENDPT(s) (((s) >> 15) & 0xf) +#define UHCI_TD_SET_DT(t) ((t) << 19) +#define UHCI_TD_GET_DT(s) (((s) >> 19) & 1) +#define UHCI_TD_SET_MAXLEN(l) (((l)-1) << 21) +#define UHCI_TD_GET_MAXLEN(s) ((((s) >> 21) + 1) & 0x7ff) +#define UHCI_TD_MAXLEN_MASK 0xffe00000 + volatile uint32_t td_buffer; +/* + * Extra information needed: + */ + struct uhci_td *next; + struct uhci_td *prev; + struct uhci_td *obj_next; + struct usb_page_cache *page_cache; + struct usb_page_cache *fix_pc; + uint32_t td_self; + uint16_t len; +} __aligned(UHCI_TD_ALIGN); + +typedef struct uhci_td uhci_td_t; + +#define UHCI_TD_ERROR (UHCI_TD_BITSTUFF | UHCI_TD_CRCTO | \ + UHCI_TD_BABBLE | UHCI_TD_DBUFFER | UHCI_TD_STALLED) + +#define UHCI_TD_SETUP(len, endp, dev) (UHCI_TD_SET_MAXLEN(len) | \ + UHCI_TD_SET_ENDPT(endp) | \ + UHCI_TD_SET_DEVADDR(dev) | \ + UHCI_TD_PID_SETUP) + +#define UHCI_TD_OUT(len, endp, dev, dt) (UHCI_TD_SET_MAXLEN(len) | \ + UHCI_TD_SET_ENDPT(endp) | \ + UHCI_TD_SET_DEVADDR(dev) | \ + UHCI_TD_PID_OUT | UHCI_TD_SET_DT(dt)) + +#define UHCI_TD_IN(len, endp, dev, dt) (UHCI_TD_SET_MAXLEN(len) | \ + UHCI_TD_SET_ENDPT(endp) | \ + UHCI_TD_SET_DEVADDR(dev) | \ + UHCI_TD_PID_IN | UHCI_TD_SET_DT(dt)) + +struct uhci_qh { +/* + * Data used by the UHCI controller. + */ + volatile uint32_t qh_h_next; + volatile uint32_t qh_e_next; +/* + * Extra information needed: + */ + struct uhci_qh *h_next; + struct uhci_qh *h_prev; + struct uhci_qh *obj_next; + struct uhci_td *e_next; + struct usb_page_cache *page_cache; + uint32_t qh_self; + uint16_t intr_pos; +} __aligned(UHCI_QH_ALIGN); + +typedef struct uhci_qh uhci_qh_t; + +/* Maximum number of isochronous TD's and QH's interrupt */ +#define UHCI_VFRAMELIST_COUNT 128 +#define UHCI_IFRAMELIST_COUNT (2 * UHCI_VFRAMELIST_COUNT) + +#if (((UHCI_VFRAMELIST_COUNT & (UHCI_VFRAMELIST_COUNT-1)) != 0) || \ + (UHCI_VFRAMELIST_COUNT > UHCI_FRAMELIST_COUNT)) +#error "UHCI_VFRAMELIST_COUNT is not power of two" +#error "or UHCI_VFRAMELIST_COUNT > UHCI_FRAMELIST_COUNT" +#endif + +#if (UHCI_VFRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER) +#error "maximum number of full-speed isochronous frames is higher than supported!" +#endif + +struct uhci_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union uhci_hub_desc { + struct usb_status stat; + struct usb_port_status ps; + uint8_t temp[128]; +}; + +struct uhci_hw_softc { + struct usb_page_cache pframes_pc; + struct usb_page_cache isoc_start_pc[UHCI_VFRAMELIST_COUNT]; + struct usb_page_cache intr_start_pc[UHCI_IFRAMELIST_COUNT]; + struct usb_page_cache ls_ctl_start_pc; + struct usb_page_cache fs_ctl_start_pc; + struct usb_page_cache bulk_start_pc; + struct usb_page_cache last_qh_pc; + struct usb_page_cache last_td_pc; + + struct usb_page pframes_pg; + struct usb_page isoc_start_pg[UHCI_VFRAMELIST_COUNT]; + struct usb_page intr_start_pg[UHCI_IFRAMELIST_COUNT]; + struct usb_page ls_ctl_start_pg; + struct usb_page fs_ctl_start_pg; + struct usb_page bulk_start_pg; + struct usb_page last_qh_pg; + struct usb_page last_td_pg; +}; + +typedef struct uhci_softc { + struct uhci_hw_softc sc_hw; + struct usb_bus sc_bus; /* base device */ + union uhci_hub_desc sc_hub_desc; + struct usb_callout sc_root_intr; + + struct usb_device *sc_devices[UHCI_MAX_DEVICES]; + /* pointer to last TD for isochronous */ + struct uhci_td *sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]; + /* pointer to last QH for interrupt */ + struct uhci_qh *sc_intr_p_last[UHCI_IFRAMELIST_COUNT]; + /* pointer to last QH for low speed control */ + struct uhci_qh *sc_ls_ctl_p_last; + /* pointer to last QH for full speed control */ + struct uhci_qh *sc_fs_ctl_p_last; + /* pointer to last QH for bulk */ + struct uhci_qh *sc_bulk_p_last; + struct uhci_qh *sc_reclaim_qh_p; + struct uhci_qh *sc_last_qh_p; + struct uhci_td *sc_last_td_p; + struct resource *sc_io_res; + struct resource *sc_irq_res; + void *sc_intr_hdl; + device_t sc_dev; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + uint32_t sc_loops; /* number of QHs that wants looping */ + + uint16_t sc_intr_stat[UHCI_IFRAMELIST_COUNT]; + + uint8_t sc_addr; /* device address */ + uint8_t sc_conf; /* device configuration */ + uint8_t sc_isreset; /* bits set if a root hub is reset */ + uint8_t sc_isresumed; /* bits set if a port was resumed */ + uint8_t sc_hub_idata[1]; + + char sc_vendor[16]; /* vendor string for root hub */ +} uhci_softc_t; + +usb_bus_mem_cb_t uhci_iterate_hw_softc; + +usb_error_t uhci_init(uhci_softc_t *sc); +void uhci_reset(uhci_softc_t *sc); +void uhci_interrupt(uhci_softc_t *sc); + +#endif /* _UHCI_H_ */ diff --git a/sys/bus/u4b/controller/uhci_pci.c b/sys/bus/u4b/controller/uhci_pci.c new file mode 100644 index 0000000000..454b904de8 --- /dev/null +++ b/sys/bus/u4b/controller/uhci_pci.c @@ -0,0 +1,452 @@ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (augustss@carlstedt.se) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* Universal Host Controller Interface + * + * UHCI spec: http://www.intel.com/ + */ + +/* The low level controller code for UHCI has been split into + * PCI probes and UHCI specific code. This was done to facilitate the + * sharing of code between *BSD's + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "usb_if.h" + +#define PCI_UHCI_VENDORID_INTEL 0x8086 +#define PCI_UHCI_VENDORID_VIA 0x1106 + +/* PIIX4E has no separate stepping */ + +static device_probe_t uhci_pci_probe; +static device_attach_t uhci_pci_attach; +static device_detach_t uhci_pci_detach; +static usb_take_controller_t uhci_pci_take_controller; + +static int +uhci_pci_take_controller(device_t self) +{ + pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2); + + return (0); +} + +static const char * +uhci_pci_match(device_t self) +{ + uint32_t device_id = pci_get_devid(self); + + switch (device_id) { + case 0x26888086: + return ("Intel 631XESB/632XESB/3100 USB controller USB-1"); + + case 0x26898086: + return ("Intel 631XESB/632XESB/3100 USB controller USB-2"); + + case 0x268a8086: + return ("Intel 631XESB/632XESB/3100 USB controller USB-3"); + + case 0x268b8086: + return ("Intel 631XESB/632XESB/3100 USB controller USB-4"); + + case 0x70208086: + return ("Intel 82371SB (PIIX3) USB controller"); + + case 0x71128086: + return ("Intel 82371AB/EB (PIIX4) USB controller"); + + case 0x24128086: + return ("Intel 82801AA (ICH) USB controller"); + + case 0x24228086: + return ("Intel 82801AB (ICH0) USB controller"); + + case 0x24428086: + return ("Intel 82801BA/BAM (ICH2) USB controller USB-A"); + + case 0x24448086: + return ("Intel 82801BA/BAM (ICH2) USB controller USB-B"); + + case 0x24828086: + return ("Intel 82801CA/CAM (ICH3) USB controller USB-A"); + + case 0x24848086: + return ("Intel 82801CA/CAM (ICH3) USB controller USB-B"); + + case 0x24878086: + return ("Intel 82801CA/CAM (ICH3) USB controller USB-C"); + + case 0x24c28086: + return ("Intel 82801DB (ICH4) USB controller USB-A"); + + case 0x24c48086: + return ("Intel 82801DB (ICH4) USB controller USB-B"); + + case 0x24c78086: + return ("Intel 82801DB (ICH4) USB controller USB-C"); + + case 0x24d28086: + return ("Intel 82801EB (ICH5) USB controller USB-A"); + + case 0x24d48086: + return ("Intel 82801EB (ICH5) USB controller USB-B"); + + case 0x24d78086: + return ("Intel 82801EB (ICH5) USB controller USB-C"); + + case 0x24de8086: + return ("Intel 82801EB (ICH5) USB controller USB-D"); + + case 0x26588086: + return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-A"); + + case 0x26598086: + return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-B"); + + case 0x265a8086: + return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-C"); + + case 0x265b8086: + return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-D"); + + case 0x27c88086: + return ("Intel 82801G (ICH7) USB controller USB-A"); + case 0x27c98086: + return ("Intel 82801G (ICH7) USB controller USB-B"); + case 0x27ca8086: + return ("Intel 82801G (ICH7) USB controller USB-C"); + case 0x27cb8086: + return ("Intel 82801G (ICH7) USB controller USB-D"); + + case 0x28308086: + return ("Intel 82801H (ICH8) USB controller USB-A"); + case 0x28318086: + return ("Intel 82801H (ICH8) USB controller USB-B"); + case 0x28328086: + return ("Intel 82801H (ICH8) USB controller USB-C"); + case 0x28348086: + return ("Intel 82801H (ICH8) USB controller USB-D"); + case 0x28358086: + return ("Intel 82801H (ICH8) USB controller USB-E"); + case 0x29348086: + return ("Intel 82801I (ICH9) USB controller"); + case 0x29358086: + return ("Intel 82801I (ICH9) USB controller"); + case 0x29368086: + return ("Intel 82801I (ICH9) USB controller"); + case 0x29378086: + return ("Intel 82801I (ICH9) USB controller"); + case 0x29388086: + return ("Intel 82801I (ICH9) USB controller"); + case 0x29398086: + return ("Intel 82801I (ICH9) USB controller"); + case 0x3a348086: + return ("Intel 82801JI (ICH10) USB controller USB-A"); + case 0x3a358086: + return ("Intel 82801JI (ICH10) USB controller USB-B"); + case 0x3a368086: + return ("Intel 82801JI (ICH10) USB controller USB-C"); + case 0x3a378086: + return ("Intel 82801JI (ICH10) USB controller USB-D"); + case 0x3a388086: + return ("Intel 82801JI (ICH10) USB controller USB-E"); + case 0x3a398086: + return ("Intel 82801JI (ICH10) USB controller USB-F"); + + case 0x719a8086: + return ("Intel 82443MX USB controller"); + + case 0x76028086: + return ("Intel 82372FB/82468GX USB controller"); + + case 0x30381106: + return ("VIA 83C572 USB controller"); + + default: + break; + } + + if ((pci_get_class(self) == PCIC_SERIALBUS) && + (pci_get_subclass(self) == PCIS_SERIALBUS_USB) && + (pci_get_progif(self) == PCI_INTERFACE_UHCI)) { + return ("UHCI (generic) USB controller"); + } + return (NULL); +} + +static int +uhci_pci_probe(device_t self) +{ + const char *desc = uhci_pci_match(self); + + if (desc) { + device_set_desc(self, desc); + return (0); + } else { + return (ENXIO); + } +} + +static int +uhci_pci_attach(device_t self) +{ + uhci_softc_t *sc = device_get_softc(self); + int rid; + int err; + + /* initialise some bus fields */ + sc->sc_bus.parent = self; + sc->sc_bus.devices = sc->sc_devices; + sc->sc_bus.devices_max = UHCI_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), + &uhci_iterate_hw_softc)) { + return ENOMEM; + } + sc->sc_dev = self; + + pci_enable_busmaster(self); + + rid = PCI_UHCI_BASE_REG; + sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_IOPORT, &rid, + RF_ACTIVE); + if (!sc->sc_io_res) { + device_printf(self, "Could not map ports\n"); + goto error; + } + sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res); + + /* disable interrupts */ + bus_space_write_2(sc->sc_io_tag, sc->sc_io_hdl, UHCI_INTR, 0); + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + goto error; + } + sc->sc_bus.bdev = device_add_child(self, "usbus", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + /* + * uhci_pci_match must never return NULL if uhci_pci_probe + * succeeded + */ + device_set_desc(sc->sc_bus.bdev, uhci_pci_match(self)); + switch (pci_get_vendor(self)) { + case PCI_UHCI_VENDORID_INTEL: + sprintf(sc->sc_vendor, "Intel"); + break; + case PCI_UHCI_VENDORID_VIA: + sprintf(sc->sc_vendor, "VIA"); + break; + default: + if (bootverbose) { + device_printf(self, "(New UHCI DeviceId=0x%08x)\n", + pci_get_devid(self)); + } + sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); + } + + switch (pci_read_config(self, PCI_USBREV, 1) & PCI_USB_REV_MASK) { + case PCI_USB_REV_PRE_1_0: + sc->sc_bus.usbrev = USB_REV_PRE_1_0; + break; + case PCI_USB_REV_1_0: + sc->sc_bus.usbrev = USB_REV_1_0; + break; + default: + /* Quirk for Parallels Desktop 4.0 */ + device_printf(self, "USB revision is unknown. Assuming v1.1.\n"); + sc->sc_bus.usbrev = USB_REV_1_1; + break; + } + +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)uhci_interrupt, sc, &sc->sc_intr_hdl); +#else + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (driver_intr_t *)uhci_interrupt, sc, &sc->sc_intr_hdl); +#endif + + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->sc_intr_hdl = NULL; + goto error; + } + /* + * Set the PIRQD enable bit and switch off all the others. We don't + * want legacy support to interfere with us XXX Does this also mean + * that the BIOS won't touch the keyboard anymore if it is connected + * to the ports of the root hub? + */ +#ifdef USB_DEBUG + if (pci_read_config(self, PCI_LEGSUP, 2) != PCI_LEGSUP_USBPIRQDEN) { + device_printf(self, "LegSup = 0x%04x\n", + pci_read_config(self, PCI_LEGSUP, 2)); + } +#endif + pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2); + + err = uhci_init(sc); + if (!err) { + err = device_probe_and_attach(sc->sc_bus.bdev); + } + if (err) { + device_printf(self, "USB init failed\n"); + goto error; + } + return (0); + +error: + uhci_pci_detach(self); + return (ENXIO); +} + +int +uhci_pci_detach(device_t self) +{ + uhci_softc_t *sc = device_get_softc(self); + device_t bdev; + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(self, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(self); + + /* + * disable interrupts that might have been switched on in + * uhci_init. + */ + if (sc->sc_io_res) { + USB_BUS_LOCK(&sc->sc_bus); + + /* stop the controller */ + uhci_reset(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); + } + pci_disable_busmaster(self); + + if (sc->sc_irq_res && sc->sc_intr_hdl) { + int err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); + + if (err) { + /* XXX or should we panic? */ + device_printf(self, "Could not tear down irq, %d\n", + err); + } + sc->sc_intr_hdl = NULL; + } + if (sc->sc_irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(self, SYS_RES_IOPORT, PCI_UHCI_BASE_REG, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_bus, &uhci_iterate_hw_softc); + + return (0); +} + +static device_method_t uhci_pci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uhci_pci_probe), + DEVMETHOD(device_attach, uhci_pci_attach), + DEVMETHOD(device_detach, uhci_pci_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(usb_take_controller, uhci_pci_take_controller), + + DEVMETHOD_END +}; + +static driver_t uhci_driver = { + .name = "uhci", + .methods = uhci_pci_methods, + .size = sizeof(struct uhci_softc), +}; + +static devclass_t uhci_devclass; + +DRIVER_MODULE(uhci, pci, uhci_driver, uhci_devclass, 0, 0); +MODULE_DEPEND(uhci, usb, 1, 1, 1); diff --git a/sys/bus/u4b/controller/uhcireg.h b/sys/bus/u4b/controller/uhcireg.h new file mode 100644 index 0000000000..95eae494e7 --- /dev/null +++ b/sys/bus/u4b/controller/uhcireg.h @@ -0,0 +1,95 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _UHCIREG_H_ +#define _UHCIREG_H_ + +#define PCI_UHCI_BASE_REG 0x20 + +/* PCI config registers */ +#define PCI_USBREV 0x60 /* USB protocol revision */ +#define PCI_USB_REV_MASK 0xff +#define PCI_USB_REV_PRE_1_0 0x00 +#define PCI_USB_REV_1_0 0x10 +#define PCI_USB_REV_1_1 0x11 +#define PCI_LEGSUP 0xc0 /* Legacy Support register */ +#define PCI_LEGSUP_USBPIRQDEN 0x2000 /* USB PIRQ D Enable */ +#define PCI_CBIO 0x20 /* configuration base IO */ +#define PCI_INTERFACE_UHCI 0x00 + +/* UHCI registers */ +#define UHCI_CMD 0x00 +#define UHCI_CMD_RS 0x0001 +#define UHCI_CMD_HCRESET 0x0002 +#define UHCI_CMD_GRESET 0x0004 +#define UHCI_CMD_EGSM 0x0008 +#define UHCI_CMD_FGR 0x0010 +#define UHCI_CMD_SWDBG 0x0020 +#define UHCI_CMD_CF 0x0040 +#define UHCI_CMD_MAXP 0x0080 +#define UHCI_STS 0x02 +#define UHCI_STS_USBINT 0x0001 +#define UHCI_STS_USBEI 0x0002 +#define UHCI_STS_RD 0x0004 +#define UHCI_STS_HSE 0x0008 +#define UHCI_STS_HCPE 0x0010 +#define UHCI_STS_HCH 0x0020 +#define UHCI_STS_ALLINTRS 0x003f +#define UHCI_INTR 0x04 +#define UHCI_INTR_TOCRCIE 0x0001 +#define UHCI_INTR_RIE 0x0002 +#define UHCI_INTR_IOCE 0x0004 +#define UHCI_INTR_SPIE 0x0008 +#define UHCI_FRNUM 0x06 +#define UHCI_FRNUM_MASK 0x03ff +#define UHCI_FLBASEADDR 0x08 +#define UHCI_SOF 0x0c +#define UHCI_SOF_MASK 0x7f +#define UHCI_PORTSC1 0x010 +#define UHCI_PORTSC2 0x012 +#define UHCI_PORTSC_CCS 0x0001 +#define UHCI_PORTSC_CSC 0x0002 +#define UHCI_PORTSC_PE 0x0004 +#define UHCI_PORTSC_POEDC 0x0008 +#define UHCI_PORTSC_LS 0x0030 +#define UHCI_PORTSC_LS_SHIFT 4 +#define UHCI_PORTSC_RD 0x0040 +#define UHCI_PORTSC_LSDA 0x0100 +#define UHCI_PORTSC_PR 0x0200 +#define UHCI_PORTSC_OCI 0x0400 +#define UHCI_PORTSC_OCIC 0x0800 +#define UHCI_PORTSC_SUSP 0x1000 + +#define URWMASK(x) ((x) & (UHCI_PORTSC_SUSP | \ + UHCI_PORTSC_PR | UHCI_PORTSC_RD | \ + UHCI_PORTSC_PE)) + +#endif /* _UHCIREG_H_ */ diff --git a/sys/bus/u4b/controller/usb_controller.c b/sys/bus/u4b/controller/usb_controller.c new file mode 100644 index 0000000000..0f942e1cdb --- /dev/null +++ b/sys/bus/u4b/controller/usb_controller.c @@ -0,0 +1,855 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "opt_ddb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define USB_DEBUG_VAR usb_ctrl_debug + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usb_if.h" + +/* function prototypes */ + +static device_probe_t usb_probe; +static device_attach_t usb_attach; +static device_detach_t usb_detach; +static device_suspend_t usb_suspend; +static device_resume_t usb_resume; +static device_shutdown_t usb_shutdown; + +static void usb_attach_sub(device_t, struct usb_bus *); + +/* static variables */ + +#ifdef USB_DEBUG +static int usb_ctrl_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ctrl, CTLFLAG_RW, 0, "USB controller"); +SYSCTL_INT(_hw_usb_ctrl, OID_AUTO, debug, CTLFLAG_RW, &usb_ctrl_debug, 0, + "Debug level"); +#endif + +static int usb_no_boot_wait = 0; +TUNABLE_INT("hw.usb.no_boot_wait", &usb_no_boot_wait); +SYSCTL_INT(_hw_usb, OID_AUTO, no_boot_wait, CTLFLAG_RDTUN, &usb_no_boot_wait, 0, + "No USB device enumerate waiting at boot."); + +static int usb_no_shutdown_wait = 0; +TUNABLE_INT("hw.usb.no_shutdown_wait", &usb_no_shutdown_wait); +SYSCTL_INT(_hw_usb, OID_AUTO, no_shutdown_wait, CTLFLAG_RW|CTLFLAG_TUN, &usb_no_shutdown_wait, 0, + "No USB device waiting at system shutdown."); + +static devclass_t usb_devclass; + +static device_method_t usb_methods[] = { + DEVMETHOD(device_probe, usb_probe), + DEVMETHOD(device_attach, usb_attach), + DEVMETHOD(device_detach, usb_detach), + DEVMETHOD(device_suspend, usb_suspend), + DEVMETHOD(device_resume, usb_resume), + DEVMETHOD(device_shutdown, usb_shutdown), + {0, 0} +}; + +static driver_t usb_driver = { + .name = "usbus", + .methods = usb_methods, + .size = 0, +}; + +/* Host Only Drivers */ +DRIVER_MODULE(usbus, ohci, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usbus, uhci, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usbus, ehci, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usbus, xhci, usb_driver, usb_devclass, 0, 0); + +/* Device Only Drivers */ +DRIVER_MODULE(usbus, at91_udp, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usbus, musbotg, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usbus, uss820, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usbus, octusb, usb_driver, usb_devclass, 0, 0); + +/*------------------------------------------------------------------------* + * usb_probe + * + * This function is called from "{ehci,ohci,uhci}_pci_attach()". + *------------------------------------------------------------------------*/ +static int +usb_probe(device_t dev) +{ + DPRINTF("\n"); + return (0); +} + +static void +usb_root_mount_rel(struct usb_bus *bus) +{ + if (bus->bus_roothold != NULL) { + DPRINTF("Releasing root mount hold %p\n", bus->bus_roothold); + root_mount_rel(bus->bus_roothold); + bus->bus_roothold = NULL; + } +} + +/*------------------------------------------------------------------------* + * usb_attach + *------------------------------------------------------------------------*/ +static int +usb_attach(device_t dev) +{ + struct usb_bus *bus = device_get_ivars(dev); + + DPRINTF("\n"); + + if (bus == NULL) { + device_printf(dev, "USB device has no ivars\n"); + return (ENXIO); + } + + if (usb_no_boot_wait == 0) { + /* delay vfs_mountroot until the bus is explored */ + bus->bus_roothold = root_mount_hold(device_get_nameunit(dev)); + } + + usb_attach_sub(dev, bus); + + return (0); /* return success */ +} + +/*------------------------------------------------------------------------* + * usb_detach + *------------------------------------------------------------------------*/ +static int +usb_detach(device_t dev) +{ + struct usb_bus *bus = device_get_softc(dev); + + DPRINTF("\n"); + + if (bus == NULL) { + /* was never setup properly */ + return (0); + } + /* Stop power watchdog */ + usb_callout_drain(&bus->power_wdog); + + /* Let the USB explore process detach all devices. */ + usb_root_mount_rel(bus); + + USB_BUS_LOCK(bus); + + /* Queue detach job */ + usb_proc_msignal(&bus->explore_proc, + &bus->detach_msg[0], &bus->detach_msg[1]); + + /* Wait for detach to complete */ + usb_proc_mwait(&bus->explore_proc, + &bus->detach_msg[0], &bus->detach_msg[1]); + + USB_BUS_UNLOCK(bus); + + /* Get rid of USB callback processes */ + + usb_proc_free(&bus->giant_callback_proc); + usb_proc_free(&bus->non_giant_callback_proc); + + /* Get rid of USB explore process */ + + usb_proc_free(&bus->explore_proc); + + /* Get rid of control transfer process */ + + usb_proc_free(&bus->control_xfer_proc); + +#if USB_HAVE_PF + usbpf_detach(bus); +#endif + return (0); +} + +/*------------------------------------------------------------------------* + * usb_suspend + *------------------------------------------------------------------------*/ +static int +usb_suspend(device_t dev) +{ + struct usb_bus *bus = device_get_softc(dev); + + DPRINTF("\n"); + + if (bus == NULL) { + /* was never setup properly */ + return (0); + } + + USB_BUS_LOCK(bus); + usb_proc_msignal(&bus->explore_proc, + &bus->suspend_msg[0], &bus->suspend_msg[1]); + USB_BUS_UNLOCK(bus); + + return (0); +} + +/*------------------------------------------------------------------------* + * usb_resume + *------------------------------------------------------------------------*/ +static int +usb_resume(device_t dev) +{ + struct usb_bus *bus = device_get_softc(dev); + + DPRINTF("\n"); + + if (bus == NULL) { + /* was never setup properly */ + return (0); + } + + USB_BUS_LOCK(bus); + usb_proc_msignal(&bus->explore_proc, + &bus->resume_msg[0], &bus->resume_msg[1]); + USB_BUS_UNLOCK(bus); + + return (0); +} + +/*------------------------------------------------------------------------* + * usb_shutdown + *------------------------------------------------------------------------*/ +static int +usb_shutdown(device_t dev) +{ + struct usb_bus *bus = device_get_softc(dev); + + DPRINTF("\n"); + + if (bus == NULL) { + /* was never setup properly */ + return (0); + } + + device_printf(bus->bdev, "Controller shutdown\n"); + + USB_BUS_LOCK(bus); + usb_proc_msignal(&bus->explore_proc, + &bus->shutdown_msg[0], &bus->shutdown_msg[1]); + if (usb_no_shutdown_wait == 0) { + /* wait for shutdown callback to be executed */ + usb_proc_mwait(&bus->explore_proc, + &bus->shutdown_msg[0], &bus->shutdown_msg[1]); + } + USB_BUS_UNLOCK(bus); + + device_printf(bus->bdev, "Controller shutdown complete\n"); + + return (0); +} + +/*------------------------------------------------------------------------* + * usb_bus_explore + * + * This function is used to explore the device tree from the root. + *------------------------------------------------------------------------*/ +static void +usb_bus_explore(struct usb_proc_msg *pm) +{ + struct usb_bus *bus; + struct usb_device *udev; + + bus = ((struct usb_bus_msg *)pm)->bus; + udev = bus->devices[USB_ROOT_HUB_ADDR]; + + if (bus->no_explore != 0) + return; + + if (udev && udev->hub) { + + if (bus->do_probe) { + bus->do_probe = 0; + bus->driver_added_refcount++; + } + if (bus->driver_added_refcount == 0) { + /* avoid zero, hence that is memory default */ + bus->driver_added_refcount = 1; + } + +#ifdef DDB + /* + * The following three lines of code are only here to + * recover from DDB: + */ + usb_proc_rewakeup(&bus->control_xfer_proc); + usb_proc_rewakeup(&bus->giant_callback_proc); + usb_proc_rewakeup(&bus->non_giant_callback_proc); +#endif + + USB_BUS_UNLOCK(bus); + +#if USB_HAVE_POWERD + /* + * First update the USB power state! + */ + usb_bus_powerd(bus); +#endif + /* Explore the Root USB HUB. */ + (udev->hub->explore) (udev); + USB_BUS_LOCK(bus); + } + usb_root_mount_rel(bus); +} + +/*------------------------------------------------------------------------* + * usb_bus_detach + * + * This function is used to detach the device tree from the root. + *------------------------------------------------------------------------*/ +static void +usb_bus_detach(struct usb_proc_msg *pm) +{ + struct usb_bus *bus; + struct usb_device *udev; + device_t dev; + + bus = ((struct usb_bus_msg *)pm)->bus; + udev = bus->devices[USB_ROOT_HUB_ADDR]; + dev = bus->bdev; + /* clear the softc */ + device_set_softc(dev, NULL); + USB_BUS_UNLOCK(bus); + + /* detach children first */ + mtx_lock(&Giant); + bus_generic_detach(dev); + mtx_unlock(&Giant); + + /* + * Free USB device and all subdevices, if any. + */ + usb_free_device(udev, 0); + + USB_BUS_LOCK(bus); + /* clear bdev variable last */ + bus->bdev = NULL; +} + +/*------------------------------------------------------------------------* + * usb_bus_suspend + * + * This function is used to suspend the USB contoller. + *------------------------------------------------------------------------*/ +static void +usb_bus_suspend(struct usb_proc_msg *pm) +{ + struct usb_bus *bus; + struct usb_device *udev; + usb_error_t err; + + bus = ((struct usb_bus_msg *)pm)->bus; + udev = bus->devices[USB_ROOT_HUB_ADDR]; + + if (udev == NULL || bus->bdev == NULL) + return; + + USB_BUS_UNLOCK(bus); + + bus_generic_shutdown(bus->bdev); + + usbd_enum_lock(udev); + + err = usbd_set_config_index(udev, USB_UNCONFIG_INDEX); + if (err) + device_printf(bus->bdev, "Could not unconfigure root HUB\n"); + + USB_BUS_LOCK(bus); + bus->hw_power_state = 0; + bus->no_explore = 1; + USB_BUS_UNLOCK(bus); + + if (bus->methods->set_hw_power != NULL) + (bus->methods->set_hw_power) (bus); + + if (bus->methods->set_hw_power_sleep != NULL) + (bus->methods->set_hw_power_sleep) (bus, USB_HW_POWER_SUSPEND); + + usbd_enum_unlock(udev); + + USB_BUS_LOCK(bus); +} + +/*------------------------------------------------------------------------* + * usb_bus_resume + * + * This function is used to resume the USB contoller. + *------------------------------------------------------------------------*/ +static void +usb_bus_resume(struct usb_proc_msg *pm) +{ + struct usb_bus *bus; + struct usb_device *udev; + usb_error_t err; + + bus = ((struct usb_bus_msg *)pm)->bus; + udev = bus->devices[USB_ROOT_HUB_ADDR]; + + if (udev == NULL || bus->bdev == NULL) + return; + + USB_BUS_UNLOCK(bus); + + usbd_enum_lock(udev); +#if 0 + DEVMETHOD(usb_take_controller, NULL); /* dummy */ +#endif + USB_TAKE_CONTROLLER(device_get_parent(bus->bdev)); + + USB_BUS_LOCK(bus); + bus->hw_power_state = + USB_HW_POWER_CONTROL | + USB_HW_POWER_BULK | + USB_HW_POWER_INTERRUPT | + USB_HW_POWER_ISOC | + USB_HW_POWER_NON_ROOT_HUB; + bus->no_explore = 0; + USB_BUS_UNLOCK(bus); + + if (bus->methods->set_hw_power_sleep != NULL) + (bus->methods->set_hw_power_sleep) (bus, USB_HW_POWER_RESUME); + + if (bus->methods->set_hw_power != NULL) + (bus->methods->set_hw_power) (bus); + + /* restore USB configuration to index 0 */ + err = usbd_set_config_index(udev, 0); + if (err) + device_printf(bus->bdev, "Could not configure root HUB\n"); + + /* probe and attach */ + err = usb_probe_and_attach(udev, USB_IFACE_INDEX_ANY); + if (err) { + device_printf(bus->bdev, "Could not probe and " + "attach root HUB\n"); + } + + usbd_enum_unlock(udev); + + USB_BUS_LOCK(bus); +} + +/*------------------------------------------------------------------------* + * usb_bus_shutdown + * + * This function is used to shutdown the USB contoller. + *------------------------------------------------------------------------*/ +static void +usb_bus_shutdown(struct usb_proc_msg *pm) +{ + struct usb_bus *bus; + struct usb_device *udev; + usb_error_t err; + + bus = ((struct usb_bus_msg *)pm)->bus; + udev = bus->devices[USB_ROOT_HUB_ADDR]; + + if (udev == NULL || bus->bdev == NULL) + return; + + USB_BUS_UNLOCK(bus); + + bus_generic_shutdown(bus->bdev); + + usbd_enum_lock(udev); + + err = usbd_set_config_index(udev, USB_UNCONFIG_INDEX); + if (err) + device_printf(bus->bdev, "Could not unconfigure root HUB\n"); + + USB_BUS_LOCK(bus); + bus->hw_power_state = 0; + bus->no_explore = 1; + USB_BUS_UNLOCK(bus); + + if (bus->methods->set_hw_power != NULL) + (bus->methods->set_hw_power) (bus); + + if (bus->methods->set_hw_power_sleep != NULL) + (bus->methods->set_hw_power_sleep) (bus, USB_HW_POWER_SHUTDOWN); + + usbd_enum_unlock(udev); + + USB_BUS_LOCK(bus); +} + +static void +usb_power_wdog(void *arg) +{ + struct usb_bus *bus = arg; + + USB_BUS_LOCK_ASSERT(bus, MA_OWNED); + + usb_callout_reset(&bus->power_wdog, + 4 * hz, usb_power_wdog, arg); + +#ifdef DDB + /* + * The following line of code is only here to recover from + * DDB: + */ + usb_proc_rewakeup(&bus->explore_proc); /* recover from DDB */ +#endif + +#if USB_HAVE_POWERD + USB_BUS_UNLOCK(bus); + + usb_bus_power_update(bus); + + USB_BUS_LOCK(bus); +#endif +} + +/*------------------------------------------------------------------------* + * usb_bus_attach + * + * This function attaches USB in context of the explore thread. + *------------------------------------------------------------------------*/ +static void +usb_bus_attach(struct usb_proc_msg *pm) +{ + struct usb_bus *bus; + struct usb_device *child; + device_t dev; + usb_error_t err; + enum usb_dev_speed speed; + + bus = ((struct usb_bus_msg *)pm)->bus; + dev = bus->bdev; + + DPRINTF("\n"); + + switch (bus->usbrev) { + case USB_REV_1_0: + speed = USB_SPEED_FULL; + device_printf(bus->bdev, "12Mbps Full Speed USB v1.0\n"); + break; + + case USB_REV_1_1: + speed = USB_SPEED_FULL; + device_printf(bus->bdev, "12Mbps Full Speed USB v1.1\n"); + break; + + case USB_REV_2_0: + speed = USB_SPEED_HIGH; + device_printf(bus->bdev, "480Mbps High Speed USB v2.0\n"); + break; + + case USB_REV_2_5: + speed = USB_SPEED_VARIABLE; + device_printf(bus->bdev, "480Mbps Wireless USB v2.5\n"); + break; + + case USB_REV_3_0: + speed = USB_SPEED_SUPER; + device_printf(bus->bdev, "5.0Gbps Super Speed USB v3.0\n"); + break; + + default: + device_printf(bus->bdev, "Unsupported USB revision\n"); + usb_root_mount_rel(bus); + return; + } + + /* default power_mask value */ + bus->hw_power_state = + USB_HW_POWER_CONTROL | + USB_HW_POWER_BULK | + USB_HW_POWER_INTERRUPT | + USB_HW_POWER_ISOC | + USB_HW_POWER_NON_ROOT_HUB; + + USB_BUS_UNLOCK(bus); + + /* make sure power is set at least once */ + + if (bus->methods->set_hw_power != NULL) { + (bus->methods->set_hw_power) (bus); + } + + /* allocate the Root USB device */ + + child = usb_alloc_device(bus->bdev, bus, NULL, 0, 0, 1, + speed, USB_MODE_HOST); + if (child) { + err = usb_probe_and_attach(child, + USB_IFACE_INDEX_ANY); + if (!err) { + if ((bus->devices[USB_ROOT_HUB_ADDR] == NULL) || + (bus->devices[USB_ROOT_HUB_ADDR]->hub == NULL)) { + err = USB_ERR_NO_ROOT_HUB; + } + } + } else { + err = USB_ERR_NOMEM; + } + + USB_BUS_LOCK(bus); + + if (err) { + device_printf(bus->bdev, "Root HUB problem, error=%s\n", + usbd_errstr(err)); + usb_root_mount_rel(bus); + } + + /* set softc - we are ready */ + device_set_softc(dev, bus); + + /* start watchdog */ + usb_power_wdog(bus); +} + +/*------------------------------------------------------------------------* + * usb_attach_sub + * + * This function creates a thread which runs the USB attach code. + *------------------------------------------------------------------------*/ +static void +usb_attach_sub(device_t dev, struct usb_bus *bus) +{ + const char *pname = device_get_nameunit(dev); + + mtx_lock(&Giant); + if (usb_devclass_ptr == NULL) + usb_devclass_ptr = devclass_find("usbus"); + mtx_unlock(&Giant); + +#if USB_HAVE_PF + usbpf_attach(bus); +#endif + /* Initialise USB process messages */ + bus->explore_msg[0].hdr.pm_callback = &usb_bus_explore; + bus->explore_msg[0].bus = bus; + bus->explore_msg[1].hdr.pm_callback = &usb_bus_explore; + bus->explore_msg[1].bus = bus; + + bus->detach_msg[0].hdr.pm_callback = &usb_bus_detach; + bus->detach_msg[0].bus = bus; + bus->detach_msg[1].hdr.pm_callback = &usb_bus_detach; + bus->detach_msg[1].bus = bus; + + bus->attach_msg[0].hdr.pm_callback = &usb_bus_attach; + bus->attach_msg[0].bus = bus; + bus->attach_msg[1].hdr.pm_callback = &usb_bus_attach; + bus->attach_msg[1].bus = bus; + + bus->suspend_msg[0].hdr.pm_callback = &usb_bus_suspend; + bus->suspend_msg[0].bus = bus; + bus->suspend_msg[1].hdr.pm_callback = &usb_bus_suspend; + bus->suspend_msg[1].bus = bus; + + bus->resume_msg[0].hdr.pm_callback = &usb_bus_resume; + bus->resume_msg[0].bus = bus; + bus->resume_msg[1].hdr.pm_callback = &usb_bus_resume; + bus->resume_msg[1].bus = bus; + + bus->shutdown_msg[0].hdr.pm_callback = &usb_bus_shutdown; + bus->shutdown_msg[0].bus = bus; + bus->shutdown_msg[1].hdr.pm_callback = &usb_bus_shutdown; + bus->shutdown_msg[1].bus = bus; + + /* Create USB explore and callback processes */ + + if (usb_proc_create(&bus->giant_callback_proc, + &bus->bus_mtx, pname, USB_PRI_MED)) { + device_printf(dev, "WARNING: Creation of USB Giant " + "callback process failed.\n"); + } else if (usb_proc_create(&bus->non_giant_callback_proc, + &bus->bus_mtx, pname, USB_PRI_HIGH)) { + device_printf(dev, "WARNING: Creation of USB non-Giant " + "callback process failed.\n"); + } else if (usb_proc_create(&bus->explore_proc, + &bus->bus_mtx, pname, USB_PRI_MED)) { + device_printf(dev, "WARNING: Creation of USB explore " + "process failed.\n"); + } else if (usb_proc_create(&bus->control_xfer_proc, + &bus->bus_mtx, pname, USB_PRI_MED)) { + device_printf(dev, "WARNING: Creation of USB control transfer " + "process failed.\n"); + } else { + /* Get final attach going */ + USB_BUS_LOCK(bus); + usb_proc_msignal(&bus->explore_proc, + &bus->attach_msg[0], &bus->attach_msg[1]); + USB_BUS_UNLOCK(bus); + + /* Do initial explore */ + usb_needs_explore(bus, 1); + } +} + +SYSUNINIT(usb_bus_unload, SI_SUB_KLD, SI_ORDER_ANY, usb_bus_unload, NULL); + +/*------------------------------------------------------------------------* + * usb_bus_mem_flush_all_cb + *------------------------------------------------------------------------*/ +#if USB_HAVE_BUSDMA +static void +usb_bus_mem_flush_all_cb(struct usb_bus *bus, struct usb_page_cache *pc, + struct usb_page *pg, usb_size_t size, usb_size_t align) +{ + usb_pc_cpu_flush(pc); +} +#endif + +/*------------------------------------------------------------------------* + * usb_bus_mem_flush_all - factored out code + *------------------------------------------------------------------------*/ +#if USB_HAVE_BUSDMA +void +usb_bus_mem_flush_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb) +{ + if (cb) { + cb(bus, &usb_bus_mem_flush_all_cb); + } +} +#endif + +/*------------------------------------------------------------------------* + * usb_bus_mem_alloc_all_cb + *------------------------------------------------------------------------*/ +#if USB_HAVE_BUSDMA +static void +usb_bus_mem_alloc_all_cb(struct usb_bus *bus, struct usb_page_cache *pc, + struct usb_page *pg, usb_size_t size, usb_size_t align) +{ + /* need to initialize the page cache */ + pc->tag_parent = bus->dma_parent_tag; + + if (usb_pc_alloc_mem(pc, pg, size, align)) { + bus->alloc_failed = 1; + } +} +#endif + +/*------------------------------------------------------------------------* + * usb_bus_mem_alloc_all - factored out code + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb_bus_mem_alloc_all(struct usb_bus *bus, bus_dma_tag_t dmat, + usb_bus_mem_cb_t *cb) +{ + bus->alloc_failed = 0; + + mtx_init(&bus->bus_mtx, device_get_nameunit(bus->parent), + NULL, MTX_DEF | MTX_RECURSE); + + usb_callout_init_mtx(&bus->power_wdog, + &bus->bus_mtx, 0); + + TAILQ_INIT(&bus->intr_q.head); + +#if USB_HAVE_BUSDMA + usb_dma_tag_setup(bus->dma_parent_tag, bus->dma_tags, + dmat, &bus->bus_mtx, NULL, 32, USB_BUS_DMA_TAG_MAX); +#endif + if ((bus->devices_max > USB_MAX_DEVICES) || + (bus->devices_max < USB_MIN_DEVICES) || + (bus->devices == NULL)) { + DPRINTFN(0, "Devices field has not been " + "initialised properly\n"); + bus->alloc_failed = 1; /* failure */ + } +#if USB_HAVE_BUSDMA + if (cb) { + cb(bus, &usb_bus_mem_alloc_all_cb); + } +#endif + if (bus->alloc_failed) { + usb_bus_mem_free_all(bus, cb); + } + return (bus->alloc_failed); +} + +/*------------------------------------------------------------------------* + * usb_bus_mem_free_all_cb + *------------------------------------------------------------------------*/ +#if USB_HAVE_BUSDMA +static void +usb_bus_mem_free_all_cb(struct usb_bus *bus, struct usb_page_cache *pc, + struct usb_page *pg, usb_size_t size, usb_size_t align) +{ + usb_pc_free_mem(pc); +} +#endif + +/*------------------------------------------------------------------------* + * usb_bus_mem_free_all - factored out code + *------------------------------------------------------------------------*/ +void +usb_bus_mem_free_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb) +{ +#if USB_HAVE_BUSDMA + if (cb) { + cb(bus, &usb_bus_mem_free_all_cb); + } + usb_dma_tag_unsetup(bus->dma_parent_tag); +#endif + + mtx_destroy(&bus->bus_mtx); +} diff --git a/sys/bus/u4b/controller/uss820dci.c b/sys/bus/u4b/controller/uss820dci.c new file mode 100644 index 0000000000..612bc81bb2 --- /dev/null +++ b/sys/bus/u4b/controller/uss820dci.c @@ -0,0 +1,2394 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains the driver for the USS820 series USB Device + * Controller + * + * NOTE: The datasheet does not document everything. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define USB_DEBUG_VAR uss820dcidebug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define USS820_DCI_BUS2SC(bus) \ + ((struct uss820dci_softc *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((struct uss820dci_softc *)0)->sc_bus)))) + +#define USS820_DCI_PC2SC(pc) \ + USS820_DCI_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus) + +#ifdef USB_DEBUG +static int uss820dcidebug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uss820dci, CTLFLAG_RW, 0, + "USB uss820dci"); +SYSCTL_INT(_hw_usb_uss820dci, OID_AUTO, debug, CTLFLAG_RW, + &uss820dcidebug, 0, "uss820dci debug level"); +#endif + +#define USS820_DCI_INTR_ENDPT 1 + +/* prototypes */ + +struct usb_bus_methods uss820dci_bus_methods; +struct usb_pipe_methods uss820dci_device_bulk_methods; +struct usb_pipe_methods uss820dci_device_ctrl_methods; +struct usb_pipe_methods uss820dci_device_intr_methods; +struct usb_pipe_methods uss820dci_device_isoc_fs_methods; + +static uss820dci_cmd_t uss820dci_setup_rx; +static uss820dci_cmd_t uss820dci_data_rx; +static uss820dci_cmd_t uss820dci_data_tx; +static uss820dci_cmd_t uss820dci_data_tx_sync; +static void uss820dci_device_done(struct usb_xfer *, usb_error_t); +static void uss820dci_do_poll(struct usb_bus *); +static void uss820dci_standard_done(struct usb_xfer *); +static void uss820dci_intr_set(struct usb_xfer *, uint8_t); +static void uss820dci_update_shared_1(struct uss820dci_softc *, uint8_t, + uint8_t, uint8_t); +static void uss820dci_root_intr(struct uss820dci_softc *); + +/* + * Here is a list of what the USS820D chip can support. The main + * limitation is that the sum of the buffer sizes must be less than + * 1120 bytes. + */ +static const struct usb_hw_ep_profile + uss820dci_ep_profile[] = { + + [0] = { + .max_in_frame_size = 32, + .max_out_frame_size = 32, + .is_simplex = 0, + .support_control = 1, + }, + [1] = { + .max_in_frame_size = 64, + .max_out_frame_size = 64, + .is_simplex = 0, + .support_multi_buffer = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_in = 1, + .support_out = 1, + }, + [2] = { + .max_in_frame_size = 8, + .max_out_frame_size = 8, + .is_simplex = 0, + .support_multi_buffer = 1, + .support_bulk = 1, + .support_interrupt = 1, + .support_in = 1, + .support_out = 1, + }, + [3] = { + .max_in_frame_size = 256, + .max_out_frame_size = 256, + .is_simplex = 0, + .support_multi_buffer = 1, + .support_isochronous = 1, + .support_in = 1, + .support_out = 1, + }, +}; + +static void +uss820dci_update_shared_1(struct uss820dci_softc *sc, uint8_t reg, + uint8_t keep_mask, uint8_t set_mask) +{ + uint8_t temp; + + USS820_WRITE_1(sc, USS820_PEND, 1); + temp = USS820_READ_1(sc, reg); + temp &= (keep_mask); + temp |= (set_mask); + USS820_WRITE_1(sc, reg, temp); + USS820_WRITE_1(sc, USS820_PEND, 0); +} + +static void +uss820dci_get_hw_ep_profile(struct usb_device *udev, + const struct usb_hw_ep_profile **ppf, uint8_t ep_addr) +{ + if (ep_addr == 0) { + *ppf = uss820dci_ep_profile + 0; + } else if (ep_addr < 5) { + *ppf = uss820dci_ep_profile + 1; + } else if (ep_addr < 7) { + *ppf = uss820dci_ep_profile + 2; + } else if (ep_addr == 7) { + *ppf = uss820dci_ep_profile + 3; + } else { + *ppf = NULL; + } +} + +static void +uss820dci_pull_up(struct uss820dci_softc *sc) +{ + uint8_t temp; + + /* pullup D+, if possible */ + + if (!sc->sc_flags.d_pulled_up && + sc->sc_flags.port_powered) { + sc->sc_flags.d_pulled_up = 1; + + DPRINTF("\n"); + + temp = USS820_READ_1(sc, USS820_MCSR); + temp |= USS820_MCSR_DPEN; + USS820_WRITE_1(sc, USS820_MCSR, temp); + } +} + +static void +uss820dci_pull_down(struct uss820dci_softc *sc) +{ + uint8_t temp; + + /* pulldown D+, if possible */ + + if (sc->sc_flags.d_pulled_up) { + sc->sc_flags.d_pulled_up = 0; + + DPRINTF("\n"); + + temp = USS820_READ_1(sc, USS820_MCSR); + temp &= ~USS820_MCSR_DPEN; + USS820_WRITE_1(sc, USS820_MCSR, temp); + } +} + +static void +uss820dci_wakeup_peer(struct uss820dci_softc *sc) +{ + if (!(sc->sc_flags.status_suspend)) { + return; + } + DPRINTFN(0, "not supported\n"); +} + +static void +uss820dci_set_address(struct uss820dci_softc *sc, uint8_t addr) +{ + DPRINTFN(5, "addr=%d\n", addr); + + USS820_WRITE_1(sc, USS820_FADDR, addr); +} + +static uint8_t +uss820dci_setup_rx(struct uss820dci_td *td) +{ + struct uss820dci_softc *sc; + struct usb_device_request req; + uint16_t count; + uint8_t rx_stat; + uint8_t temp; + + /* select the correct endpoint */ + bus_space_write_1(td->io_tag, td->io_hdl, + USS820_EPINDEX, td->ep_index); + + /* read out FIFO status */ + rx_stat = bus_space_read_1(td->io_tag, td->io_hdl, + USS820_RXSTAT); + + /* get pointer to softc */ + sc = USS820_DCI_PC2SC(td->pc); + + DPRINTFN(5, "rx_stat=0x%02x rem=%u\n", rx_stat, td->remainder); + + if (!(rx_stat & USS820_RXSTAT_RXSETUP)) { + goto not_complete; + } + /* clear did stall */ + td->did_stall = 0; + + /* clear stall and all I/O */ + uss820dci_update_shared_1(sc, USS820_EPCON, + 0xFF ^ (USS820_EPCON_TXSTL | + USS820_EPCON_RXSTL | + USS820_EPCON_RXIE | + USS820_EPCON_TXOE), 0); + + /* clear end overwrite flag */ + uss820dci_update_shared_1(sc, USS820_RXSTAT, + 0xFF ^ USS820_RXSTAT_EDOVW, 0); + + /* get the packet byte count */ + count = bus_space_read_1(td->io_tag, td->io_hdl, + USS820_RXCNTL); + count |= (bus_space_read_1(td->io_tag, td->io_hdl, + USS820_RXCNTH) << 8); + count &= 0x3FF; + + /* verify data length */ + if (count != td->remainder) { + DPRINTFN(0, "Invalid SETUP packet " + "length, %d bytes\n", count); + goto setup_not_complete; + } + if (count != sizeof(req)) { + DPRINTFN(0, "Unsupported SETUP packet " + "length, %d bytes\n", count); + goto setup_not_complete; + } + /* receive data */ + bus_space_read_multi_1(td->io_tag, td->io_hdl, + USS820_RXDAT, (void *)&req, sizeof(req)); + + /* read out FIFO status */ + rx_stat = bus_space_read_1(td->io_tag, td->io_hdl, + USS820_RXSTAT); + + if (rx_stat & (USS820_RXSTAT_EDOVW | + USS820_RXSTAT_STOVW)) { + DPRINTF("new SETUP packet received\n"); + return (1); /* not complete */ + } + /* clear receive setup bit */ + uss820dci_update_shared_1(sc, USS820_RXSTAT, + 0xFF ^ (USS820_RXSTAT_RXSETUP | + USS820_RXSTAT_EDOVW | + USS820_RXSTAT_STOVW), 0); + + /* set RXFFRC bit */ + temp = bus_space_read_1(td->io_tag, td->io_hdl, + USS820_RXCON); + temp |= USS820_RXCON_RXFFRC; + bus_space_write_1(td->io_tag, td->io_hdl, + USS820_RXCON, temp); + + /* copy data into real buffer */ + usbd_copy_in(td->pc, 0, &req, sizeof(req)); + + td->offset = sizeof(req); + td->remainder = 0; + + /* sneak peek the set address */ + if ((req.bmRequestType == UT_WRITE_DEVICE) && + (req.bRequest == UR_SET_ADDRESS)) { + sc->sc_dv_addr = req.wValue[0] & 0x7F; + } else { + sc->sc_dv_addr = 0xFF; + } + + /* reset TX FIFO */ + temp = USS820_READ_1(sc, USS820_TXCON); + temp |= USS820_TXCON_TXCLR; + USS820_WRITE_1(sc, USS820_TXCON, temp); + temp &= ~USS820_TXCON_TXCLR; + USS820_WRITE_1(sc, USS820_TXCON, temp); + + return (0); /* complete */ + +setup_not_complete: + + /* set RXFFRC bit */ + temp = bus_space_read_1(td->io_tag, td->io_hdl, + USS820_RXCON); + temp |= USS820_RXCON_RXFFRC; + bus_space_write_1(td->io_tag, td->io_hdl, + USS820_RXCON, temp); + + /* FALLTHROUGH */ + +not_complete: + /* abort any ongoing transfer */ + if (!td->did_stall) { + DPRINTFN(5, "stalling\n"); + /* set stall */ + uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF, + (USS820_EPCON_TXSTL | USS820_EPCON_RXSTL)); + + td->did_stall = 1; + } + + /* clear end overwrite flag, if any */ + if (rx_stat & USS820_RXSTAT_RXSETUP) { + uss820dci_update_shared_1(sc, USS820_RXSTAT, + 0xFF ^ (USS820_RXSTAT_EDOVW | + USS820_RXSTAT_STOVW | + USS820_RXSTAT_RXSETUP), 0); + } + return (1); /* not complete */ + +} + +static uint8_t +uss820dci_data_rx(struct uss820dci_td *td) +{ + struct usb_page_search buf_res; + uint16_t count; + uint8_t rx_flag; + uint8_t rx_stat; + uint8_t rx_cntl; + uint8_t to; + uint8_t got_short; + + to = 2; /* don't loop forever! */ + got_short = 0; + + /* select the correct endpoint */ + bus_space_write_1(td->io_tag, td->io_hdl, USS820_EPINDEX, td->ep_index); + + /* check if any of the FIFO banks have data */ +repeat: + /* read out FIFO flag */ + rx_flag = bus_space_read_1(td->io_tag, td->io_hdl, + USS820_RXFLG); + /* read out FIFO status */ + rx_stat = bus_space_read_1(td->io_tag, td->io_hdl, + USS820_RXSTAT); + + DPRINTFN(5, "rx_stat=0x%02x rx_flag=0x%02x rem=%u\n", + rx_stat, rx_flag, td->remainder); + + if (rx_stat & (USS820_RXSTAT_RXSETUP | + USS820_RXSTAT_RXSOVW | + USS820_RXSTAT_EDOVW)) { + if (td->remainder == 0) { + /* + * We are actually complete and have + * received the next SETUP + */ + DPRINTFN(5, "faking complete\n"); + return (0); /* complete */ + } + /* + * USB Host Aborted the transfer. + */ + td->error = 1; + return (0); /* complete */ + } + /* check for errors */ + if (rx_flag & (USS820_RXFLG_RXOVF | + USS820_RXFLG_RXURF)) { + DPRINTFN(5, "overflow or underflow\n"); + /* should not happen */ + td->error = 1; + return (0); /* complete */ + } + /* check status */ + if (!(rx_flag & (USS820_RXFLG_RXFIF0 | + USS820_RXFLG_RXFIF1))) { + + /* read out EPCON register */ + /* enable RX input */ + if (!td->did_enable) { + uss820dci_update_shared_1(USS820_DCI_PC2SC(td->pc), + USS820_EPCON, 0xFF, USS820_EPCON_RXIE); + td->did_enable = 1; + } + return (1); /* not complete */ + } + /* get the packet byte count */ + count = bus_space_read_1(td->io_tag, td->io_hdl, + USS820_RXCNTL); + + count |= (bus_space_read_1(td->io_tag, td->io_hdl, + USS820_RXCNTH) << 8); + count &= 0x3FF; + + DPRINTFN(5, "count=0x%04x\n", count); + + /* verify the packet byte count */ + if (count != td->max_packet_size) { + if (count < td->max_packet_size) { + /* we have a short packet */ + td->short_pkt = 1; + got_short = 1; + } else { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + } + /* verify the packet byte count */ + if (count > td->remainder) { + /* invalid USB packet */ + td->error = 1; + return (0); /* we are complete */ + } + while (count > 0) { + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* receive data */ + bus_space_read_multi_1(td->io_tag, td->io_hdl, + USS820_RXDAT, buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* set RXFFRC bit */ + rx_cntl = bus_space_read_1(td->io_tag, td->io_hdl, + USS820_RXCON); + rx_cntl |= USS820_RXCON_RXFFRC; + bus_space_write_1(td->io_tag, td->io_hdl, + USS820_RXCON, rx_cntl); + + /* check if we are complete */ + if ((td->remainder == 0) || got_short) { + if (td->short_pkt) { + /* we are complete */ + return (0); + } + /* else need to receive a zero length packet */ + } + if (--to) { + goto repeat; + } + return (1); /* not complete */ +} + +static uint8_t +uss820dci_data_tx(struct uss820dci_td *td) +{ + struct usb_page_search buf_res; + uint16_t count; + uint16_t count_copy; + uint8_t rx_stat; + uint8_t tx_flag; + uint8_t to; + + /* select the correct endpoint */ + bus_space_write_1(td->io_tag, td->io_hdl, + USS820_EPINDEX, td->ep_index); + + to = 2; /* don't loop forever! */ + +repeat: + /* read out TX FIFO flags */ + tx_flag = bus_space_read_1(td->io_tag, td->io_hdl, + USS820_TXFLG); + + /* read out RX FIFO status last */ + rx_stat = bus_space_read_1(td->io_tag, td->io_hdl, + USS820_RXSTAT); + + DPRINTFN(5, "rx_stat=0x%02x tx_flag=0x%02x rem=%u\n", + rx_stat, tx_flag, td->remainder); + + if (rx_stat & (USS820_RXSTAT_RXSETUP | + USS820_RXSTAT_RXSOVW | + USS820_RXSTAT_EDOVW)) { + /* + * The current transfer was aborted + * by the USB Host + */ + td->error = 1; + return (0); /* complete */ + } + if (tx_flag & (USS820_TXFLG_TXOVF | + USS820_TXFLG_TXURF)) { + td->error = 1; + return (0); /* complete */ + } + if (tx_flag & USS820_TXFLG_TXFIF0) { + if (tx_flag & USS820_TXFLG_TXFIF1) { + return (1); /* not complete */ + } + } + if ((!td->support_multi_buffer) && + (tx_flag & (USS820_TXFLG_TXFIF0 | + USS820_TXFLG_TXFIF1))) { + return (1); /* not complete */ + } + count = td->max_packet_size; + if (td->remainder < count) { + /* we have a short packet */ + td->short_pkt = 1; + count = td->remainder; + } + count_copy = count; + while (count > 0) { + + usbd_get_page(td->pc, td->offset, &buf_res); + + /* get correct length */ + if (buf_res.length > count) { + buf_res.length = count; + } + /* transmit data */ + bus_space_write_multi_1(td->io_tag, td->io_hdl, + USS820_TXDAT, buf_res.buffer, buf_res.length); + + /* update counters */ + count -= buf_res.length; + td->offset += buf_res.length; + td->remainder -= buf_res.length; + } + + /* post-write high packet byte count first */ + bus_space_write_1(td->io_tag, td->io_hdl, + USS820_TXCNTH, count_copy >> 8); + + /* post-write low packet byte count last */ + bus_space_write_1(td->io_tag, td->io_hdl, + USS820_TXCNTL, count_copy); + + /* + * Enable TX output, which must happen after that we have written + * data into the FIFO. This is undocumented. + */ + if (!td->did_enable) { + uss820dci_update_shared_1(USS820_DCI_PC2SC(td->pc), + USS820_EPCON, 0xFF, USS820_EPCON_TXOE); + td->did_enable = 1; + } + /* check remainder */ + if (td->remainder == 0) { + if (td->short_pkt) { + return (0); /* complete */ + } + /* else we need to transmit a short packet */ + } + if (--to) { + goto repeat; + } + return (1); /* not complete */ +} + +static uint8_t +uss820dci_data_tx_sync(struct uss820dci_td *td) +{ + struct uss820dci_softc *sc; + uint8_t rx_stat; + uint8_t tx_flag; + + /* select the correct endpoint */ + bus_space_write_1(td->io_tag, td->io_hdl, + USS820_EPINDEX, td->ep_index); + + /* read out TX FIFO flag */ + tx_flag = bus_space_read_1(td->io_tag, td->io_hdl, + USS820_TXFLG); + + /* read out RX FIFO status last */ + rx_stat = bus_space_read_1(td->io_tag, td->io_hdl, + USS820_RXSTAT); + + DPRINTFN(5, "rx_stat=0x%02x rem=%u\n", rx_stat, td->remainder); + + if (rx_stat & (USS820_RXSTAT_RXSETUP | + USS820_RXSTAT_RXSOVW | + USS820_RXSTAT_EDOVW)) { + DPRINTFN(5, "faking complete\n"); + /* Race condition */ + return (0); /* complete */ + } + DPRINTFN(5, "tx_flag=0x%02x rem=%u\n", + tx_flag, td->remainder); + + if (tx_flag & (USS820_TXFLG_TXOVF | + USS820_TXFLG_TXURF)) { + td->error = 1; + return (0); /* complete */ + } + if (tx_flag & (USS820_TXFLG_TXFIF0 | + USS820_TXFLG_TXFIF1)) { + return (1); /* not complete */ + } + sc = USS820_DCI_PC2SC(td->pc); + if (sc->sc_dv_addr != 0xFF) { + /* write function address */ + uss820dci_set_address(sc, sc->sc_dv_addr); + } + return (0); /* complete */ +} + +static uint8_t +uss820dci_xfer_do_fifo(struct usb_xfer *xfer) +{ + struct uss820dci_td *td; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + while (1) { + if ((td->func) (td)) { + /* operation in progress */ + break; + } + if (((void *)td) == xfer->td_transfer_last) { + goto done; + } + if (td->error) { + goto done; + } else if (td->remainder > 0) { + /* + * We had a short transfer. If there is no alternate + * next, stop processing ! + */ + if (!td->alt_next) { + goto done; + } + } + /* + * Fetch the next transfer descriptor. + */ + td = td->obj_next; + xfer->td_transfer_cache = td; + } + return (1); /* not complete */ + +done: + /* compute all actual lengths */ + + uss820dci_standard_done(xfer); + + return (0); /* complete */ +} + +static void +uss820dci_interrupt_poll(struct uss820dci_softc *sc) +{ + struct usb_xfer *xfer; + +repeat: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + if (!uss820dci_xfer_do_fifo(xfer)) { + /* queue has been modified */ + goto repeat; + } + } +} + +static void +uss820dci_wait_suspend(struct uss820dci_softc *sc, uint8_t on) +{ + uint8_t scr; + uint8_t scratch; + + scr = USS820_READ_1(sc, USS820_SCR); + scratch = USS820_READ_1(sc, USS820_SCRATCH); + + if (on) { + scr |= USS820_SCR_IE_SUSP; + scratch &= ~USS820_SCRATCH_IE_RESUME; + } else { + scr &= ~USS820_SCR_IE_SUSP; + scratch |= USS820_SCRATCH_IE_RESUME; + } + + USS820_WRITE_1(sc, USS820_SCR, scr); + USS820_WRITE_1(sc, USS820_SCRATCH, scratch); +} + +void +uss820dci_interrupt(struct uss820dci_softc *sc) +{ + uint8_t ssr; + uint8_t event; + + USB_BUS_LOCK(&sc->sc_bus); + + ssr = USS820_READ_1(sc, USS820_SSR); + + ssr &= (USS820_SSR_SUSPEND | + USS820_SSR_RESUME | + USS820_SSR_RESET); + + /* acknowledge all interrupts */ + + uss820dci_update_shared_1(sc, USS820_SSR, 0, 0); + + /* check for any bus state change interrupts */ + + if (ssr) { + + event = 0; + + if (ssr & USS820_SSR_RESET) { + sc->sc_flags.status_bus_reset = 1; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + /* disable resume interrupt */ + uss820dci_wait_suspend(sc, 1); + + event = 1; + } + /* + * If "RESUME" and "SUSPEND" is set at the same time + * we interpret that like "RESUME". Resume is set when + * there is at least 3 milliseconds of inactivity on + * the USB BUS. + */ + if (ssr & USS820_SSR_RESUME) { + if (sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 1; + /* disable resume interrupt */ + uss820dci_wait_suspend(sc, 1); + event = 1; + } + } else if (ssr & USS820_SSR_SUSPEND) { + if (!sc->sc_flags.status_suspend) { + sc->sc_flags.status_suspend = 1; + sc->sc_flags.change_suspend = 1; + /* enable resume interrupt */ + uss820dci_wait_suspend(sc, 0); + event = 1; + } + } + if (event) { + + DPRINTF("real bus interrupt 0x%02x\n", ssr); + + /* complete root HUB interrupt endpoint */ + uss820dci_root_intr(sc); + } + } + /* acknowledge all SBI interrupts */ + uss820dci_update_shared_1(sc, USS820_SBI, 0, 0); + + /* acknowledge all SBI1 interrupts */ + uss820dci_update_shared_1(sc, USS820_SBI1, 0, 0); + + /* poll all active transfers */ + uss820dci_interrupt_poll(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +uss820dci_setup_standard_chain_sub(struct uss820_std_temp *temp) +{ + struct uss820dci_td *td; + + /* get current Transfer Descriptor */ + td = temp->td_next; + temp->td = td; + + /* prepare for next TD */ + temp->td_next = td->obj_next; + + /* fill out the Transfer Descriptor */ + td->func = temp->func; + td->pc = temp->pc; + td->offset = temp->offset; + td->remainder = temp->len; + td->error = 0; + td->did_enable = 0; + td->did_stall = temp->did_stall; + td->short_pkt = temp->short_pkt; + td->alt_next = temp->setup_alt_next; +} + +static void +uss820dci_setup_standard_chain(struct usb_xfer *xfer) +{ + struct uss820_std_temp temp; + struct uss820dci_softc *sc; + struct uss820dci_td *td; + uint32_t x; + uint8_t ep_no; + + DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", + xfer->address, UE_GET_ADDR(xfer->endpointno), + xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); + + temp.max_frame_size = xfer->max_frame_size; + + td = xfer->td_start[0]; + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + /* setup temp */ + + temp.pc = NULL; + temp.td = NULL; + temp.td_next = xfer->td_start[0]; + temp.offset = 0; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + temp.did_stall = !xfer->flags_int.control_stall; + + sc = USS820_DCI_BUS2SC(xfer->xroot->bus); + ep_no = (xfer->endpointno & UE_ADDR); + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.func = &uss820dci_setup_rx; + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.short_pkt = temp.len ? 1 : 0; + /* check for last frame */ + if (xfer->nframes == 1) { + /* no STATUS stage yet, SETUP is last */ + if (xfer->flags_int.control_act) + temp.setup_alt_next = 0; + } + + uss820dci_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + if (x != xfer->nframes) { + if (xfer->endpointno & UE_DIR_IN) { + temp.func = &uss820dci_data_tx; + } else { + temp.func = &uss820dci_data_rx; + } + + /* setup "pc" pointer */ + temp.pc = xfer->frbuffers + x; + } + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + + x++; + + if (x == xfer->nframes) { + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_act) { + temp.setup_alt_next = 0; + } + } else { + temp.setup_alt_next = 0; + } + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.short_pkt = 0; + + } else { + + /* regular data transfer */ + + temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1; + } + + uss820dci_setup_standard_chain_sub(&temp); + + if (xfer->flags_int.isochronous_xfr) { + temp.offset += temp.len; + } else { + /* get next Page Cache pointer */ + temp.pc = xfer->frbuffers + x; + } + } + + /* check for control transfer */ + if (xfer->flags_int.control_xfr) { + uint8_t need_sync; + + /* always setup a valid "pc" pointer for status and sync */ + temp.pc = xfer->frbuffers + 0; + temp.len = 0; + temp.short_pkt = 0; + temp.setup_alt_next = 0; + + /* check if we should append a status stage */ + if (!xfer->flags_int.control_act) { + + /* + * Send a DATA1 message and invert the current + * endpoint direction. + */ + if (xfer->endpointno & UE_DIR_IN) { + temp.func = &uss820dci_data_rx; + need_sync = 0; + } else { + temp.func = &uss820dci_data_tx; + need_sync = 1; + } + temp.len = 0; + temp.short_pkt = 0; + + uss820dci_setup_standard_chain_sub(&temp); + if (need_sync) { + /* we need a SYNC point after TX */ + temp.func = &uss820dci_data_tx_sync; + uss820dci_setup_standard_chain_sub(&temp); + } + } + } + /* must have at least one frame! */ + td = temp.td; + xfer->td_transfer_last = td; +} + +static void +uss820dci_timeout(void *arg) +{ + struct usb_xfer *xfer = arg; + + DPRINTF("xfer=%p\n", xfer); + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* transfer is transferred */ + uss820dci_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +uss820dci_intr_set(struct usb_xfer *xfer, uint8_t set) +{ + struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus); + uint8_t ep_no = (xfer->endpointno & UE_ADDR); + uint8_t ep_reg; + uint8_t temp; + + DPRINTFN(15, "endpoint 0x%02x\n", xfer->endpointno); + + if (ep_no > 3) { + ep_reg = USS820_SBIE1; + } else { + ep_reg = USS820_SBIE; + } + + ep_no &= 3; + ep_no = 1 << (2 * ep_no); + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + ep_no <<= 1; /* RX interrupt only */ + } else { + ep_no |= (ep_no << 1); /* RX and TX interrupt */ + } + } else { + if (!(xfer->endpointno & UE_DIR_IN)) { + ep_no <<= 1; + } + } + temp = USS820_READ_1(sc, ep_reg); + if (set) { + temp |= ep_no; + } else { + temp &= ~ep_no; + } + USS820_WRITE_1(sc, ep_reg, temp); +} + +static void +uss820dci_start_standard_chain(struct usb_xfer *xfer) +{ + DPRINTFN(9, "\n"); + + /* poll one time */ + if (uss820dci_xfer_do_fifo(xfer)) { + + /* + * Only enable the endpoint interrupt when we are + * actually waiting for data, hence we are dealing + * with level triggered interrupts ! + */ + uss820dci_intr_set(xfer, 1); + + /* put transfer on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) { + usbd_transfer_timeout_ms(xfer, + &uss820dci_timeout, xfer->timeout); + } + } +} + +static void +uss820dci_root_intr(struct uss820dci_softc *sc) +{ + DPRINTFN(9, "\n"); + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* set port bit */ + sc->sc_hub_idata[0] = 0x02; /* we only have one port */ + + uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, + sizeof(sc->sc_hub_idata)); +} + +static usb_error_t +uss820dci_standard_done_sub(struct usb_xfer *xfer) +{ + struct uss820dci_td *td; + uint32_t len; + uint8_t error; + + DPRINTFN(9, "\n"); + + td = xfer->td_transfer_cache; + + do { + len = td->remainder; + + if (xfer->aframes != xfer->nframes) { + /* + * Verify the length and subtract + * the remainder from "frlengths[]": + */ + if (len > xfer->frlengths[xfer->aframes]) { + td->error = 1; + } else { + xfer->frlengths[xfer->aframes] -= len; + } + } + /* Check for transfer error */ + if (td->error) { + /* the transfer is finished */ + error = 1; + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + td = td->obj_next; + } else { + td = NULL; + } + } else { + /* the transfer is finished */ + td = NULL; + } + error = 0; + break; + } + td = td->obj_next; + + /* this USB frame is complete */ + error = 0; + break; + + } while (0); + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + return (error ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); +} + +static void +uss820dci_standard_done(struct usb_xfer *xfer) +{ + usb_error_t err = 0; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = uss820dci_standard_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = uss820dci_standard_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + err = uss820dci_standard_done_sub(xfer); + } +done: + uss820dci_device_done(xfer, err); +} + +/*------------------------------------------------------------------------* + * uss820dci_device_done + * + * NOTE: this function can be called more than one time on the + * same USB transfer! + *------------------------------------------------------------------------*/ +static void +uss820dci_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); + + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + uss820dci_intr_set(xfer, 0); + } + /* dequeue transfer and start next transfer */ + usbd_transfer_done(xfer, error); +} + +static void +uss820dci_set_stall(struct usb_device *udev, struct usb_xfer *xfer, + struct usb_endpoint *ep, uint8_t *did_stall) +{ + struct uss820dci_softc *sc; + uint8_t ep_no; + uint8_t ep_type; + uint8_t ep_dir; + uint8_t temp; + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + DPRINTFN(5, "endpoint=%p\n", ep); + + if (xfer) { + /* cancel any ongoing transfers */ + uss820dci_device_done(xfer, USB_ERR_STALLED); + } + /* set FORCESTALL */ + sc = USS820_DCI_BUS2SC(udev->bus); + ep_no = (ep->edesc->bEndpointAddress & UE_ADDR); + ep_dir = (ep->edesc->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)); + ep_type = (ep->edesc->bmAttributes & UE_XFERTYPE); + + if (ep_type == UE_CONTROL) { + /* should not happen */ + return; + } + USS820_WRITE_1(sc, USS820_EPINDEX, ep_no); + + if (ep_dir == UE_DIR_IN) { + temp = USS820_EPCON_TXSTL; + } else { + temp = USS820_EPCON_RXSTL; + } + uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF, temp); +} + +static void +uss820dci_clear_stall_sub(struct uss820dci_softc *sc, + uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir) +{ + uint8_t temp; + + if (ep_type == UE_CONTROL) { + /* clearing stall is not needed */ + return; + } + /* select endpoint index */ + USS820_WRITE_1(sc, USS820_EPINDEX, ep_no); + + /* clear stall and disable I/O transfers */ + if (ep_dir == UE_DIR_IN) { + temp = 0xFF ^ (USS820_EPCON_TXOE | + USS820_EPCON_TXSTL); + } else { + temp = 0xFF ^ (USS820_EPCON_RXIE | + USS820_EPCON_RXSTL); + } + uss820dci_update_shared_1(sc, USS820_EPCON, temp, 0); + + if (ep_dir == UE_DIR_IN) { + /* reset data toggle */ + USS820_WRITE_1(sc, USS820_TXSTAT, + USS820_TXSTAT_TXSOVW); + + /* reset FIFO */ + temp = USS820_READ_1(sc, USS820_TXCON); + temp |= USS820_TXCON_TXCLR; + USS820_WRITE_1(sc, USS820_TXCON, temp); + temp &= ~USS820_TXCON_TXCLR; + USS820_WRITE_1(sc, USS820_TXCON, temp); + } else { + + /* reset data toggle */ + uss820dci_update_shared_1(sc, USS820_RXSTAT, + 0, USS820_RXSTAT_RXSOVW); + + /* reset FIFO */ + temp = USS820_READ_1(sc, USS820_RXCON); + temp |= USS820_RXCON_RXCLR; + temp &= ~USS820_RXCON_RXFFRC; + USS820_WRITE_1(sc, USS820_RXCON, temp); + temp &= ~USS820_RXCON_RXCLR; + USS820_WRITE_1(sc, USS820_RXCON, temp); + } +} + +static void +uss820dci_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) +{ + struct uss820dci_softc *sc; + struct usb_endpoint_descriptor *ed; + + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + DPRINTFN(5, "endpoint=%p\n", ep); + + /* check mode */ + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + /* get softc */ + sc = USS820_DCI_BUS2SC(udev->bus); + + /* get endpoint descriptor */ + ed = ep->edesc; + + /* reset endpoint */ + uss820dci_clear_stall_sub(sc, + (ed->bEndpointAddress & UE_ADDR), + (ed->bmAttributes & UE_XFERTYPE), + (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT))); +} + +usb_error_t +uss820dci_init(struct uss820dci_softc *sc) +{ + const struct usb_hw_ep_profile *pf; + uint8_t n; + uint8_t temp; + + DPRINTF("start\n"); + + /* set up the bus structure */ + sc->sc_bus.usbrev = USB_REV_1_1; + sc->sc_bus.methods = &uss820dci_bus_methods; + + USB_BUS_LOCK(&sc->sc_bus); + + /* we always have VBUS */ + sc->sc_flags.status_vbus = 1; + + /* reset the chip */ + USS820_WRITE_1(sc, USS820_SCR, USS820_SCR_SRESET); + DELAY(100); + USS820_WRITE_1(sc, USS820_SCR, 0); + + /* wait for reset to complete */ + for (n = 0;; n++) { + + temp = USS820_READ_1(sc, USS820_MCSR); + + if (temp & USS820_MCSR_INIT) { + break; + } + if (n == 100) { + USB_BUS_UNLOCK(&sc->sc_bus); + return (USB_ERR_INVAL); + } + /* wait a little for things to stabilise */ + DELAY(100); + } + + /* do a pulldown */ + uss820dci_pull_down(sc); + + /* wait 10ms for pulldown to stabilise */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100); + + /* check hardware revision */ + temp = USS820_READ_1(sc, USS820_REV); + + if (temp < 0x13) { + USB_BUS_UNLOCK(&sc->sc_bus); + return (USB_ERR_INVAL); + } + /* enable interrupts */ + USS820_WRITE_1(sc, USS820_SCR, + USS820_SCR_T_IRQ | + USS820_SCR_IE_RESET | + /* USS820_SCR_RWUPE | */ + USS820_SCR_IE_SUSP | + USS820_SCR_IRQPOL); + + /* enable interrupts */ + USS820_WRITE_1(sc, USS820_SCRATCH, + USS820_SCRATCH_IE_RESUME); + + /* enable features */ + USS820_WRITE_1(sc, USS820_MCSR, + USS820_MCSR_BDFEAT | + USS820_MCSR_FEAT); + + sc->sc_flags.mcsr_feat = 1; + + /* disable interrupts */ + USS820_WRITE_1(sc, USS820_SBIE, 0); + + /* disable interrupts */ + USS820_WRITE_1(sc, USS820_SBIE1, 0); + + /* disable all endpoints */ + for (n = 0; n != USS820_EP_MAX; n++) { + + /* select endpoint */ + USS820_WRITE_1(sc, USS820_EPINDEX, n); + + /* disable endpoint */ + uss820dci_update_shared_1(sc, USS820_EPCON, 0, 0); + } + + /* + * Initialise default values for some registers that cannot be + * changed during operation! + */ + for (n = 0; n != USS820_EP_MAX; n++) { + + uss820dci_get_hw_ep_profile(NULL, &pf, n); + + /* the maximum frame sizes should be the same */ + if (pf->max_in_frame_size != pf->max_out_frame_size) { + DPRINTF("Max frame size mismatch %u != %u\n", + pf->max_in_frame_size, pf->max_out_frame_size); + } + if (pf->support_isochronous) { + if (pf->max_in_frame_size <= 64) { + temp = (USS820_TXCON_FFSZ_16_64 | + USS820_TXCON_TXISO | + USS820_TXCON_ATM); + } else if (pf->max_in_frame_size <= 256) { + temp = (USS820_TXCON_FFSZ_64_256 | + USS820_TXCON_TXISO | + USS820_TXCON_ATM); + } else if (pf->max_in_frame_size <= 512) { + temp = (USS820_TXCON_FFSZ_8_512 | + USS820_TXCON_TXISO | + USS820_TXCON_ATM); + } else { /* 1024 bytes */ + temp = (USS820_TXCON_FFSZ_32_1024 | + USS820_TXCON_TXISO | + USS820_TXCON_ATM); + } + } else { + if ((pf->max_in_frame_size <= 8) && + (sc->sc_flags.mcsr_feat)) { + temp = (USS820_TXCON_FFSZ_8_512 | + USS820_TXCON_ATM); + } else if (pf->max_in_frame_size <= 16) { + temp = (USS820_TXCON_FFSZ_16_64 | + USS820_TXCON_ATM); + } else if ((pf->max_in_frame_size <= 32) && + (sc->sc_flags.mcsr_feat)) { + temp = (USS820_TXCON_FFSZ_32_1024 | + USS820_TXCON_ATM); + } else { /* 64 bytes */ + temp = (USS820_TXCON_FFSZ_64_256 | + USS820_TXCON_ATM); + } + } + + /* need to configure the chip early */ + + USS820_WRITE_1(sc, USS820_EPINDEX, n); + USS820_WRITE_1(sc, USS820_TXCON, temp); + USS820_WRITE_1(sc, USS820_RXCON, temp); + + if (pf->support_control) { + temp = USS820_EPCON_CTLEP | + USS820_EPCON_RXSPM | + USS820_EPCON_RXIE | + USS820_EPCON_RXEPEN | + USS820_EPCON_TXOE | + USS820_EPCON_TXEPEN; + } else { + temp = USS820_EPCON_RXEPEN | USS820_EPCON_TXEPEN; + } + + uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF, temp); + } + + USB_BUS_UNLOCK(&sc->sc_bus); + + /* catch any lost interrupts */ + + uss820dci_do_poll(&sc->sc_bus); + + return (0); /* success */ +} + +void +uss820dci_uninit(struct uss820dci_softc *sc) +{ + uint8_t temp; + + USB_BUS_LOCK(&sc->sc_bus); + + /* disable all interrupts */ + temp = USS820_READ_1(sc, USS820_SCR); + temp &= ~USS820_SCR_T_IRQ; + USS820_WRITE_1(sc, USS820_SCR, temp); + + sc->sc_flags.port_powered = 0; + sc->sc_flags.status_vbus = 0; + sc->sc_flags.status_bus_reset = 0; + sc->sc_flags.status_suspend = 0; + sc->sc_flags.change_suspend = 0; + sc->sc_flags.change_connect = 1; + + uss820dci_pull_down(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +uss820dci_suspend(struct uss820dci_softc *sc) +{ + /* TODO */ +} + +static void +uss820dci_resume(struct uss820dci_softc *sc) +{ + /* TODO */ +} + +static void +uss820dci_do_poll(struct usb_bus *bus) +{ + struct uss820dci_softc *sc = USS820_DCI_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + uss820dci_interrupt_poll(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/*------------------------------------------------------------------------* + * at91dci bulk support + *------------------------------------------------------------------------*/ +static void +uss820dci_device_bulk_open(struct usb_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_bulk_close(struct usb_xfer *xfer) +{ + uss820dci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +uss820dci_device_bulk_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_bulk_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + uss820dci_setup_standard_chain(xfer); + uss820dci_start_standard_chain(xfer); +} + +struct usb_pipe_methods uss820dci_device_bulk_methods = +{ + .open = uss820dci_device_bulk_open, + .close = uss820dci_device_bulk_close, + .enter = uss820dci_device_bulk_enter, + .start = uss820dci_device_bulk_start, +}; + +/*------------------------------------------------------------------------* + * at91dci control support + *------------------------------------------------------------------------*/ +static void +uss820dci_device_ctrl_open(struct usb_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_ctrl_close(struct usb_xfer *xfer) +{ + uss820dci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +uss820dci_device_ctrl_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_ctrl_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + uss820dci_setup_standard_chain(xfer); + uss820dci_start_standard_chain(xfer); +} + +struct usb_pipe_methods uss820dci_device_ctrl_methods = +{ + .open = uss820dci_device_ctrl_open, + .close = uss820dci_device_ctrl_close, + .enter = uss820dci_device_ctrl_enter, + .start = uss820dci_device_ctrl_start, +}; + +/*------------------------------------------------------------------------* + * at91dci interrupt support + *------------------------------------------------------------------------*/ +static void +uss820dci_device_intr_open(struct usb_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_intr_close(struct usb_xfer *xfer) +{ + uss820dci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +uss820dci_device_intr_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_intr_start(struct usb_xfer *xfer) +{ + /* setup TDs */ + uss820dci_setup_standard_chain(xfer); + uss820dci_start_standard_chain(xfer); +} + +struct usb_pipe_methods uss820dci_device_intr_methods = +{ + .open = uss820dci_device_intr_open, + .close = uss820dci_device_intr_close, + .enter = uss820dci_device_intr_enter, + .start = uss820dci_device_intr_start, +}; + +/*------------------------------------------------------------------------* + * at91dci full speed isochronous support + *------------------------------------------------------------------------*/ +static void +uss820dci_device_isoc_fs_open(struct usb_xfer *xfer) +{ + return; +} + +static void +uss820dci_device_isoc_fs_close(struct usb_xfer *xfer) +{ + uss820dci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +uss820dci_device_isoc_fs_enter(struct usb_xfer *xfer) +{ + struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus); + uint32_t temp; + uint32_t nframes; + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes); + + /* get the current frame index - we don't need the high bits */ + + nframes = USS820_READ_1(sc, USS820_SOFL); + + /* + * check if the frame index is within the window where the + * frames will be inserted + */ + temp = (nframes - xfer->endpoint->isoc_next) & USS820_SOFL_MASK; + + if ((xfer->endpoint->is_synced == 0) || + (temp < xfer->nframes)) { + /* + * If there is data underflow or the pipe queue is + * empty we schedule the transfer a few frames ahead + * of the current frame position. Else two isochronous + * transfers might overlap. + */ + xfer->endpoint->isoc_next = (nframes + 3) & USS820_SOFL_MASK; + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + temp = (xfer->endpoint->isoc_next - nframes) & USS820_SOFL_MASK; + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb_isoc_time_expand(&sc->sc_bus, nframes) + temp + + xfer->nframes; + + /* compute frame number for next insertion */ + xfer->endpoint->isoc_next += xfer->nframes; + + /* setup TDs */ + uss820dci_setup_standard_chain(xfer); +} + +static void +uss820dci_device_isoc_fs_start(struct usb_xfer *xfer) +{ + /* start TD chain */ + uss820dci_start_standard_chain(xfer); +} + +struct usb_pipe_methods uss820dci_device_isoc_fs_methods = +{ + .open = uss820dci_device_isoc_fs_open, + .close = uss820dci_device_isoc_fs_close, + .enter = uss820dci_device_isoc_fs_enter, + .start = uss820dci_device_isoc_fs_start, +}; + +/*------------------------------------------------------------------------* + * at91dci root control support + *------------------------------------------------------------------------* + * Simulate a hardware HUB by handling all the necessary requests. + *------------------------------------------------------------------------*/ + +static const struct usb_device_descriptor uss820dci_devd = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = UDESC_DEVICE, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_FSHUB, + .bMaxPacketSize = 64, + .bcdDevice = {0x00, 0x01}, + .iManufacturer = 1, + .iProduct = 2, + .bNumConfigurations = 1, +}; + +static const struct usb_device_qualifier uss820dci_odevd = { + .bLength = sizeof(struct usb_device_qualifier), + .bDescriptorType = UDESC_DEVICE_QUALIFIER, + .bcdUSB = {0x00, 0x02}, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_FSHUB, + .bMaxPacketSize0 = 0, + .bNumConfigurations = 0, +}; + +static const struct uss820dci_config_desc uss820dci_confd = { + .confd = { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(uss820dci_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0, + }, + .ifcd = { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = 0, + }, + + .endpd = { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = (UE_DIR_IN | USS820_DCI_INTR_ENDPT), + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, + .bInterval = 255, + }, +}; + +static const struct usb_hub_descriptor_min uss820dci_hubd = { + .bDescLength = sizeof(uss820dci_hubd), + .bDescriptorType = UDESC_HUB, + .bNbrPorts = 1, + .wHubCharacteristics[0] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) & 0xFF, + .wHubCharacteristics[1] = + (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL) >> 8, + .bPwrOn2PwrGood = 50, + .bHubContrCurrent = 0, + .DeviceRemovable = {0}, /* port is removable */ +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_VENDOR \ + 'A', 0, 'G', 0, 'E', 0, 'R', 0, 'E', 0 + +#define STRING_PRODUCT \ + 'D', 0, 'C', 0, 'I', 0, ' ', 0, 'R', 0, \ + 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, \ + 'U', 0, 'B', 0, + +USB_MAKE_STRING_DESC(STRING_LANG, uss820dci_langtab); +USB_MAKE_STRING_DESC(STRING_VENDOR, uss820dci_vendor); +USB_MAKE_STRING_DESC(STRING_PRODUCT, uss820dci_product); + +static usb_error_t +uss820dci_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + struct uss820dci_softc *sc = USS820_DCI_BUS2SC(udev->bus); + const void *ptr; + uint16_t len; + uint16_t value; + uint16_t index; + usb_error_t err; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* buffer reset */ + ptr = (const void *)&sc->sc_hub_temp; + len = 0; + err = 0; + + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + /* demultiplex the control request */ + + switch (req->bmRequestType) { + case UT_READ_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (req->bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_DESCRIPTOR: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (UGETW(req->wValue)) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SYNCH_FRAME: + goto tr_valid; /* nop */ + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (req->bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_INTERFACE: + switch (req->bRequest) { + case UR_SET_INTERFACE: + goto tr_handle_set_interface; + case UR_CLEAR_FEATURE: + goto tr_valid; /* nop */ + case UR_SET_FEATURE: + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (req->bRequest) { + case UR_GET_INTERFACE: + goto tr_handle_get_interface; + case UR_GET_STATUS: + goto tr_handle_get_iface_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_INTERFACE: + case UT_WRITE_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_READ_CLASS_INTERFACE: + case UT_READ_VENDOR_INTERFACE: + /* XXX forward */ + break; + + case UT_WRITE_CLASS_DEVICE: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_valid; + case UR_SET_DESCRIPTOR: + case UR_SET_FEATURE: + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_CLASS_OTHER: + switch (req->bRequest) { + case UR_CLEAR_FEATURE: + goto tr_handle_clear_port_feature; + case UR_SET_FEATURE: + goto tr_handle_set_port_feature; + case UR_CLEAR_TT_BUFFER: + case UR_RESET_TT: + case UR_STOP_TT: + goto tr_valid; + + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_OTHER: + switch (req->bRequest) { + case UR_GET_TT_STATE: + goto tr_handle_get_tt_state; + case UR_GET_STATUS: + goto tr_handle_get_port_status; + default: + goto tr_stalled; + } + break; + + case UT_READ_CLASS_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_class_descriptor; + case UR_GET_STATUS: + goto tr_handle_get_class_status; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + goto tr_valid; + +tr_handle_get_descriptor: + switch (value >> 8) { + case UDESC_DEVICE: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(uss820dci_devd); + ptr = (const void *)&uss820dci_devd; + goto tr_valid; + case UDESC_CONFIG: + if (value & 0xff) { + goto tr_stalled; + } + len = sizeof(uss820dci_confd); + ptr = (const void *)&uss820dci_confd; + goto tr_valid; + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + len = sizeof(uss820dci_langtab); + ptr = (const void *)&uss820dci_langtab; + goto tr_valid; + + case 1: /* Vendor */ + len = sizeof(uss820dci_vendor); + ptr = (const void *)&uss820dci_vendor; + goto tr_valid; + + case 2: /* Product */ + len = sizeof(uss820dci_product); + ptr = (const void *)&uss820dci_product; + goto tr_valid; + default: + break; + } + break; + default: + goto tr_stalled; + } + goto tr_stalled; + +tr_handle_get_config: + len = 1; + sc->sc_hub_temp.wValue[0] = sc->sc_conf; + goto tr_valid; + +tr_handle_get_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED); + goto tr_valid; + +tr_handle_set_address: + if (value & 0xFF00) { + goto tr_stalled; + } + sc->sc_rt_addr = value; + goto tr_valid; + +tr_handle_set_config: + if (value >= 2) { + goto tr_stalled; + } + sc->sc_conf = value; + goto tr_valid; + +tr_handle_get_interface: + len = 1; + sc->sc_hub_temp.wValue[0] = 0; + goto tr_valid; + +tr_handle_get_tt_state: +tr_handle_get_class_status: +tr_handle_get_iface_status: +tr_handle_get_ep_status: + len = 2; + USETW(sc->sc_hub_temp.wValue, 0); + goto tr_valid; + +tr_handle_set_halt: +tr_handle_set_interface: +tr_handle_set_wakeup: +tr_handle_clear_wakeup: +tr_handle_clear_halt: + goto tr_valid; + +tr_handle_clear_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index); + + switch (value) { + case UHF_PORT_SUSPEND: + uss820dci_wakeup_peer(sc); + break; + + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 0; + break; + + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 0; + uss820dci_pull_down(sc); + break; + case UHF_C_PORT_CONNECTION: + sc->sc_flags.change_connect = 0; + break; + case UHF_C_PORT_SUSPEND: + sc->sc_flags.change_suspend = 0; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_set_port_feature: + if (index != 1) { + goto tr_stalled; + } + DPRINTFN(9, "UR_SET_PORT_FEATURE\n"); + + switch (value) { + case UHF_PORT_ENABLE: + sc->sc_flags.port_enabled = 1; + break; + case UHF_PORT_SUSPEND: + case UHF_PORT_RESET: + case UHF_PORT_TEST: + case UHF_PORT_INDICATOR: + /* nops */ + break; + case UHF_PORT_POWER: + sc->sc_flags.port_powered = 1; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + goto tr_valid; + +tr_handle_get_port_status: + + DPRINTFN(9, "UR_GET_PORT_STATUS\n"); + + if (index != 1) { + goto tr_stalled; + } + if (sc->sc_flags.status_vbus) { + uss820dci_pull_up(sc); + } else { + uss820dci_pull_down(sc); + } + + /* Select FULL-speed and Device Side Mode */ + + value = UPS_PORT_MODE_DEVICE; + + if (sc->sc_flags.port_powered) { + value |= UPS_PORT_POWER; + } + if (sc->sc_flags.port_enabled) { + value |= UPS_PORT_ENABLED; + } + if (sc->sc_flags.status_vbus && + sc->sc_flags.status_bus_reset) { + value |= UPS_CURRENT_CONNECT_STATUS; + } + if (sc->sc_flags.status_suspend) { + value |= UPS_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortStatus, value); + + value = 0; + + if (sc->sc_flags.change_connect) { + value |= UPS_C_CONNECT_STATUS; + } + if (sc->sc_flags.change_suspend) { + value |= UPS_C_SUSPEND; + } + USETW(sc->sc_hub_temp.ps.wPortChange, value); + len = sizeof(sc->sc_hub_temp.ps); + goto tr_valid; + +tr_handle_get_class_descriptor: + if (value & 0xFF) { + goto tr_stalled; + } + ptr = (const void *)&uss820dci_hubd; + len = sizeof(uss820dci_hubd); + goto tr_valid; + +tr_stalled: + err = USB_ERR_STALLED; +tr_valid: +done: + *plength = len; + *pptr = ptr; + return (err); +} + +static void +uss820dci_xfer_setup(struct usb_setup_params *parm) +{ + const struct usb_hw_ep_profile *pf; + struct uss820dci_softc *sc; + struct usb_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t n; + uint8_t ep_no; + + sc = USS820_DCI_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + /* + * NOTE: This driver does not use any of the parameters that + * are computed from the following values. Just set some + * reasonable dummies: + */ + parm->hc_max_packet_size = 0x500; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x500; + + usbd_transfer_setup_sub(parm); + + /* + * compute maximum number of TDs + */ + if (parm->methods == &uss820dci_device_ctrl_methods) { + + ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ; + + } else if (parm->methods == &uss820dci_device_bulk_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else if (parm->methods == &uss820dci_device_intr_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else if (parm->methods == &uss820dci_device_isoc_fs_methods) { + + ntd = xfer->nframes + 1 /* SYNC */ ; + + } else { + + ntd = 0; + } + + /* + * check if "usbd_transfer_setup_sub" set an error + */ + if (parm->err) { + return; + } + /* + * allocate transfer descriptors + */ + last_obj = NULL; + + /* + * get profile stuff + */ + if (ntd) { + + ep_no = xfer->endpointno & UE_ADDR; + uss820dci_get_hw_ep_profile(parm->udev, &pf, ep_no); + + if (pf == NULL) { + /* should not happen */ + parm->err = USB_ERR_INVAL; + return; + } + } else { + ep_no = 0; + pf = NULL; + } + + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + for (n = 0; n != ntd; n++) { + + struct uss820dci_td *td; + + if (parm->buf) { + + td = USB_ADD_BYTES(parm->buf, parm->size[0]); + + /* init TD */ + td->io_tag = sc->sc_io_tag; + td->io_hdl = sc->sc_io_hdl; + td->max_packet_size = xfer->max_packet_size; + td->ep_index = ep_no; + if (pf->support_multi_buffer && + (parm->methods != &uss820dci_device_ctrl_methods)) { + td->support_multi_buffer = 1; + } + td->obj_next = last_obj; + + last_obj = td; + } + parm->size[0] += sizeof(*td); + } + + xfer->td_start[0] = last_obj; +} + +static void +uss820dci_xfer_unsetup(struct usb_xfer *xfer) +{ + return; +} + +static void +uss820dci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *ep) +{ + struct uss820dci_softc *sc = USS820_DCI_BUS2SC(udev->bus); + + DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", + ep, udev->address, + edesc->bEndpointAddress, udev->flags.usb_mode, + sc->sc_rt_addr); + + if (udev->device_index != sc->sc_rt_addr) { + + if (udev->flags.usb_mode != USB_MODE_DEVICE) { + /* not supported */ + return; + } + if (udev->speed != USB_SPEED_FULL) { + /* not supported */ + return; + } + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + ep->methods = &uss820dci_device_ctrl_methods; + break; + case UE_INTERRUPT: + ep->methods = &uss820dci_device_intr_methods; + break; + case UE_ISOCHRONOUS: + ep->methods = &uss820dci_device_isoc_fs_methods; + break; + case UE_BULK: + ep->methods = &uss820dci_device_bulk_methods; + break; + default: + /* do nothing */ + break; + } + } +} + +static void +uss820dci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) +{ + struct uss820dci_softc *sc = USS820_DCI_BUS2SC(bus); + + switch (state) { + case USB_HW_POWER_SUSPEND: + uss820dci_suspend(sc); + break; + case USB_HW_POWER_SHUTDOWN: + uss820dci_uninit(sc); + break; + case USB_HW_POWER_RESUME: + uss820dci_resume(sc); + break; + default: + break; + } +} + +struct usb_bus_methods uss820dci_bus_methods = +{ + .endpoint_init = &uss820dci_ep_init, + .xfer_setup = &uss820dci_xfer_setup, + .xfer_unsetup = &uss820dci_xfer_unsetup, + .get_hw_ep_profile = &uss820dci_get_hw_ep_profile, + .set_stall = &uss820dci_set_stall, + .clear_stall = &uss820dci_clear_stall, + .roothub_exec = &uss820dci_roothub_exec, + .xfer_poll = &uss820dci_do_poll, + .set_hw_power_sleep = uss820dci_set_hw_power_sleep, +}; diff --git a/sys/bus/u4b/controller/uss820dci.h b/sys/bus/u4b/controller/uss820dci.h new file mode 100644 index 0000000000..8a27f15144 --- /dev/null +++ b/sys/bus/u4b/controller/uss820dci.h @@ -0,0 +1,354 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2007 Hans Petter Selasky + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USS820_DCI_H_ +#define _USS820_DCI_H_ + +#define USS820_MAX_DEVICES (USB_MIN_DEVICES + 1) + +#define USS820_EP_MAX 8 /* maximum number of endpoints */ + +#define USS820_TXDAT 0x00 /* Transmit FIFO data */ + +#define USS820_TXCNTL 0x01 /* Transmit FIFO byte count low */ +#define USS820_TXCNTL_MASK 0xFF + +#define USS820_TXCNTH 0x02 /* Transmit FIFO byte count high */ +#define USS820_TXCNTH_MASK 0x03 +#define USS820_TXCNTH_UNUSED 0xFC + +#define USS820_TXCON 0x03 /* USB transmit FIFO control */ +#define USS820_TXCON_REVRP 0x01 +#define USS820_TXCON_ADVRM 0x02 +#define USS820_TXCON_ATM 0x04 /* Automatic Transmit Management */ +#define USS820_TXCON_TXISO 0x08 /* Transmit Isochronous Data */ +#define USS820_TXCON_UNUSED 0x10 +#define USS820_TXCON_FFSZ_16_64 0x00 +#define USS820_TXCON_FFSZ_64_256 0x20 +#define USS820_TXCON_FFSZ_8_512 0x40 +#define USS820_TXCON_FFSZ_32_1024 0x60 +#define USS820_TXCON_FFSZ_MASK 0x60 +#define USS820_TXCON_TXCLR 0x80 /* Transmit FIFO clear */ + +#define USS820_TXFLG 0x04 /* Transmit FIFO flag (Read Only) */ +#define USS820_TXFLG_TXOVF 0x01 /* TX overrun */ +#define USS820_TXFLG_TXURF 0x02 /* TX underrun */ +#define USS820_TXFLG_TXFULL 0x04 /* TX full */ +#define USS820_TXFLG_TXEMP 0x08 /* TX empty */ +#define USS820_TXFLG_UNUSED 0x30 +#define USS820_TXFLG_TXFIF0 0x40 +#define USS820_TXFLG_TXFIF1 0x80 + +#define USS820_RXDAT 0x05 /* Receive FIFO data */ + +#define USS820_RXCNTL 0x06 /* Receive FIFO byte count low */ +#define USS820_RXCNTL_MASK 0xFF + +#define USS820_RXCNTH 0x07 /* Receive FIFO byte count high */ +#define USS820_RXCNTH_MASK 0x03 +#define USS820_RXCNTH_UNUSED 0xFC + +#define USS820_RXCON 0x08 /* Receive FIFO control */ +#define USS820_RXCON_REVWP 0x01 +#define USS820_RXCON_ADVWM 0x02 +#define USS820_RXCON_ARM 0x04 /* Auto Receive Management */ +#define USS820_RXCON_RXISO 0x08 /* Receive Isochronous Data */ +#define USS820_RXCON_RXFFRC 0x10 /* FIFO Read Complete */ +#define USS820_RXCON_FFSZ_16_64 0x00 +#define USS820_RXCON_FFSZ_64_256 0x20 +#define USS820_RXCON_FFSZ_8_512 0x40 +#define USS820_RXCON_FFSZ_32_1024 0x60 +#define USS820_RXCON_RXCLR 0x80 /* Receive FIFO clear */ + +#define USS820_RXFLG 0x09 /* Receive FIFO flag (Read Only) */ +#define USS820_RXFLG_RXOVF 0x01 /* RX overflow */ +#define USS820_RXFLG_RXURF 0x02 /* RX underflow */ +#define USS820_RXFLG_RXFULL 0x04 /* RX full */ +#define USS820_RXFLG_RXEMP 0x08 /* RX empty */ +#define USS820_RXFLG_RXFLUSH 0x10 /* RX flush */ +#define USS820_RXFLG_UNUSED 0x20 +#define USS820_RXFLG_RXFIF0 0x40 +#define USS820_RXFLG_RXFIF1 0x80 + +#define USS820_EPINDEX 0x0a /* Endpoint index selection */ +#define USS820_EPINDEX_MASK 0x07 +#define USS820_EPINDEX_UNUSED 0xF8 + +#define USS820_EPCON 0x0b /* Endpoint control */ +#define USS820_EPCON_TXEPEN 0x01 /* Transmit Endpoint Enable */ +#define USS820_EPCON_TXOE 0x02 /* Transmit Output Enable */ +#define USS820_EPCON_RXEPEN 0x04 /* Receive Endpoint Enable */ +#define USS820_EPCON_RXIE 0x08 /* Receive Input Enable */ +#define USS820_EPCON_RXSPM 0x10 /* Receive Single-Packet Mode */ +#define USS820_EPCON_CTLEP 0x20 /* Control Endpoint */ +#define USS820_EPCON_TXSTL 0x40 /* Stall Transmit Endpoint */ +#define USS820_EPCON_RXSTL 0x80 /* Stall Receive Endpoint */ + +#define USS820_TXSTAT 0x0c /* Transmit status */ +#define USS820_TXSTAT_TXACK 0x01 /* Transmit Acknowledge */ +#define USS820_TXSTAT_TXERR 0x02 /* Transmit Error */ +#define USS820_TXSTAT_TXVOID 0x04 /* Transmit Void */ +#define USS820_TXSTAT_TXSOVW 0x08 /* Transmit Data Sequence Overwrite + * Bit */ +#define USS820_TXSTAT_TXFLUSH 0x10 /* Transmit FIFO Packet Flushed */ +#define USS820_TXSTAT_TXNAKE 0x20 /* Transmit NAK Mode Enable */ +#define USS820_TXSTAT_TXDSAM 0x40 /* Transmit Data-Set-Available Mode */ +#define USS820_TXSTAT_TXSEQ 0x80 /* Transmitter Current Sequence Bit */ + +#define USS820_RXSTAT 0x0d /* Receive status */ +#define USS820_RXSTAT_RXACK 0x01 /* Receive Acknowledge */ +#define USS820_RXSTAT_RXERR 0x02 /* Receive Error */ +#define USS820_RXSTAT_RXVOID 0x04 /* Receive Void */ +#define USS820_RXSTAT_RXSOVW 0x08 /* Receive Data Sequence Overwrite Bit */ +#define USS820_RXSTAT_EDOVW 0x10 /* End Overwrite Flag */ +#define USS820_RXSTAT_STOVW 0x20 /* Start Overwrite Flag */ +#define USS820_RXSTAT_RXSETUP 0x40 /* Received SETUP token */ +#define USS820_RXSTAT_RXSEQ 0x80 /* Receiver Endpoint Sequence Bit */ + +#define USS820_SOFL 0x0e /* Start Of Frame counter low */ +#define USS820_SOFL_MASK 0xFF + +#define USS820_SOFH 0x0f /* Start Of Frame counter high */ +#define USS820_SOFH_MASK 0x07 +#define USS820_SOFH_SOFDIS 0x08 /* SOF Pin Output Disable */ +#define USS820_SOFH_FTLOCK 0x10 /* Frame Timer Lock */ +#define USS820_SOFH_SOFIE 0x20 /* SOF Interrupt Enable */ +#define USS820_SOFH_ASOF 0x40 /* Any Start of Frame */ +#define USS820_SOFH_SOFACK 0x80 /* SOF Token Received Without Error */ + +#define USS820_FADDR 0x10 /* Function Address */ +#define USS820_FADDR_MASK 0x7F +#define USS820_FADDR_UNUSED 0x80 + +#define USS820_SCR 0x11 /* System Control */ +#define USS820_SCR_UNUSED 0x01 +#define USS820_SCR_T_IRQ 0x02 /* Global Interrupt Enable */ +#define USS820_SCR_IRQLVL 0x04 /* Interrupt Mode */ +#define USS820_SCR_SRESET 0x08 /* Software reset */ +#define USS820_SCR_IE_RESET 0x10 /* Enable Reset Interrupt */ +#define USS820_SCR_IE_SUSP 0x20 /* Enable Suspend Interrupt */ +#define USS820_SCR_RWUPE 0x40 /* Enable Remote Wake-Up Feature */ +#define USS820_SCR_IRQPOL 0x80 /* IRQ polarity */ + +#define USS820_SSR 0x12 /* System Status */ +#define USS820_SSR_RESET 0x01 /* Reset Condition Detected on USB + * cable */ +#define USS820_SSR_SUSPEND 0x02 /* Suspend Detected */ +#define USS820_SSR_RESUME 0x04 /* Resume Detected */ +#define USS820_SSR_SUSPDIS 0x08 /* Suspend Disable */ +#define USS820_SSR_SUSPPO 0x10 /* Suspend Power Off */ +#define USS820_SSR_UNUSED 0xE0 + +#define USS820_UNK0 0x13 /* Unknown */ +#define USS820_UNK0_UNUSED 0xFF + +#define USS820_SBI 0x14 /* Serial bus interrupt low */ +#define USS820_SBI_FTXD0 0x01 /* Function Transmit Done, EP 0 */ +#define USS820_SBI_FRXD0 0x02 /* Function Receive Done, EP 0 */ +#define USS820_SBI_FTXD1 0x04 +#define USS820_SBI_FRXD1 0x08 +#define USS820_SBI_FTXD2 0x10 +#define USS820_SBI_FRXD2 0x20 +#define USS820_SBI_FTXD3 0x40 +#define USS820_SBI_FRXD3 0x80 + +#define USS820_SBI1 0x15 /* Serial bus interrupt high */ +#define USS820_SBI1_FTXD4 0x01 +#define USS820_SBI1_FRXD4 0x02 +#define USS820_SBI1_FTXD5 0x04 +#define USS820_SBI1_FRXD5 0x08 +#define USS820_SBI1_FTXD6 0x10 +#define USS820_SBI1_FRXD6 0x20 +#define USS820_SBI1_FTXD7 0x40 +#define USS820_SBI1_FRXD7 0x80 + +#define USS820_SBIE 0x16 /* Serial bus interrupt enable low */ +#define USS820_SBIE_FTXIE0 0x01 +#define USS820_SBIE_FRXIE0 0x02 +#define USS820_SBIE_FTXIE1 0x04 +#define USS820_SBIE_FRXIE1 0x08 +#define USS820_SBIE_FTXIE2 0x10 +#define USS820_SBIE_FRXIE2 0x20 +#define USS820_SBIE_FTXIE3 0x40 +#define USS820_SBIE_FRXIE3 0x80 + +#define USS820_SBIE1 0x17 /* Serial bus interrupt enable high */ +#define USS820_SBIE1_FTXIE4 0x01 +#define USS820_SBIE1_FRXIE4 0x02 +#define USS820_SBIE1_FTXIE5 0x04 +#define USS820_SBIE1_FRXIE5 0x08 +#define USS820_SBIE1_FTXIE6 0x10 +#define USS820_SBIE1_FRXIE6 0x20 +#define USS820_SBIE1_FTXIE7 0x40 +#define USS820_SBIE1_FRXIE7 0x80 + +#define USS820_REV 0x18 /* Hardware revision */ +#define USS820_REV_MIN 0x0F +#define USS820_REV_MAJ 0xF0 + +#define USS820_LOCK 0x19 /* Suspend power-off locking */ +#define USS820_LOCK_UNLOCKED 0x01 +#define USS820_LOCK_UNUSED 0xFE + +#define USS820_PEND 0x1a /* Pend hardware status update */ +#define USS820_PEND_PEND 0x01 +#define USS820_PEND_UNUSED 0xFE + +#define USS820_SCRATCH 0x1b /* Scratch firmware information */ +#define USS820_SCRATCH_MASK 0x7F +#define USS820_SCRATCH_IE_RESUME 0x80 /* Enable Resume Interrupt */ + +#define USS820_MCSR 0x1c /* Miscellaneous control and status */ +#define USS820_MCSR_DPEN 0x01 /* DPLS Pull-Up Enable */ +#define USS820_MCSR_SUSPLOE 0x02 /* Suspend Lock Out Enable */ +#define USS820_MCSR_BDFEAT 0x04 /* Board Feature Enable */ +#define USS820_MCSR_FEAT 0x08 /* Feature Enable */ +#define USS820_MCSR_PKGID 0x10 /* Package Identification */ +#define USS820_MCSR_SUSPS 0x20 /* Suspend Status */ +#define USS820_MCSR_INIT 0x40 /* Device Initialized */ +#define USS820_MCSR_RWUPR 0x80 /* Remote Wakeup-Up Remember */ + +#define USS820_DSAV 0x1d /* Data set available low (Read Only) */ +#define USS820_DSAV_TXAV0 0x01 +#define USS820_DSAV_RXAV0 0x02 +#define USS820_DSAV_TXAV1 0x04 +#define USS820_DSAV_RXAV1 0x08 +#define USS820_DSAV_TXAV2 0x10 +#define USS820_DSAV_RXAV2 0x20 +#define USS820_DSAV_TXAV3 0x40 +#define USS820_DSAV_RXAV3 0x80 + +#define USS820_DSAV1 0x1e /* Data set available high */ +#define USS820_DSAV1_TXAV4 0x01 +#define USS820_DSAV1_RXAV4 0x02 +#define USS820_DSAV1_TXAV5 0x04 +#define USS820_DSAV1_RXAV5 0x08 +#define USS820_DSAV1_TXAV6 0x10 +#define USS820_DSAV1_RXAV6 0x20 +#define USS820_DSAV1_TXAV7 0x40 +#define USS820_DSAV1_RXAV7 0x80 + +#define USS820_UNK1 0x1f /* Unknown */ +#define USS820_UNK1_UNKNOWN 0xFF + +#define USS820_READ_1(sc, reg) \ + bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg) + +#define USS820_WRITE_1(sc, reg, data) \ + bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data) + +struct uss820dci_td; + +typedef uint8_t (uss820dci_cmd_t)(struct uss820dci_td *td); + +struct uss820dci_td { + bus_space_tag_t io_tag; + bus_space_handle_t io_hdl; + struct uss820dci_td *obj_next; + uss820dci_cmd_t *func; + struct usb_page_cache *pc; + uint32_t offset; + uint32_t remainder; + uint16_t max_packet_size; + uint8_t ep_index; + uint8_t error:1; + uint8_t alt_next:1; + uint8_t short_pkt:1; + uint8_t support_multi_buffer:1; + uint8_t did_stall:1; + uint8_t did_enable:1; +}; + +struct uss820_std_temp { + uss820dci_cmd_t *func; + struct usb_page_cache *pc; + struct uss820dci_td *td; + struct uss820dci_td *td_next; + uint32_t len; + uint32_t offset; + uint16_t max_frame_size; + uint8_t short_pkt; + /* + * short_pkt = 0: transfer should be short terminated + * short_pkt = 1: transfer should not be short terminated + */ + uint8_t setup_alt_next; + uint8_t did_stall; +}; + +struct uss820dci_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union uss820_hub_temp { + uWord wValue; + struct usb_port_status ps; +}; + +struct uss820_flags { + uint8_t change_connect:1; + uint8_t change_suspend:1; + uint8_t status_suspend:1; /* set if suspended */ + uint8_t status_vbus:1; /* set if present */ + uint8_t status_bus_reset:1; /* set if reset complete */ + uint8_t clocks_off:1; + uint8_t port_powered:1; + uint8_t port_enabled:1; + uint8_t d_pulled_up:1; + uint8_t mcsr_feat:1; +}; + +struct uss820dci_softc { + struct usb_bus sc_bus; + union uss820_hub_temp sc_hub_temp; + + struct usb_device *sc_devices[USS820_MAX_DEVICES]; + struct resource *sc_io_res; + struct resource *sc_irq_res; + void *sc_intr_hdl; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + uint8_t sc_rt_addr; /* root HUB address */ + uint8_t sc_dv_addr; /* device address */ + uint8_t sc_conf; /* root HUB config */ + + uint8_t sc_hub_idata[1]; + + struct uss820_flags sc_flags; +}; + +/* prototypes */ + +usb_error_t uss820dci_init(struct uss820dci_softc *sc); +void uss820dci_uninit(struct uss820dci_softc *sc); +void uss820dci_interrupt(struct uss820dci_softc *sc); + +#endif /* _USS820_DCI_H_ */ diff --git a/sys/bus/u4b/controller/uss820dci_atmelarm.c b/sys/bus/u4b/controller/uss820dci_atmelarm.c new file mode 100644 index 0000000000..7fd83a229c --- /dev/null +++ b/sys/bus/u4b/controller/uss820dci_atmelarm.c @@ -0,0 +1,205 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2008 Hans Petter Selasky + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +static device_probe_t uss820_atmelarm_probe; +static device_attach_t uss820_atmelarm_attach; +static device_detach_t uss820_atmelarm_detach; + +static device_method_t uss820dci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uss820_atmelarm_probe), + DEVMETHOD(device_attach, uss820_atmelarm_attach), + DEVMETHOD(device_detach, uss820_atmelarm_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD_END +}; + +static driver_t uss820dci_driver = { + .name = "uss820", + .methods = uss820dci_methods, + .size = sizeof(struct uss820dci_softc), +}; + +static devclass_t uss820dci_devclass; + +DRIVER_MODULE(uss820, atmelarm, uss820dci_driver, uss820dci_devclass, 0, 0); +MODULE_DEPEND(uss820, usb, 1, 1, 1); + +static const char *const uss820_desc = "USS820 USB Device Controller"; + +static int +uss820_atmelarm_probe(device_t dev) +{ + device_set_desc(dev, uss820_desc); + return (0); /* success */ +} + +static int +uss820_atmelarm_attach(device_t dev) +{ + struct uss820dci_softc *sc = device_get_softc(dev); + int err; + int rid; + + /* initialise some bus fields */ + sc->sc_bus.parent = dev; + sc->sc_bus.devices = sc->sc_devices; + sc->sc_bus.devices_max = USS820_MAX_DEVICES; + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_bus, + USB_GET_DMA_TAG(dev), NULL)) { + return (ENOMEM); + } + rid = 0; + sc->sc_io_res = + bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); + + if (!sc->sc_io_res) { + goto error; + } + sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res); + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + goto error; + } + sc->sc_bus.bdev = device_add_child(dev, "usbus", -1); + if (!(sc->sc_bus.bdev)) { + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)uss820dci_interrupt, sc, &sc->sc_intr_hdl); +#else + err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (driver_intr_t *)uss820dci_interrupt, sc, &sc->sc_intr_hdl); +#endif + if (err) { + sc->sc_intr_hdl = NULL; + goto error; + } + err = uss820dci_init(sc); + if (err) { + device_printf(dev, "Init failed\n"); + goto error; + } + err = device_probe_and_attach(sc->sc_bus.bdev); + if (err) { + device_printf(dev, "USB probe and attach failed\n"); + goto error; + } + return (0); + +error: + uss820_atmelarm_detach(dev); + return (ENXIO); +} + +static int +uss820_atmelarm_detach(device_t dev) +{ + struct uss820dci_softc *sc = device_get_softc(dev); + device_t bdev; + int err; + + if (sc->sc_bus.bdev) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(dev, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(dev); + + if (sc->sc_irq_res && sc->sc_intr_hdl) { + /* + * only call at91_udp_uninit() after at91_udp_init() + */ + uss820dci_uninit(sc); + + err = bus_teardown_intr(dev, sc->sc_irq_res, + sc->sc_intr_hdl); + sc->sc_intr_hdl = NULL; + } + if (sc->sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, 0, + sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(dev, SYS_RES_IOPORT, 0, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + usb_bus_mem_free_all(&sc->sc_bus, NULL); + + return (0); +} diff --git a/sys/bus/u4b/controller/xhci.c b/sys/bus/u4b/controller/xhci.c new file mode 100644 index 0000000000..a9690f9775 --- /dev/null +++ b/sys/bus/u4b/controller/xhci.c @@ -0,0 +1,3945 @@ +/*- + * Copyright (c) 2010 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * USB eXtensible Host Controller Interface, a.k.a. USB 3.0 controller. + * + * The XHCI 1.0 spec can be found at + * http://www.intel.com/technology/usb/download/xHCI_Specification_for_USB.pdf + * and the USB 3.0 spec at + * http://www.usb.org/developers/docs/usb_30_spec_060910.zip + */ + +/* + * A few words about the design implementation: This driver emulates + * the concept about TDs which is found in EHCI specification. This + * way we avoid too much diveration among USB drivers. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define USB_DEBUG_VAR xhcidebug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define XHCI_BUS2SC(bus) \ + ((struct xhci_softc *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((struct xhci_softc *)0)->sc_bus)))) + +#ifdef USB_DEBUG +static int xhcidebug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, xhci, CTLFLAG_RW, 0, "USB XHCI"); +SYSCTL_INT(_hw_usb_xhci, OID_AUTO, debug, CTLFLAG_RW, + &xhcidebug, 0, "Debug level"); + +TUNABLE_INT("hw.usb.xhci.debug", &xhcidebug); + +#endif + +#define XHCI_INTR_ENDPT 1 + +struct xhci_std_temp { + struct xhci_softc *sc; + struct usb_page_cache *pc; + struct xhci_td *td; + struct xhci_td *td_next; + uint32_t len; + uint32_t offset; + uint32_t max_packet_size; + uint32_t average; + uint16_t isoc_delta; + uint16_t isoc_frame; + uint8_t shortpkt; + uint8_t multishort; + uint8_t last_frame; + uint8_t trb_type; + uint8_t direction; + uint8_t tbc; + uint8_t tlbpc; + uint8_t step_td; +}; + +static void xhci_do_poll(struct usb_bus *); +static void xhci_device_done(struct usb_xfer *, usb_error_t); +static void xhci_root_intr(struct xhci_softc *); +static void xhci_free_device_ext(struct usb_device *); +static struct xhci_endpoint_ext *xhci_get_endpoint_ext(struct usb_device *, + struct usb_endpoint_descriptor *); +static usb_proc_callback_t xhci_configure_msg; +static usb_error_t xhci_configure_device(struct usb_device *); +static usb_error_t xhci_configure_endpoint(struct usb_device *, + struct usb_endpoint_descriptor *, uint64_t, uint16_t, + uint8_t, uint8_t, uint8_t, uint16_t, uint16_t); +static usb_error_t xhci_configure_mask(struct usb_device *, + uint32_t, uint8_t); +static usb_error_t xhci_cmd_evaluate_ctx(struct xhci_softc *, + uint64_t, uint8_t); +static void xhci_endpoint_doorbell(struct usb_xfer *); +static void xhci_ctx_set_le32(struct xhci_softc *sc, volatile uint32_t *ptr, uint32_t val); +static uint32_t xhci_ctx_get_le32(struct xhci_softc *sc, volatile uint32_t *ptr); +static void xhci_ctx_set_le64(struct xhci_softc *sc, volatile uint64_t *ptr, uint64_t val); +#ifdef USB_DEBUG +static uint64_t xhci_ctx_get_le64(struct xhci_softc *sc, volatile uint64_t *ptr); +#endif + +extern struct usb_bus_methods xhci_bus_methods; + +#ifdef USB_DEBUG +static void +xhci_dump_trb(struct xhci_trb *trb) +{ + DPRINTFN(5, "trb = %p\n", trb); + DPRINTFN(5, "qwTrb0 = 0x%016llx\n", (long long)le64toh(trb->qwTrb0)); + DPRINTFN(5, "dwTrb2 = 0x%08x\n", le32toh(trb->dwTrb2)); + DPRINTFN(5, "dwTrb3 = 0x%08x\n", le32toh(trb->dwTrb3)); +} + +static void +xhci_dump_endpoint(struct xhci_softc *sc, struct xhci_endp_ctx *pep) +{ + DPRINTFN(5, "pep = %p\n", pep); + DPRINTFN(5, "dwEpCtx0=0x%08x\n", xhci_ctx_get_le32(sc, &pep->dwEpCtx0)); + DPRINTFN(5, "dwEpCtx1=0x%08x\n", xhci_ctx_get_le32(sc, &pep->dwEpCtx1)); + DPRINTFN(5, "qwEpCtx2=0x%016llx\n", (long long)xhci_ctx_get_le64(sc, &pep->qwEpCtx2)); + DPRINTFN(5, "dwEpCtx4=0x%08x\n", xhci_ctx_get_le32(sc, &pep->dwEpCtx4)); + DPRINTFN(5, "dwEpCtx5=0x%08x\n", xhci_ctx_get_le32(sc, &pep->dwEpCtx5)); + DPRINTFN(5, "dwEpCtx6=0x%08x\n", xhci_ctx_get_le32(sc, &pep->dwEpCtx6)); + DPRINTFN(5, "dwEpCtx7=0x%08x\n", xhci_ctx_get_le32(sc, &pep->dwEpCtx7)); +} + +static void +xhci_dump_device(struct xhci_softc *sc, struct xhci_slot_ctx *psl) +{ + DPRINTFN(5, "psl = %p\n", psl); + DPRINTFN(5, "dwSctx0=0x%08x\n", xhci_ctx_get_le32(sc, &psl->dwSctx0)); + DPRINTFN(5, "dwSctx1=0x%08x\n", xhci_ctx_get_le32(sc, &psl->dwSctx1)); + DPRINTFN(5, "dwSctx2=0x%08x\n", xhci_ctx_get_le32(sc, &psl->dwSctx2)); + DPRINTFN(5, "dwSctx3=0x%08x\n", xhci_ctx_get_le32(sc, &psl->dwSctx3)); +} +#endif + +static void +xhci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb) +{ + struct xhci_softc *sc = XHCI_BUS2SC(bus); + uint8_t i; + + cb(bus, &sc->sc_hw.root_pc, &sc->sc_hw.root_pg, + sizeof(struct xhci_hw_root), XHCI_PAGE_SIZE); + + cb(bus, &sc->sc_hw.ctx_pc, &sc->sc_hw.ctx_pg, + sizeof(struct xhci_dev_ctx_addr), XHCI_PAGE_SIZE); + + for (i = 0; i != XHCI_MAX_SCRATCHPADS; i++) { + cb(bus, &sc->sc_hw.scratch_pc[i], &sc->sc_hw.scratch_pg[i], + XHCI_PAGE_SIZE, XHCI_PAGE_SIZE); + } +} + +static void +xhci_ctx_set_le32(struct xhci_softc *sc, volatile uint32_t *ptr, uint32_t val) +{ + if (sc->sc_ctx_is_64_byte) { + uint32_t offset; + /* exploit the fact that our structures are XHCI_PAGE_SIZE aligned */ + /* all contexts are initially 32-bytes */ + offset = ((uintptr_t)ptr) & ((XHCI_PAGE_SIZE - 1) & ~(31U)); + ptr = (volatile uint32_t *)(((volatile uint8_t *)ptr) + offset); + } + *ptr = htole32(val); +} + +static uint32_t +xhci_ctx_get_le32(struct xhci_softc *sc, volatile uint32_t *ptr) +{ + if (sc->sc_ctx_is_64_byte) { + uint32_t offset; + /* exploit the fact that our structures are XHCI_PAGE_SIZE aligned */ + /* all contexts are initially 32-bytes */ + offset = ((uintptr_t)ptr) & ((XHCI_PAGE_SIZE - 1) & ~(31U)); + ptr = (volatile uint32_t *)(((volatile uint8_t *)ptr) + offset); + } + return (le32toh(*ptr)); +} + +static void +xhci_ctx_set_le64(struct xhci_softc *sc, volatile uint64_t *ptr, uint64_t val) +{ + if (sc->sc_ctx_is_64_byte) { + uint32_t offset; + /* exploit the fact that our structures are XHCI_PAGE_SIZE aligned */ + /* all contexts are initially 32-bytes */ + offset = ((uintptr_t)ptr) & ((XHCI_PAGE_SIZE - 1) & ~(31U)); + ptr = (volatile uint64_t *)(((volatile uint8_t *)ptr) + offset); + } + *ptr = htole64(val); +} + +#ifdef USB_DEBUG +static uint64_t +xhci_ctx_get_le64(struct xhci_softc *sc, volatile uint64_t *ptr) +{ + if (sc->sc_ctx_is_64_byte) { + uint32_t offset; + /* exploit the fact that our structures are XHCI_PAGE_SIZE aligned */ + /* all contexts are initially 32-bytes */ + offset = ((uintptr_t)ptr) & ((XHCI_PAGE_SIZE - 1) & ~(31U)); + ptr = (volatile uint64_t *)(((volatile uint8_t *)ptr) + offset); + } + return (le64toh(*ptr)); +} +#endif + +usb_error_t +xhci_start_controller(struct xhci_softc *sc) +{ + struct usb_page_search buf_res; + struct xhci_hw_root *phwr; + struct xhci_dev_ctx_addr *pdctxa; + uint64_t addr; + uint32_t temp; + uint16_t i; + + DPRINTF("\n"); + + sc->sc_capa_off = 0; + sc->sc_oper_off = XREAD1(sc, capa, XHCI_CAPLENGTH); + sc->sc_runt_off = XREAD4(sc, capa, XHCI_RTSOFF) & ~0x1F; + sc->sc_door_off = XREAD4(sc, capa, XHCI_DBOFF) & ~0x3; + + DPRINTF("CAPLENGTH=0x%x\n", sc->sc_oper_off); + DPRINTF("RUNTIMEOFFSET=0x%x\n", sc->sc_runt_off); + DPRINTF("DOOROFFSET=0x%x\n", sc->sc_door_off); + + sc->sc_event_ccs = 1; + sc->sc_event_idx = 0; + sc->sc_command_ccs = 1; + sc->sc_command_idx = 0; + + DPRINTF("xHCI version = 0x%04x\n", XREAD2(sc, capa, XHCI_HCIVERSION)); + + temp = XREAD4(sc, capa, XHCI_HCSPARAMS0); + + DPRINTF("HCS0 = 0x%08x\n", temp); + + if (XHCI_HCS0_CSZ(temp)) { + sc->sc_ctx_is_64_byte = 1; + device_printf(sc->sc_bus.parent, "64 byte context size.\n"); + } else { + sc->sc_ctx_is_64_byte = 0; + device_printf(sc->sc_bus.parent, "32 byte context size.\n"); + } + + /* Reset controller */ + XWRITE4(sc, oper, XHCI_USBCMD, XHCI_CMD_HCRST); + + for (i = 0; i != 100; i++) { + usb_pause_mtx(NULL, hz / 100); + temp = XREAD4(sc, oper, XHCI_USBCMD) & + (XHCI_CMD_HCRST | XHCI_STS_CNR); + if (!temp) + break; + } + + if (temp) { + device_printf(sc->sc_bus.parent, "Controller " + "reset timeout.\n"); + return (USB_ERR_IOERROR); + } + + if (!(XREAD4(sc, oper, XHCI_PAGESIZE) & XHCI_PAGESIZE_4K)) { + device_printf(sc->sc_bus.parent, "Controller does " + "not support 4K page size.\n"); + return (USB_ERR_IOERROR); + } + + temp = XREAD4(sc, capa, XHCI_HCSPARAMS1); + + i = XHCI_HCS1_N_PORTS(temp); + + if (i == 0) { + device_printf(sc->sc_bus.parent, "Invalid number " + "of ports: %u\n", i); + return (USB_ERR_IOERROR); + } + + sc->sc_noport = i; + sc->sc_noslot = XHCI_HCS1_DEVSLOT_MAX(temp); + + if (sc->sc_noslot > XHCI_MAX_DEVICES) + sc->sc_noslot = XHCI_MAX_DEVICES; + + /* setup number of device slots */ + + DPRINTF("CONFIG=0x%08x -> 0x%08x\n", + XREAD4(sc, oper, XHCI_CONFIG), sc->sc_noslot); + + XWRITE4(sc, oper, XHCI_CONFIG, sc->sc_noslot); + + DPRINTF("Max slots: %u\n", sc->sc_noslot); + + temp = XREAD4(sc, capa, XHCI_HCSPARAMS2); + + sc->sc_noscratch = XHCI_HCS2_SPB_MAX(temp); + + if (sc->sc_noscratch > XHCI_MAX_SCRATCHPADS) { + device_printf(sc->sc_bus.parent, "XHCI request " + "too many scratchpads\n"); + return (USB_ERR_NOMEM); + } + + DPRINTF("Max scratch: %u\n", sc->sc_noscratch); + + temp = XREAD4(sc, capa, XHCI_HCSPARAMS3); + + sc->sc_exit_lat_max = XHCI_HCS3_U1_DEL(temp) + + XHCI_HCS3_U2_DEL(temp) + 250 /* us */; + + temp = XREAD4(sc, oper, XHCI_USBSTS); + + /* clear interrupts */ + XWRITE4(sc, oper, XHCI_USBSTS, temp); + /* disable all device notifications */ + XWRITE4(sc, oper, XHCI_DNCTRL, 0); + + /* setup device context base address */ + usbd_get_page(&sc->sc_hw.ctx_pc, 0, &buf_res); + pdctxa = buf_res.buffer; + memset(pdctxa, 0, sizeof(*pdctxa)); + + addr = buf_res.physaddr; + addr += (uintptr_t)&((struct xhci_dev_ctx_addr *)0)->qwSpBufPtr[0]; + + /* slot 0 points to the table of scratchpad pointers */ + pdctxa->qwBaaDevCtxAddr[0] = htole64(addr); + + for (i = 0; i != sc->sc_noscratch; i++) { + struct usb_page_search buf_scp; + usbd_get_page(&sc->sc_hw.scratch_pc[i], 0, &buf_scp); + pdctxa->qwSpBufPtr[i] = htole64((uint64_t)buf_scp.physaddr); + } + + addr = buf_res.physaddr; + + XWRITE4(sc, oper, XHCI_DCBAAP_LO, (uint32_t)addr); + XWRITE4(sc, oper, XHCI_DCBAAP_HI, (uint32_t)(addr >> 32)); + XWRITE4(sc, oper, XHCI_DCBAAP_LO, (uint32_t)addr); + XWRITE4(sc, oper, XHCI_DCBAAP_HI, (uint32_t)(addr >> 32)); + + /* Setup event table size */ + + temp = XREAD4(sc, capa, XHCI_HCSPARAMS2); + + DPRINTF("HCS2=0x%08x\n", temp); + + temp = XHCI_HCS2_ERST_MAX(temp); + temp = 1U << temp; + if (temp > XHCI_MAX_RSEG) + temp = XHCI_MAX_RSEG; + + sc->sc_erst_max = temp; + + DPRINTF("ERSTSZ=0x%08x -> 0x%08x\n", + XREAD4(sc, runt, XHCI_ERSTSZ(0)), temp); + + XWRITE4(sc, runt, XHCI_ERSTSZ(0), XHCI_ERSTS_SET(temp)); + + /* Setup interrupt rate */ + XWRITE4(sc, runt, XHCI_IMOD(0), XHCI_IMOD_DEFAULT); + + usbd_get_page(&sc->sc_hw.root_pc, 0, &buf_res); + + phwr = buf_res.buffer; + addr = buf_res.physaddr; + addr += (uintptr_t)&((struct xhci_hw_root *)0)->hwr_events[0]; + + /* reset hardware root structure */ + memset(phwr, 0, sizeof(*phwr)); + + phwr->hwr_ring_seg[0].qwEvrsTablePtr = htole64(addr); + phwr->hwr_ring_seg[0].dwEvrsTableSize = htole32(XHCI_MAX_EVENTS); + + DPRINTF("ERDP(0)=0x%016llx\n", (unsigned long long)addr); + + XWRITE4(sc, runt, XHCI_ERDP_LO(0), (uint32_t)addr); + XWRITE4(sc, runt, XHCI_ERDP_HI(0), (uint32_t)(addr >> 32)); + + addr = (uint64_t)buf_res.physaddr; + + DPRINTF("ERSTBA(0)=0x%016llx\n", (unsigned long long)addr); + + XWRITE4(sc, runt, XHCI_ERSTBA_LO(0), (uint32_t)addr); + XWRITE4(sc, runt, XHCI_ERSTBA_HI(0), (uint32_t)(addr >> 32)); + + /* Setup interrupter registers */ + + temp = XREAD4(sc, runt, XHCI_IMAN(0)); + temp |= XHCI_IMAN_INTR_ENA; + XWRITE4(sc, runt, XHCI_IMAN(0), temp); + + /* setup command ring control base address */ + addr = buf_res.physaddr; + addr += (uintptr_t)&((struct xhci_hw_root *)0)->hwr_commands[0]; + + DPRINTF("CRCR=0x%016llx\n", (unsigned long long)addr); + + XWRITE4(sc, oper, XHCI_CRCR_LO, ((uint32_t)addr) | XHCI_CRCR_LO_RCS); + XWRITE4(sc, oper, XHCI_CRCR_HI, (uint32_t)(addr >> 32)); + + phwr->hwr_commands[XHCI_MAX_COMMANDS - 1].qwTrb0 = htole64(addr); + + usb_bus_mem_flush_all(&sc->sc_bus, &xhci_iterate_hw_softc); + + /* Go! */ + XWRITE4(sc, oper, XHCI_USBCMD, XHCI_CMD_RS | + XHCI_CMD_INTE | XHCI_CMD_HSEE); + + for (i = 0; i != 100; i++) { + usb_pause_mtx(NULL, hz / 100); + temp = XREAD4(sc, oper, XHCI_USBSTS) & XHCI_STS_HCH; + if (!temp) + break; + } + if (temp) { + XWRITE4(sc, oper, XHCI_USBCMD, 0); + device_printf(sc->sc_bus.parent, "Run timeout.\n"); + return (USB_ERR_IOERROR); + } + + /* catch any lost interrupts */ + xhci_do_poll(&sc->sc_bus); + + return (0); +} + +usb_error_t +xhci_halt_controller(struct xhci_softc *sc) +{ + uint32_t temp; + uint16_t i; + + DPRINTF("\n"); + + sc->sc_capa_off = 0; + sc->sc_oper_off = XREAD1(sc, capa, XHCI_CAPLENGTH); + sc->sc_runt_off = XREAD4(sc, capa, XHCI_RTSOFF) & ~0xF; + sc->sc_door_off = XREAD4(sc, capa, XHCI_DBOFF) & ~0x3; + + /* Halt controller */ + XWRITE4(sc, oper, XHCI_USBCMD, 0); + + for (i = 0; i != 100; i++) { + usb_pause_mtx(NULL, hz / 100); + temp = XREAD4(sc, oper, XHCI_USBSTS) & XHCI_STS_HCH; + if (temp) + break; + } + + if (!temp) { + device_printf(sc->sc_bus.parent, "Controller halt timeout.\n"); + return (USB_ERR_IOERROR); + } + return (0); +} + +usb_error_t +xhci_init(struct xhci_softc *sc, device_t self) +{ + /* initialise some bus fields */ + sc->sc_bus.parent = self; + + /* set the bus revision */ + sc->sc_bus.usbrev = USB_REV_3_0; + + /* set up the bus struct */ + sc->sc_bus.methods = &xhci_bus_methods; + + /* setup devices array */ + sc->sc_bus.devices = sc->sc_devices; + sc->sc_bus.devices_max = XHCI_MAX_DEVICES; + + /* setup command queue mutex and condition varible */ + cv_init(&sc->sc_cmd_cv, "CMDQ"); + sx_init(&sc->sc_cmd_sx, "CMDQ lock"); + + /* get all DMA memory */ + if (usb_bus_mem_alloc_all(&sc->sc_bus, + USB_GET_DMA_TAG(self), &xhci_iterate_hw_softc)) { + return (ENOMEM); + } + + sc->sc_config_msg[0].hdr.pm_callback = &xhci_configure_msg; + sc->sc_config_msg[0].bus = &sc->sc_bus; + sc->sc_config_msg[1].hdr.pm_callback = &xhci_configure_msg; + sc->sc_config_msg[1].bus = &sc->sc_bus; + + if (usb_proc_create(&sc->sc_config_proc, + &sc->sc_bus.bus_mtx, device_get_nameunit(self), USB_PRI_MED)) { + printf("WARNING: Creation of XHCI configure " + "callback process failed.\n"); + } + return (0); +} + +void +xhci_uninit(struct xhci_softc *sc) +{ + usb_proc_free(&sc->sc_config_proc); + + usb_bus_mem_free_all(&sc->sc_bus, &xhci_iterate_hw_softc); + + cv_destroy(&sc->sc_cmd_cv); + sx_destroy(&sc->sc_cmd_sx); +} + +static void +xhci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) +{ + struct xhci_softc *sc = XHCI_BUS2SC(bus); + + switch (state) { + case USB_HW_POWER_SUSPEND: + DPRINTF("Stopping the XHCI\n"); + xhci_halt_controller(sc); + break; + case USB_HW_POWER_SHUTDOWN: + DPRINTF("Stopping the XHCI\n"); + xhci_halt_controller(sc); + break; + case USB_HW_POWER_RESUME: + DPRINTF("Starting the XHCI\n"); + xhci_start_controller(sc); + break; + default: + break; + } +} + +static usb_error_t +xhci_generic_done_sub(struct usb_xfer *xfer) +{ + struct xhci_td *td; + struct xhci_td *td_alt_next; + uint32_t len; + uint8_t status; + + td = xfer->td_transfer_cache; + td_alt_next = td->alt_next; + + if (xfer->aframes != xfer->nframes) + usbd_xfer_set_frame_len(xfer, xfer->aframes, 0); + + while (1) { + + usb_pc_cpu_invalidate(td->page_cache); + + status = td->status; + len = td->remainder; + + DPRINTFN(4, "xfer=%p[%u/%u] rem=%u/%u status=%u\n", + xfer, (unsigned int)xfer->aframes, + (unsigned int)xfer->nframes, + (unsigned int)len, (unsigned int)td->len, + (unsigned int)status); + + /* + * Verify the status length and + * add the length to "frlengths[]": + */ + if (len > td->len) { + /* should not happen */ + DPRINTF("Invalid status length, " + "0x%04x/0x%04x bytes\n", len, td->len); + status = XHCI_TRB_ERROR_LENGTH; + } else if (xfer->aframes != xfer->nframes) { + xfer->frlengths[xfer->aframes] += td->len - len; + } + /* Check for last transfer */ + if (((void *)td) == xfer->td_transfer_last) { + td = NULL; + break; + } + /* Check for transfer error */ + if (status != XHCI_TRB_ERROR_SHORT_PKT && + status != XHCI_TRB_ERROR_SUCCESS) { + /* the transfer is finished */ + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + if (xfer->flags_int.short_frames_ok || + xfer->flags_int.isochronous_xfr || + xfer->flags_int.control_xfr) { + /* follow alt next */ + td = td->alt_next; + } else { + /* the transfer is finished */ + td = NULL; + } + break; + } + td = td->obj_next; + + if (td->alt_next != td_alt_next) { + /* this USB frame is complete */ + break; + } + } + + /* update transfer cache */ + + xfer->td_transfer_cache = td; + + return ((status == XHCI_TRB_ERROR_STALL) ? USB_ERR_STALLED : + (status != XHCI_TRB_ERROR_SHORT_PKT && + status != XHCI_TRB_ERROR_SUCCESS) ? USB_ERR_IOERROR : + USB_ERR_NORMAL_COMPLETION); +} + +static void +xhci_generic_done(struct usb_xfer *xfer) +{ + usb_error_t err = 0; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) + err = xhci_generic_done_sub(xfer); + + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) + goto done; + } + + while (xfer->aframes != xfer->nframes) { + + err = xhci_generic_done_sub(xfer); + xfer->aframes++; + + if (xfer->td_transfer_cache == NULL) + goto done; + } + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) + err = xhci_generic_done_sub(xfer); +done: + /* transfer is complete */ + xhci_device_done(xfer, err); +} + +static void +xhci_activate_transfer(struct usb_xfer *xfer) +{ + struct xhci_td *td; + + td = xfer->td_transfer_cache; + + usb_pc_cpu_invalidate(td->page_cache); + + if (!(td->td_trb[0].dwTrb3 & htole32(XHCI_TRB_3_CYCLE_BIT))) { + + /* activate the transfer */ + + td->td_trb[0].dwTrb3 |= htole32(XHCI_TRB_3_CYCLE_BIT); + usb_pc_cpu_flush(td->page_cache); + + xhci_endpoint_doorbell(xfer); + } +} + +static void +xhci_skip_transfer(struct usb_xfer *xfer) +{ + struct xhci_td *td; + struct xhci_td *td_last; + + td = xfer->td_transfer_cache; + td_last = xfer->td_transfer_last; + + td = td->alt_next; + + usb_pc_cpu_invalidate(td->page_cache); + + if (!(td->td_trb[0].dwTrb3 & htole32(XHCI_TRB_3_CYCLE_BIT))) { + + usb_pc_cpu_invalidate(td_last->page_cache); + + /* copy LINK TRB to current waiting location */ + + td->td_trb[0].qwTrb0 = td_last->td_trb[td_last->ntrb].qwTrb0; + td->td_trb[0].dwTrb2 = td_last->td_trb[td_last->ntrb].dwTrb2; + usb_pc_cpu_flush(td->page_cache); + + td->td_trb[0].dwTrb3 = td_last->td_trb[td_last->ntrb].dwTrb3; + usb_pc_cpu_flush(td->page_cache); + + xhci_endpoint_doorbell(xfer); + } +} + +/*------------------------------------------------------------------------* + * xhci_check_transfer + *------------------------------------------------------------------------*/ +static void +xhci_check_transfer(struct xhci_softc *sc, struct xhci_trb *trb) +{ + int64_t offset; + uint64_t td_event; + uint32_t temp; + uint32_t remainder; + uint8_t status; + uint8_t halted; + uint8_t epno; + uint8_t index; + uint8_t i; + + /* decode TRB */ + td_event = le64toh(trb->qwTrb0); + temp = le32toh(trb->dwTrb2); + + remainder = XHCI_TRB_2_REM_GET(temp); + status = XHCI_TRB_2_ERROR_GET(temp); + + temp = le32toh(trb->dwTrb3); + epno = XHCI_TRB_3_EP_GET(temp); + index = XHCI_TRB_3_SLOT_GET(temp); + + /* check if error means halted */ + halted = (status != XHCI_TRB_ERROR_SHORT_PKT && + status != XHCI_TRB_ERROR_SUCCESS); + + DPRINTF("slot=%u epno=%u remainder=%u status=%u\n", + index, epno, remainder, status); + + if (index > sc->sc_noslot) { + DPRINTF("Invalid slot.\n"); + return; + } + + if ((epno == 0) || (epno >= XHCI_MAX_ENDPOINTS)) { + DPRINTF("Invalid endpoint.\n"); + return; + } + + /* try to find the USB transfer that generated the event */ + for (i = 0; i != (XHCI_MAX_TRANSFERS - 1); i++) { + struct usb_xfer *xfer; + struct xhci_td *td; + struct xhci_endpoint_ext *pepext; + + pepext = &sc->sc_hw.devs[index].endp[epno]; + + xfer = pepext->xfer[i]; + if (xfer == NULL) + continue; + + td = xfer->td_transfer_cache; + + DPRINTFN(5, "Checking if 0x%016llx == (0x%016llx .. 0x%016llx)\n", + (long long)td_event, + (long long)td->td_self, + (long long)td->td_self + sizeof(td->td_trb)); + + /* + * NOTE: Some XHCI implementations might not trigger + * an event on the last LINK TRB so we need to + * consider both the last and second last event + * address as conditions for a successful transfer. + * + * NOTE: We assume that the XHCI will only trigger one + * event per chain of TRBs. + */ + + offset = td_event - td->td_self; + + if (offset >= 0 && + offset < sizeof(td->td_trb)) { + + usb_pc_cpu_invalidate(td->page_cache); + + /* compute rest of remainder, if any */ + for (i = (offset / 16) + 1; i < td->ntrb; i++) { + temp = le32toh(td->td_trb[i].dwTrb2); + remainder += XHCI_TRB_2_BYTES_GET(temp); + } + + DPRINTFN(5, "New remainder: %u\n", remainder); + + /* clear isochronous transfer errors */ + if (xfer->flags_int.isochronous_xfr) { + if (halted) { + halted = 0; + status = XHCI_TRB_ERROR_SUCCESS; + remainder = td->len; + } + } + + /* "td->remainder" is verified later */ + td->remainder = remainder; + td->status = status; + + usb_pc_cpu_flush(td->page_cache); + + /* + * 1) Last transfer descriptor makes the + * transfer done + */ + if (((void *)td) == xfer->td_transfer_last) { + DPRINTF("TD is last\n"); + xhci_generic_done(xfer); + break; + } + + /* + * 2) Any kind of error makes the transfer + * done + */ + if (halted) { + DPRINTF("TD has I/O error\n"); + xhci_generic_done(xfer); + break; + } + + /* + * 3) If there is no alternate next transfer, + * a short packet also makes the transfer done + */ + if (td->remainder > 0) { + DPRINTF("TD has short pkt\n"); + if (xfer->flags_int.short_frames_ok || + xfer->flags_int.isochronous_xfr || + xfer->flags_int.control_xfr) { + /* follow the alt next */ + xfer->td_transfer_cache = td->alt_next; + xhci_activate_transfer(xfer); + break; + } + xhci_skip_transfer(xfer); + xhci_generic_done(xfer); + break; + } + + /* + * 4) Transfer complete - go to next TD + */ + DPRINTF("Following next TD\n"); + xfer->td_transfer_cache = td->obj_next; + xhci_activate_transfer(xfer); + break; /* there should only be one match */ + } + } +} + +static void +xhci_check_command(struct xhci_softc *sc, struct xhci_trb *trb) +{ + if (sc->sc_cmd_addr == trb->qwTrb0) { + DPRINTF("Received command event\n"); + sc->sc_cmd_result[0] = trb->dwTrb2; + sc->sc_cmd_result[1] = trb->dwTrb3; + cv_signal(&sc->sc_cmd_cv); + } +} + +static void +xhci_interrupt_poll(struct xhci_softc *sc) +{ + struct usb_page_search buf_res; + struct xhci_hw_root *phwr; + uint64_t addr; + uint32_t temp; + uint16_t i; + uint8_t event; + uint8_t j; + uint8_t k; + uint8_t t; + + usbd_get_page(&sc->sc_hw.root_pc, 0, &buf_res); + + phwr = buf_res.buffer; + + /* Receive any events */ + + usb_pc_cpu_invalidate(&sc->sc_hw.root_pc); + + i = sc->sc_event_idx; + j = sc->sc_event_ccs; + t = 2; + + while (1) { + + temp = le32toh(phwr->hwr_events[i].dwTrb3); + + k = (temp & XHCI_TRB_3_CYCLE_BIT) ? 1 : 0; + + if (j != k) + break; + + event = XHCI_TRB_3_TYPE_GET(temp); + + DPRINTFN(10, "event[%u] = %u (0x%016llx 0x%08lx 0x%08lx)\n", + i, event, (long long)le64toh(phwr->hwr_events[i].qwTrb0), + (long)le32toh(phwr->hwr_events[i].dwTrb2), + (long)le32toh(phwr->hwr_events[i].dwTrb3)); + + switch (event) { + case XHCI_TRB_EVENT_TRANSFER: + xhci_check_transfer(sc, &phwr->hwr_events[i]); + break; + case XHCI_TRB_EVENT_CMD_COMPLETE: + xhci_check_command(sc, &phwr->hwr_events[i]); + break; + default: + DPRINTF("Unhandled event = %u\n", event); + break; + } + + i++; + + if (i == XHCI_MAX_EVENTS) { + i = 0; + j ^= 1; + + /* check for timeout */ + if (!--t) + break; + } + } + + sc->sc_event_idx = i; + sc->sc_event_ccs = j; + + /* + * NOTE: The Event Ring Dequeue Pointer Register is 64-bit + * latched. That means to activate the register we need to + * write both the low and high double word of the 64-bit + * register. + */ + + addr = (uint32_t)buf_res.physaddr; + addr += (uintptr_t)&((struct xhci_hw_root *)0)->hwr_events[i]; + + /* try to clear busy bit */ + addr |= XHCI_ERDP_LO_BUSY; + + XWRITE4(sc, runt, XHCI_ERDP_LO(0), (uint32_t)addr); + XWRITE4(sc, runt, XHCI_ERDP_HI(0), (uint32_t)(addr >> 32)); +} + +static usb_error_t +xhci_do_command(struct xhci_softc *sc, struct xhci_trb *trb, + uint16_t timeout_ms) +{ + struct usb_page_search buf_res; + struct xhci_hw_root *phwr; + uint64_t addr; + uint32_t temp; + uint8_t i; + uint8_t j; + int err; + + XHCI_CMD_ASSERT_LOCKED(sc); + + /* get hardware root structure */ + + usbd_get_page(&sc->sc_hw.root_pc, 0, &buf_res); + + phwr = buf_res.buffer; + + /* Queue command */ + + USB_BUS_LOCK(&sc->sc_bus); + + i = sc->sc_command_idx; + j = sc->sc_command_ccs; + + DPRINTFN(10, "command[%u] = %u (0x%016llx, 0x%08lx, 0x%08lx)\n", + i, XHCI_TRB_3_TYPE_GET(le32toh(trb->dwTrb3)), + (long long)le64toh(trb->qwTrb0), + (long)le32toh(trb->dwTrb2), + (long)le32toh(trb->dwTrb3)); + + phwr->hwr_commands[i].qwTrb0 = trb->qwTrb0; + phwr->hwr_commands[i].dwTrb2 = trb->dwTrb2; + + usb_pc_cpu_flush(&sc->sc_hw.root_pc); + + temp = trb->dwTrb3; + + if (j) + temp |= htole32(XHCI_TRB_3_CYCLE_BIT); + else + temp &= ~htole32(XHCI_TRB_3_CYCLE_BIT); + + temp &= ~htole32(XHCI_TRB_3_TC_BIT); + + phwr->hwr_commands[i].dwTrb3 = temp; + + usb_pc_cpu_flush(&sc->sc_hw.root_pc); + + addr = buf_res.physaddr; + addr += (uintptr_t)&((struct xhci_hw_root *)0)->hwr_commands[i]; + + sc->sc_cmd_addr = htole64(addr); + + i++; + + if (i == (XHCI_MAX_COMMANDS - 1)) { + + if (j) { + temp = htole32(XHCI_TRB_3_TC_BIT | + XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK) | + XHCI_TRB_3_CYCLE_BIT); + } else { + temp = htole32(XHCI_TRB_3_TC_BIT | + XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK)); + } + + phwr->hwr_commands[i].dwTrb3 = temp; + + usb_pc_cpu_flush(&sc->sc_hw.root_pc); + + i = 0; + j ^= 1; + } + + sc->sc_command_idx = i; + sc->sc_command_ccs = j; + + XWRITE4(sc, door, XHCI_DOORBELL(0), 0); + + err = cv_timedwait(&sc->sc_cmd_cv, &sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(timeout_ms)); + + if (err) { + DPRINTFN(0, "Command timeout!\n"); + err = USB_ERR_TIMEOUT; + trb->dwTrb2 = 0; + trb->dwTrb3 = 0; + } else { + temp = le32toh(sc->sc_cmd_result[0]); + if (XHCI_TRB_2_ERROR_GET(temp) != XHCI_TRB_ERROR_SUCCESS) + err = USB_ERR_IOERROR; + + trb->dwTrb2 = sc->sc_cmd_result[0]; + trb->dwTrb3 = sc->sc_cmd_result[1]; + } + + USB_BUS_UNLOCK(&sc->sc_bus); + + return (err); +} + +#if 0 +static usb_error_t +xhci_cmd_nop(struct xhci_softc *sc) +{ + struct xhci_trb trb; + uint32_t temp; + + DPRINTF("\n"); + + trb.qwTrb0 = 0; + trb.dwTrb2 = 0; + temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_NOOP); + + trb.dwTrb3 = htole32(temp); + + return (xhci_do_command(sc, &trb, 100 /* ms */)); +} +#endif + +static usb_error_t +xhci_cmd_enable_slot(struct xhci_softc *sc, uint8_t *pslot) +{ + struct xhci_trb trb; + uint32_t temp; + usb_error_t err; + + DPRINTF("\n"); + + trb.qwTrb0 = 0; + trb.dwTrb2 = 0; + trb.dwTrb3 = htole32(XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_ENABLE_SLOT)); + + err = xhci_do_command(sc, &trb, 100 /* ms */); + if (err) + goto done; + + temp = le32toh(trb.dwTrb3); + + *pslot = XHCI_TRB_3_SLOT_GET(temp); + +done: + return (err); +} + +static usb_error_t +xhci_cmd_disable_slot(struct xhci_softc *sc, uint8_t slot_id) +{ + struct xhci_trb trb; + uint32_t temp; + + DPRINTF("\n"); + + trb.qwTrb0 = 0; + trb.dwTrb2 = 0; + temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_DISABLE_SLOT) | + XHCI_TRB_3_SLOT_SET(slot_id); + + trb.dwTrb3 = htole32(temp); + + return (xhci_do_command(sc, &trb, 100 /* ms */)); +} + +static usb_error_t +xhci_cmd_set_address(struct xhci_softc *sc, uint64_t input_ctx, + uint8_t bsr, uint8_t slot_id) +{ + struct xhci_trb trb; + uint32_t temp; + + DPRINTF("\n"); + + trb.qwTrb0 = htole64(input_ctx); + trb.dwTrb2 = 0; + temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_ADDRESS_DEVICE) | + XHCI_TRB_3_SLOT_SET(slot_id); + + if (bsr) + temp |= XHCI_TRB_3_BSR_BIT; + + trb.dwTrb3 = htole32(temp); + + return (xhci_do_command(sc, &trb, 500 /* ms */)); +} + +static usb_error_t +xhci_set_address(struct usb_device *udev, struct mtx *mtx, uint16_t address) +{ + struct usb_page_search buf_inp; + struct usb_page_search buf_dev; + struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); + struct xhci_hw_dev *hdev; + struct xhci_dev_ctx *pdev; + struct xhci_endpoint_ext *pepext; + uint32_t temp; + uint16_t mps; + usb_error_t err; + uint8_t index; + + /* the root HUB case is not handled here */ + if (udev->parent_hub == NULL) + return (USB_ERR_INVAL); + + index = udev->controller_slot_id; + + hdev = &sc->sc_hw.devs[index]; + + if (mtx != NULL) + mtx_unlock(mtx); + + XHCI_CMD_LOCK(sc); + + switch (hdev->state) { + case XHCI_ST_DEFAULT: + case XHCI_ST_ENABLED: + + hdev->state = XHCI_ST_ENABLED; + + /* set configure mask to slot and EP0 */ + xhci_configure_mask(udev, 3, 0); + + /* configure input slot context structure */ + err = xhci_configure_device(udev); + + if (err != 0) { + DPRINTF("Could not configure device\n"); + break; + } + + /* configure input endpoint context structure */ + switch (udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + mps = 8; + break; + case USB_SPEED_HIGH: + mps = 64; + break; + default: + mps = 512; + break; + } + + pepext = xhci_get_endpoint_ext(udev, + &udev->ctrl_ep_desc); + err = xhci_configure_endpoint(udev, + &udev->ctrl_ep_desc, pepext->physaddr, + 0, 1, 1, 0, mps, mps); + + if (err != 0) { + DPRINTF("Could not configure default endpoint\n"); + break; + } + + /* execute set address command */ + usbd_get_page(&hdev->input_pc, 0, &buf_inp); + + err = xhci_cmd_set_address(sc, buf_inp.physaddr, + (address == 0), index); + + if (err != 0) { + DPRINTF("Could not set address " + "for slot %u.\n", index); + if (address != 0) + break; + } + + /* update device address to new value */ + + usbd_get_page(&hdev->device_pc, 0, &buf_dev); + pdev = buf_dev.buffer; + usb_pc_cpu_invalidate(&hdev->device_pc); + + temp = xhci_ctx_get_le32(sc, &pdev->ctx_slot.dwSctx3); + udev->address = XHCI_SCTX_3_DEV_ADDR_GET(temp); + + /* update device state to new value */ + + if (address != 0) + hdev->state = XHCI_ST_ADDRESSED; + else + hdev->state = XHCI_ST_DEFAULT; + break; + + default: + DPRINTF("Wrong state for set address.\n"); + err = USB_ERR_IOERROR; + break; + } + XHCI_CMD_UNLOCK(sc); + + if (mtx != NULL) + mtx_lock(mtx); + + return (err); +} + +static usb_error_t +xhci_cmd_configure_ep(struct xhci_softc *sc, uint64_t input_ctx, + uint8_t deconfigure, uint8_t slot_id) +{ + struct xhci_trb trb; + uint32_t temp; + + DPRINTF("\n"); + + trb.qwTrb0 = htole64(input_ctx); + trb.dwTrb2 = 0; + temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_CONFIGURE_EP) | + XHCI_TRB_3_SLOT_SET(slot_id); + + if (deconfigure) + temp |= XHCI_TRB_3_DCEP_BIT; + + trb.dwTrb3 = htole32(temp); + + return (xhci_do_command(sc, &trb, 100 /* ms */)); +} + +static usb_error_t +xhci_cmd_evaluate_ctx(struct xhci_softc *sc, uint64_t input_ctx, + uint8_t slot_id) +{ + struct xhci_trb trb; + uint32_t temp; + + DPRINTF("\n"); + + trb.qwTrb0 = htole64(input_ctx); + trb.dwTrb2 = 0; + temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_EVALUATE_CTX) | + XHCI_TRB_3_SLOT_SET(slot_id); + trb.dwTrb3 = htole32(temp); + + return (xhci_do_command(sc, &trb, 100 /* ms */)); +} + +static usb_error_t +xhci_cmd_reset_ep(struct xhci_softc *sc, uint8_t preserve, + uint8_t ep_id, uint8_t slot_id) +{ + struct xhci_trb trb; + uint32_t temp; + + DPRINTF("\n"); + + trb.qwTrb0 = 0; + trb.dwTrb2 = 0; + temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_RESET_EP) | + XHCI_TRB_3_SLOT_SET(slot_id) | + XHCI_TRB_3_EP_SET(ep_id); + + if (preserve) + temp |= XHCI_TRB_3_PRSV_BIT; + + trb.dwTrb3 = htole32(temp); + + return (xhci_do_command(sc, &trb, 100 /* ms */)); +} + +static usb_error_t +xhci_cmd_set_tr_dequeue_ptr(struct xhci_softc *sc, uint64_t dequeue_ptr, + uint16_t stream_id, uint8_t ep_id, uint8_t slot_id) +{ + struct xhci_trb trb; + uint32_t temp; + + DPRINTF("\n"); + + trb.qwTrb0 = htole64(dequeue_ptr); + + temp = XHCI_TRB_2_STREAM_SET(stream_id); + trb.dwTrb2 = htole32(temp); + + temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_SET_TR_DEQUEUE) | + XHCI_TRB_3_SLOT_SET(slot_id) | + XHCI_TRB_3_EP_SET(ep_id); + trb.dwTrb3 = htole32(temp); + + return (xhci_do_command(sc, &trb, 100 /* ms */)); +} + +static usb_error_t +xhci_cmd_stop_ep(struct xhci_softc *sc, uint8_t suspend, + uint8_t ep_id, uint8_t slot_id) +{ + struct xhci_trb trb; + uint32_t temp; + + DPRINTF("\n"); + + trb.qwTrb0 = 0; + trb.dwTrb2 = 0; + temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_STOP_EP) | + XHCI_TRB_3_SLOT_SET(slot_id) | + XHCI_TRB_3_EP_SET(ep_id); + + if (suspend) + temp |= XHCI_TRB_3_SUSP_EP_BIT; + + trb.dwTrb3 = htole32(temp); + + return (xhci_do_command(sc, &trb, 100 /* ms */)); +} + +static usb_error_t +xhci_cmd_reset_dev(struct xhci_softc *sc, uint8_t slot_id) +{ + struct xhci_trb trb; + uint32_t temp; + + DPRINTF("\n"); + + trb.qwTrb0 = 0; + trb.dwTrb2 = 0; + temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_RESET_DEVICE) | + XHCI_TRB_3_SLOT_SET(slot_id); + + trb.dwTrb3 = htole32(temp); + + return (xhci_do_command(sc, &trb, 100 /* ms */)); +} + +/*------------------------------------------------------------------------* + * xhci_interrupt - XHCI interrupt handler + *------------------------------------------------------------------------*/ +void +xhci_interrupt(struct xhci_softc *sc) +{ + uint32_t status; + uint32_t temp; + + USB_BUS_LOCK(&sc->sc_bus); + + status = XREAD4(sc, oper, XHCI_USBSTS); + + /* acknowledge interrupts */ + + XWRITE4(sc, oper, XHCI_USBSTS, status); + + temp = XREAD4(sc, runt, XHCI_IMAN(0)); + + /* acknowledge pending event */ + + XWRITE4(sc, runt, XHCI_IMAN(0), temp); + + DPRINTFN(16, "real interrupt (sts=0x%08x, " + "iman=0x%08x)\n", status, temp); + + if (status != 0) { + if (status & XHCI_STS_PCD) { + xhci_root_intr(sc); + } + + if (status & XHCI_STS_HCH) { + printf("%s: host controller halted\n", + __FUNCTION__); + } + + if (status & XHCI_STS_HSE) { + printf("%s: host system error\n", + __FUNCTION__); + } + + if (status & XHCI_STS_HCE) { + printf("%s: host controller error\n", + __FUNCTION__); + } + } + + xhci_interrupt_poll(sc); + + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/*------------------------------------------------------------------------* + * xhci_timeout - XHCI timeout handler + *------------------------------------------------------------------------*/ +static void +xhci_timeout(void *arg) +{ + struct usb_xfer *xfer = arg; + + DPRINTF("xfer=%p\n", xfer); + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* transfer is transferred */ + xhci_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +xhci_do_poll(struct usb_bus *bus) +{ + struct xhci_softc *sc = XHCI_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + xhci_interrupt_poll(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +xhci_setup_generic_chain_sub(struct xhci_std_temp *temp) +{ + struct usb_page_search buf_res; + struct xhci_td *td; + struct xhci_td *td_next; + struct xhci_td *td_alt_next; + uint32_t buf_offset; + uint32_t average; + uint32_t len_old; + uint32_t dword; + uint8_t shortpkt_old; + uint8_t precompute; + uint8_t x; + + td_alt_next = NULL; + buf_offset = 0; + shortpkt_old = temp->shortpkt; + len_old = temp->len; + precompute = 1; + +restart: + + td = temp->td; + td_next = temp->td_next; + + while (1) { + + if (temp->len == 0) { + + if (temp->shortpkt) + break; + + /* send a Zero Length Packet, ZLP, last */ + + temp->shortpkt = 1; + average = 0; + + } else { + + average = temp->average; + + if (temp->len < average) { + if (temp->len % temp->max_packet_size) { + temp->shortpkt = 1; + } + average = temp->len; + } + } + + if (td_next == NULL) + panic("%s: out of XHCI transfer descriptors!", __FUNCTION__); + + /* get next TD */ + + td = td_next; + td_next = td->obj_next; + + /* check if we are pre-computing */ + + if (precompute) { + + /* update remaining length */ + + temp->len -= average; + + continue; + } + /* fill out current TD */ + + td->len = average; + td->remainder = 0; + td->status = 0; + + /* update remaining length */ + + temp->len -= average; + + /* reset TRB index */ + + x = 0; + + if (temp->trb_type == XHCI_TRB_TYPE_SETUP_STAGE) { + /* immediate data */ + + if (average > 8) + average = 8; + + td->td_trb[0].qwTrb0 = 0; + + usbd_copy_out(temp->pc, temp->offset + buf_offset, + (uint8_t *)(uintptr_t)&td->td_trb[0].qwTrb0, + average); + + dword = XHCI_TRB_2_BYTES_SET(8) | + XHCI_TRB_2_TDSZ_SET(0) | + XHCI_TRB_2_IRQ_SET(0); + + td->td_trb[0].dwTrb2 = htole32(dword); + + dword = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_SETUP_STAGE) | + XHCI_TRB_3_IDT_BIT | XHCI_TRB_3_CYCLE_BIT; + + /* check wLength */ + if (td->td_trb[0].qwTrb0 & + htole64(XHCI_TRB_0_WLENGTH_MASK)) { + if (td->td_trb[0].qwTrb0 & htole64(1)) + dword |= XHCI_TRB_3_TRT_IN; + else + dword |= XHCI_TRB_3_TRT_OUT; + } + + td->td_trb[0].dwTrb3 = htole32(dword); +#ifdef USB_DEBUG + xhci_dump_trb(&td->td_trb[x]); +#endif + x++; + + } else do { + + uint32_t npkt; + + /* fill out buffer pointers */ + + if (average == 0) { + npkt = 1; + memset(&buf_res, 0, sizeof(buf_res)); + } else { + usbd_get_page(temp->pc, temp->offset + + buf_offset, &buf_res); + + /* get length to end of page */ + if (buf_res.length > average) + buf_res.length = average; + + /* check for maximum length */ + if (buf_res.length > XHCI_TD_PAGE_SIZE) + buf_res.length = XHCI_TD_PAGE_SIZE; + + /* setup npkt */ + npkt = (average + temp->max_packet_size - 1) / + temp->max_packet_size; + + if (npkt > 31) + npkt = 31; + } + + /* fill out TRB's */ + td->td_trb[x].qwTrb0 = + htole64((uint64_t)buf_res.physaddr); + + dword = + XHCI_TRB_2_BYTES_SET(buf_res.length) | + XHCI_TRB_2_TDSZ_SET(npkt) | + XHCI_TRB_2_IRQ_SET(0); + + td->td_trb[x].dwTrb2 = htole32(dword); + + dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT | + XHCI_TRB_3_TYPE_SET(temp->trb_type) | + XHCI_TRB_3_FRID_SET(temp->isoc_frame / 8) | + XHCI_TRB_3_TBC_SET(temp->tbc) | + XHCI_TRB_3_TLBPC_SET(temp->tlbpc); + + if (temp->direction == UE_DIR_IN) { + dword |= XHCI_TRB_3_DIR_IN; + + /* + * NOTE: Only the SETUP stage should + * use the IDT bit. Else transactions + * can be sent using the wrong data + * toggle value. + */ + if (temp->trb_type != + XHCI_TRB_TYPE_SETUP_STAGE && + temp->trb_type != + XHCI_TRB_TYPE_STATUS_STAGE) + dword |= XHCI_TRB_3_ISP_BIT; + } + + td->td_trb[x].dwTrb3 = htole32(dword); + + average -= buf_res.length; + buf_offset += buf_res.length; +#ifdef USB_DEBUG + xhci_dump_trb(&td->td_trb[x]); +#endif + x++; + + } while (average != 0); + + td->td_trb[x-1].dwTrb3 |= htole32(XHCI_TRB_3_IOC_BIT); + + /* store number of data TRB's */ + + td->ntrb = x; + + DPRINTF("NTRB=%u\n", x); + + /* fill out link TRB */ + + if (td_next != NULL) { + /* link the current TD with the next one */ + td->td_trb[x].qwTrb0 = htole64((uint64_t)td_next->td_self); + DPRINTF("LINK=0x%08llx\n", (long long)td_next->td_self); + } else { + /* this field will get updated later */ + DPRINTF("NOLINK\n"); + } + + dword = XHCI_TRB_2_IRQ_SET(0); + + td->td_trb[x].dwTrb2 = htole32(dword); + + dword = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK) | + XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_IOC_BIT; + + td->td_trb[x].dwTrb3 = htole32(dword); + + td->alt_next = td_alt_next; +#ifdef USB_DEBUG + xhci_dump_trb(&td->td_trb[x]); +#endif + usb_pc_cpu_flush(td->page_cache); + } + + if (precompute) { + precompute = 0; + + /* setup alt next pointer, if any */ + if (temp->last_frame) { + td_alt_next = NULL; + } else { + /* we use this field internally */ + td_alt_next = td_next; + } + + /* restore */ + temp->shortpkt = shortpkt_old; + temp->len = len_old; + goto restart; + } + + /* remove cycle bit from first if we are stepping the TRBs */ + if (temp->step_td) + td->td_trb[0].dwTrb3 &= ~htole32(XHCI_TRB_3_CYCLE_BIT); + + /* remove chain bit because this is the last TRB in the chain */ + td->td_trb[td->ntrb - 1].dwTrb2 &= ~htole32(XHCI_TRB_2_TDSZ_SET(15)); + td->td_trb[td->ntrb - 1].dwTrb3 &= ~htole32(XHCI_TRB_3_CHAIN_BIT); + + usb_pc_cpu_flush(td->page_cache); + + temp->td = td; + temp->td_next = td_next; +} + +static void +xhci_setup_generic_chain(struct usb_xfer *xfer) +{ + struct xhci_std_temp temp; + struct xhci_td *td; + uint32_t x; + uint32_t y; + uint8_t mult; + + temp.step_td = 0; + temp.tbc = 0; + temp.tlbpc = 0; + temp.average = xfer->max_hc_frame_size; + temp.max_packet_size = xfer->max_packet_size; + temp.sc = XHCI_BUS2SC(xfer->xroot->bus); + temp.pc = NULL; + temp.last_frame = 0; + temp.offset = 0; + temp.multishort = xfer->flags_int.isochronous_xfr || + xfer->flags_int.control_xfr || + xfer->flags_int.short_frames_ok; + + /* toggle the DMA set we are using */ + xfer->flags_int.curr_dma_set ^= 1; + + /* get next DMA set */ + td = xfer->td_start[xfer->flags_int.curr_dma_set]; + + temp.td = NULL; + temp.td_next = td; + + xfer->td_transfer_first = td; + xfer->td_transfer_cache = td; + + if (xfer->flags_int.isochronous_xfr) { + uint8_t shift; + + /* compute multiplier for ISOCHRONOUS transfers */ + mult = xfer->endpoint->ecomp ? + (xfer->endpoint->ecomp->bmAttributes & 3) : 0; + /* check for USB 2.0 multiplier */ + if (mult == 0) { + mult = (xfer->endpoint->edesc-> + wMaxPacketSize[1] >> 3) & 3; + } + /* range check */ + if (mult > 2) + mult = 3; + else + mult++; + + x = XREAD4(temp.sc, runt, XHCI_MFINDEX); + + DPRINTF("MFINDEX=0x%08x\n", x); + + switch (usbd_get_speed(xfer->xroot->udev)) { + case USB_SPEED_FULL: + shift = 3; + temp.isoc_delta = 8; /* 1ms */ + x += temp.isoc_delta - 1; + x &= ~(temp.isoc_delta - 1); + break; + default: + shift = usbd_xfer_get_fps_shift(xfer); + temp.isoc_delta = 1U << shift; + x += temp.isoc_delta - 1; + x &= ~(temp.isoc_delta - 1); + /* simple frame load balancing */ + x += xfer->endpoint->usb_uframe; + break; + } + + y = XHCI_MFINDEX_GET(x - xfer->endpoint->isoc_next); + + if ((xfer->endpoint->is_synced == 0) || + (y < (xfer->nframes << shift)) || + (XHCI_MFINDEX_GET(-y) >= (128 * 8))) { + /* + * If there is data underflow or the pipe + * queue is empty we schedule the transfer a + * few frames ahead of the current frame + * position. Else two isochronous transfers + * might overlap. + */ + xfer->endpoint->isoc_next = XHCI_MFINDEX_GET(x + (3 * 8)); + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + + /* compute isochronous completion time */ + + y = XHCI_MFINDEX_GET(xfer->endpoint->isoc_next - (x & ~7)); + + xfer->isoc_time_complete = + usb_isoc_time_expand(&temp.sc->sc_bus, x / 8) + + (y / 8) + (((xfer->nframes << shift) + 7) / 8); + + x = 0; + temp.isoc_frame = xfer->endpoint->isoc_next; + temp.trb_type = XHCI_TRB_TYPE_ISOCH; + + xfer->endpoint->isoc_next += xfer->nframes << shift; + + } else if (xfer->flags_int.control_xfr) { + + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_hdr) { + + temp.len = xfer->frlengths[0]; + temp.pc = xfer->frbuffers + 0; + temp.shortpkt = temp.len ? 1 : 0; + temp.trb_type = XHCI_TRB_TYPE_SETUP_STAGE; + temp.direction = 0; + + /* check for last frame */ + if (xfer->nframes == 1) { + /* no STATUS stage yet, SETUP is last */ + if (xfer->flags_int.control_act) + temp.last_frame = 1; + } + + xhci_setup_generic_chain_sub(&temp); + } + x = 1; + mult = 1; + temp.isoc_delta = 0; + temp.isoc_frame = 0; + temp.trb_type = XHCI_TRB_TYPE_DATA_STAGE; + } else { + x = 0; + mult = 1; + temp.isoc_delta = 0; + temp.isoc_frame = 0; + temp.trb_type = XHCI_TRB_TYPE_NORMAL; + } + + if (x != xfer->nframes) { + /* setup page_cache pointer */ + temp.pc = xfer->frbuffers + x; + /* set endpoint direction */ + temp.direction = UE_GET_DIR(xfer->endpointno); + } + + while (x != xfer->nframes) { + + /* DATA0 / DATA1 message */ + + temp.len = xfer->frlengths[x]; + temp.step_td = ((xfer->endpointno & UE_DIR_IN) && + x != 0 && temp.multishort == 0); + + x++; + + if (x == xfer->nframes) { + if (xfer->flags_int.control_xfr) { + /* no STATUS stage yet, DATA is last */ + if (xfer->flags_int.control_act) + temp.last_frame = 1; + } else { + temp.last_frame = 1; + } + } + if (temp.len == 0) { + + /* make sure that we send an USB packet */ + + temp.shortpkt = 0; + + temp.tbc = 0; + temp.tlbpc = mult - 1; + + } else if (xfer->flags_int.isochronous_xfr) { + + uint8_t tdpc; + + /* isochronous transfers don't have short packet termination */ + + temp.shortpkt = 1; + + /* isochronous transfers have a transfer limit */ + + if (temp.len > xfer->max_frame_size) + temp.len = xfer->max_frame_size; + + /* compute TD packet count */ + tdpc = (temp.len + xfer->max_packet_size - 1) / + xfer->max_packet_size; + + temp.tbc = ((tdpc + mult - 1) / mult) - 1; + temp.tlbpc = (tdpc % mult); + + if (temp.tlbpc == 0) + temp.tlbpc = mult - 1; + else + temp.tlbpc--; + } else { + + /* regular data transfer */ + + temp.shortpkt = xfer->flags.force_short_xfer ? 0 : 1; + } + + xhci_setup_generic_chain_sub(&temp); + + if (xfer->flags_int.isochronous_xfr) { + temp.offset += xfer->frlengths[x - 1]; + temp.isoc_frame += temp.isoc_delta; + } else { + /* get next Page Cache pointer */ + temp.pc = xfer->frbuffers + x; + } + } + + /* check if we should append a status stage */ + + if (xfer->flags_int.control_xfr && + !xfer->flags_int.control_act) { + + /* + * Send a DATA1 message and invert the current + * endpoint direction. + */ + temp.step_td = (xfer->nframes != 0); + temp.direction = UE_GET_DIR(xfer->endpointno) ^ UE_DIR_IN; + temp.len = 0; + temp.pc = NULL; + temp.shortpkt = 0; + temp.last_frame = 1; + temp.trb_type = XHCI_TRB_TYPE_STATUS_STAGE; + + xhci_setup_generic_chain_sub(&temp); + } + + td = temp.td; + + /* must have at least one frame! */ + + xfer->td_transfer_last = td; + + DPRINTF("first=%p last=%p\n", xfer->td_transfer_first, td); +} + +static void +xhci_set_slot_pointer(struct xhci_softc *sc, uint8_t index, uint64_t dev_addr) +{ + struct usb_page_search buf_res; + struct xhci_dev_ctx_addr *pdctxa; + + usbd_get_page(&sc->sc_hw.ctx_pc, 0, &buf_res); + + pdctxa = buf_res.buffer; + + DPRINTF("addr[%u]=0x%016llx\n", index, (long long)dev_addr); + + pdctxa->qwBaaDevCtxAddr[index] = htole64(dev_addr); + + usb_pc_cpu_flush(&sc->sc_hw.ctx_pc); +} + +static usb_error_t +xhci_configure_mask(struct usb_device *udev, uint32_t mask, uint8_t drop) +{ + struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); + struct usb_page_search buf_inp; + struct xhci_input_dev_ctx *pinp; + uint8_t index; + + index = udev->controller_slot_id; + + usbd_get_page(&sc->sc_hw.devs[index].input_pc, 0, &buf_inp); + + pinp = buf_inp.buffer; + + if (drop) { + mask &= XHCI_INCTX_NON_CTRL_MASK; + xhci_ctx_set_le32(sc, &pinp->ctx_input.dwInCtx0, mask); + xhci_ctx_set_le32(sc, &pinp->ctx_input.dwInCtx1, 0); + } else { + xhci_ctx_set_le32(sc, &pinp->ctx_input.dwInCtx0, 0); + xhci_ctx_set_le32(sc, &pinp->ctx_input.dwInCtx1, mask); + } + return (0); +} + +static usb_error_t +xhci_configure_endpoint(struct usb_device *udev, + struct usb_endpoint_descriptor *edesc, uint64_t ring_addr, + uint16_t interval, uint8_t max_packet_count, uint8_t mult, + uint8_t fps_shift, uint16_t max_packet_size, uint16_t max_frame_size) +{ + struct usb_page_search buf_inp; + struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); + struct xhci_input_dev_ctx *pinp; + uint32_t temp; + uint8_t index; + uint8_t epno; + uint8_t type; + + index = udev->controller_slot_id; + + usbd_get_page(&sc->sc_hw.devs[index].input_pc, 0, &buf_inp); + + pinp = buf_inp.buffer; + + epno = edesc->bEndpointAddress; + type = edesc->bmAttributes & UE_XFERTYPE; + + if (type == UE_CONTROL) + epno |= UE_DIR_IN; + + epno = XHCI_EPNO2EPID(epno); + + if (epno == 0) + return (USB_ERR_NO_PIPE); /* invalid */ + + if (max_packet_count == 0) + return (USB_ERR_BAD_BUFSIZE); + + max_packet_count--; + + if (mult == 0) + return (USB_ERR_BAD_BUFSIZE); + + temp = XHCI_EPCTX_0_EPSTATE_SET(0) | + XHCI_EPCTX_0_MAXP_STREAMS_SET(0) | + XHCI_EPCTX_0_LSA_SET(0); + + switch (udev->speed) { + case USB_SPEED_FULL: + case USB_SPEED_LOW: + /* 1ms -> 125us */ + fps_shift += 3; + break; + default: + break; + } + + switch (type) { + case UE_INTERRUPT: + if (fps_shift > 3) + fps_shift--; + temp |= XHCI_EPCTX_0_IVAL_SET(fps_shift); + break; + case UE_ISOCHRONOUS: + temp |= XHCI_EPCTX_0_IVAL_SET(fps_shift); + + switch (udev->speed) { + case USB_SPEED_SUPER: + if (mult > 3) + mult = 3; + temp |= XHCI_EPCTX_0_MULT_SET(mult - 1); + max_packet_count /= mult; + break; + default: + break; + } + break; + default: + break; + } + + xhci_ctx_set_le32(sc, &pinp->ctx_ep[epno - 1].dwEpCtx0, temp); + + temp = + XHCI_EPCTX_1_HID_SET(0) | + XHCI_EPCTX_1_MAXB_SET(max_packet_count) | + XHCI_EPCTX_1_MAXP_SIZE_SET(max_packet_size); + + if ((udev->parent_hs_hub != NULL) || (udev->address != 0)) { + if (type != UE_ISOCHRONOUS) + temp |= XHCI_EPCTX_1_CERR_SET(3); + } + + switch (type) { + case UE_CONTROL: + temp |= XHCI_EPCTX_1_EPTYPE_SET(4); + break; + case UE_ISOCHRONOUS: + temp |= XHCI_EPCTX_1_EPTYPE_SET(1); + break; + case UE_BULK: + temp |= XHCI_EPCTX_1_EPTYPE_SET(2); + break; + default: + temp |= XHCI_EPCTX_1_EPTYPE_SET(3); + break; + } + + /* check for IN direction */ + if (epno & 1) + temp |= XHCI_EPCTX_1_EPTYPE_SET(4); + + xhci_ctx_set_le32(sc, &pinp->ctx_ep[epno - 1].dwEpCtx1, temp); + + ring_addr |= XHCI_EPCTX_2_DCS_SET(1); + + xhci_ctx_set_le64(sc, &pinp->ctx_ep[epno - 1].qwEpCtx2, ring_addr); + + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + case UE_ISOCHRONOUS: + temp = XHCI_EPCTX_4_MAX_ESIT_PAYLOAD_SET(max_frame_size) | + XHCI_EPCTX_4_AVG_TRB_LEN_SET(MIN(XHCI_PAGE_SIZE, + max_frame_size)); + break; + case UE_CONTROL: + temp = XHCI_EPCTX_4_AVG_TRB_LEN_SET(8); + break; + default: + temp = XHCI_EPCTX_4_AVG_TRB_LEN_SET(XHCI_PAGE_SIZE); + break; + } + + xhci_ctx_set_le32(sc, &pinp->ctx_ep[epno - 1].dwEpCtx4, temp); + +#ifdef USB_DEBUG + xhci_dump_endpoint(sc, &pinp->ctx_ep[epno - 1]); +#endif + usb_pc_cpu_flush(&sc->sc_hw.devs[index].input_pc); + + return (0); /* success */ +} + +static usb_error_t +xhci_configure_endpoint_by_xfer(struct usb_xfer *xfer) +{ + struct xhci_endpoint_ext *pepext; + struct usb_endpoint_ss_comp_descriptor *ecomp; + + pepext = xhci_get_endpoint_ext(xfer->xroot->udev, + xfer->endpoint->edesc); + + ecomp = xfer->endpoint->ecomp; + + pepext->trb[0].dwTrb3 = 0; /* halt any transfers */ + usb_pc_cpu_flush(pepext->page_cache); + + return (xhci_configure_endpoint(xfer->xroot->udev, + xfer->endpoint->edesc, pepext->physaddr, + xfer->interval, xfer->max_packet_count, + (ecomp != NULL) ? (ecomp->bmAttributes & 3) + 1 : 1, + usbd_xfer_get_fps_shift(xfer), xfer->max_packet_size, + xfer->max_frame_size)); +} + +static usb_error_t +xhci_configure_device(struct usb_device *udev) +{ + struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); + struct usb_page_search buf_inp; + struct usb_page_cache *pcinp; + struct xhci_input_dev_ctx *pinp; + struct usb_device *hubdev; + uint32_t temp; + uint32_t route; + uint32_t rh_port; + uint8_t is_hub; + uint8_t index; + uint8_t depth; + + index = udev->controller_slot_id; + + DPRINTF("index=%u\n", index); + + pcinp = &sc->sc_hw.devs[index].input_pc; + + usbd_get_page(pcinp, 0, &buf_inp); + + pinp = buf_inp.buffer; + + rh_port = 0; + route = 0; + + /* figure out route string and root HUB port number */ + + for (hubdev = udev; hubdev != NULL; hubdev = hubdev->parent_hub) { + + if (hubdev->parent_hub == NULL) + break; + + depth = hubdev->parent_hub->depth; + + /* + * NOTE: HS/FS/LS devices and the SS root HUB can have + * more than 15 ports + */ + + rh_port = hubdev->port_no; + + if (depth == 0) + break; + + if (rh_port > 15) + rh_port = 15; + + if (depth < 6) + route |= rh_port << (4 * (depth - 1)); + } + + DPRINTF("Route=0x%08x\n", route); + + temp = XHCI_SCTX_0_ROUTE_SET(route); + + switch (sc->sc_hw.devs[index].state) { + case XHCI_ST_CONFIGURED: + temp |= XHCI_SCTX_0_CTX_NUM_SET(XHCI_MAX_ENDPOINTS - 1); + break; + default: + temp |= XHCI_SCTX_0_CTX_NUM_SET(1); + break; + } + + switch (udev->speed) { + case USB_SPEED_LOW: + temp |= XHCI_SCTX_0_SPEED_SET(2); + break; + case USB_SPEED_HIGH: + temp |= XHCI_SCTX_0_SPEED_SET(3); + break; + case USB_SPEED_FULL: + temp |= XHCI_SCTX_0_SPEED_SET(1); + break; + default: + temp |= XHCI_SCTX_0_SPEED_SET(4); + break; + } + + is_hub = sc->sc_hw.devs[index].nports != 0 && + (udev->speed == USB_SPEED_SUPER || + udev->speed == USB_SPEED_HIGH); + + if (is_hub) { + temp |= XHCI_SCTX_0_HUB_SET(1); +#if 0 + if (udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) { + DPRINTF("HUB supports MTT\n"); + temp |= XHCI_SCTX_0_MTT_SET(1); + } +#endif + } + + xhci_ctx_set_le32(sc, &pinp->ctx_slot.dwSctx0, temp); + + temp = XHCI_SCTX_1_RH_PORT_SET(rh_port); + + if (is_hub) { + temp |= XHCI_SCTX_1_NUM_PORTS_SET( + sc->sc_hw.devs[index].nports); + } + + switch (udev->speed) { + case USB_SPEED_SUPER: + switch (sc->sc_hw.devs[index].state) { + case XHCI_ST_ADDRESSED: + case XHCI_ST_CONFIGURED: + /* enable power save */ + temp |= XHCI_SCTX_1_MAX_EL_SET(sc->sc_exit_lat_max); + break; + default: + /* disable power save */ + break; + } + break; + default: + break; + } + + xhci_ctx_set_le32(sc, &pinp->ctx_slot.dwSctx1, temp); + + temp = XHCI_SCTX_2_IRQ_TARGET_SET(0); + + if (is_hub) + temp |= XHCI_SCTX_2_TT_THINK_TIME_SET(sc->sc_hw.devs[index].tt); + + hubdev = udev->parent_hs_hub; + + /* check if we should activate the transaction translator */ + switch (udev->speed) { + case USB_SPEED_FULL: + case USB_SPEED_LOW: + if (hubdev != NULL) { + temp |= XHCI_SCTX_2_TT_HUB_SID_SET( + hubdev->controller_slot_id); + temp |= XHCI_SCTX_2_TT_PORT_NUM_SET( + udev->hs_port_no); + } + break; + default: + break; + } + + xhci_ctx_set_le32(sc, &pinp->ctx_slot.dwSctx2, temp); + + temp = XHCI_SCTX_3_DEV_ADDR_SET(udev->address) | + XHCI_SCTX_3_SLOT_STATE_SET(0); + + xhci_ctx_set_le32(sc, &pinp->ctx_slot.dwSctx3, temp); + +#ifdef USB_DEBUG + xhci_dump_device(sc, &pinp->ctx_slot); +#endif + usb_pc_cpu_flush(pcinp); + + return (0); /* success */ +} + +static usb_error_t +xhci_alloc_device_ext(struct usb_device *udev) +{ + struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); + struct usb_page_search buf_dev; + struct usb_page_search buf_ep; + struct xhci_trb *trb; + struct usb_page_cache *pc; + struct usb_page *pg; + uint64_t addr; + uint8_t index; + uint8_t i; + + index = udev->controller_slot_id; + + pc = &sc->sc_hw.devs[index].device_pc; + pg = &sc->sc_hw.devs[index].device_pg; + + /* need to initialize the page cache */ + pc->tag_parent = sc->sc_bus.dma_parent_tag; + + if (usb_pc_alloc_mem(pc, pg, sc->sc_ctx_is_64_byte ? + (2 * sizeof(struct xhci_dev_ctx)) : + sizeof(struct xhci_dev_ctx), XHCI_PAGE_SIZE)) + goto error; + + usbd_get_page(pc, 0, &buf_dev); + + pc = &sc->sc_hw.devs[index].input_pc; + pg = &sc->sc_hw.devs[index].input_pg; + + /* need to initialize the page cache */ + pc->tag_parent = sc->sc_bus.dma_parent_tag; + + if (usb_pc_alloc_mem(pc, pg, sc->sc_ctx_is_64_byte ? + (2 * sizeof(struct xhci_input_dev_ctx)) : + sizeof(struct xhci_input_dev_ctx), XHCI_PAGE_SIZE)) + goto error; + + pc = &sc->sc_hw.devs[index].endpoint_pc; + pg = &sc->sc_hw.devs[index].endpoint_pg; + + /* need to initialize the page cache */ + pc->tag_parent = sc->sc_bus.dma_parent_tag; + + if (usb_pc_alloc_mem(pc, pg, sizeof(struct xhci_dev_endpoint_trbs), XHCI_PAGE_SIZE)) + goto error; + + /* initialise all endpoint LINK TRBs */ + + for (i = 0; i != XHCI_MAX_ENDPOINTS; i++) { + + /* lookup endpoint TRB ring */ + usbd_get_page(pc, (uintptr_t)&((struct xhci_dev_endpoint_trbs *)0)->trb[i][0], &buf_ep); + + /* get TRB pointer */ + trb = buf_ep.buffer; + trb += XHCI_MAX_TRANSFERS - 1; + + /* get TRB start address */ + addr = buf_ep.physaddr; + + /* create LINK TRB */ + trb->qwTrb0 = htole64(addr); + trb->dwTrb2 = htole32(XHCI_TRB_2_IRQ_SET(0)); + trb->dwTrb3 = htole32(XHCI_TRB_3_CYCLE_BIT | + XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK)); + } + + usb_pc_cpu_flush(pc); + + xhci_set_slot_pointer(sc, index, buf_dev.physaddr); + + return (0); + +error: + xhci_free_device_ext(udev); + + return (USB_ERR_NOMEM); +} + +static void +xhci_free_device_ext(struct usb_device *udev) +{ + struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); + uint8_t index; + + index = udev->controller_slot_id; + xhci_set_slot_pointer(sc, index, 0); + + usb_pc_free_mem(&sc->sc_hw.devs[index].device_pc); + usb_pc_free_mem(&sc->sc_hw.devs[index].input_pc); + usb_pc_free_mem(&sc->sc_hw.devs[index].endpoint_pc); +} + +static struct xhci_endpoint_ext * +xhci_get_endpoint_ext(struct usb_device *udev, struct usb_endpoint_descriptor *edesc) +{ + struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); + struct xhci_endpoint_ext *pepext; + struct usb_page_cache *pc; + struct usb_page_search buf_ep; + uint8_t epno; + uint8_t index; + + epno = edesc->bEndpointAddress; + if ((edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) + epno |= UE_DIR_IN; + + epno = XHCI_EPNO2EPID(epno); + + index = udev->controller_slot_id; + + pc = &sc->sc_hw.devs[index].endpoint_pc; + + usbd_get_page(pc, (uintptr_t)&((struct xhci_dev_endpoint_trbs *)0)->trb[epno][0], &buf_ep); + + pepext = &sc->sc_hw.devs[index].endp[epno]; + pepext->page_cache = pc; + pepext->trb = buf_ep.buffer; + pepext->physaddr = buf_ep.physaddr; + + return (pepext); +} + +static void +xhci_endpoint_doorbell(struct usb_xfer *xfer) +{ + struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus); + uint8_t epno; + uint8_t index; + + epno = xfer->endpointno; + if (xfer->flags_int.control_xfr) + epno |= UE_DIR_IN; + + epno = XHCI_EPNO2EPID(epno); + index = xfer->xroot->udev->controller_slot_id; + + if (xfer->xroot->udev->flags.self_suspended == 0) + XWRITE4(sc, door, XHCI_DOORBELL(index), epno | XHCI_DB_SID_SET(0)); +} + +static void +xhci_transfer_remove(struct usb_xfer *xfer, usb_error_t error) +{ + struct xhci_endpoint_ext *pepext; + + if (xfer->flags_int.bandwidth_reclaimed) { + xfer->flags_int.bandwidth_reclaimed = 0; + + pepext = xhci_get_endpoint_ext(xfer->xroot->udev, + xfer->endpoint->edesc); + + pepext->trb_used--; + + pepext->xfer[xfer->qh_pos] = NULL; + + if (error && pepext->trb_running != 0) { + pepext->trb_halted = 1; + pepext->trb_running = 0; + } + } +} + +static usb_error_t +xhci_transfer_insert(struct usb_xfer *xfer) +{ + struct xhci_td *td_first; + struct xhci_td *td_last; + struct xhci_endpoint_ext *pepext; + uint64_t addr; + uint8_t i; + uint8_t inext; + uint8_t trb_limit; + + DPRINTFN(8, "\n"); + + /* check if already inserted */ + if (xfer->flags_int.bandwidth_reclaimed) { + DPRINTFN(8, "Already in schedule\n"); + return (0); + } + + pepext = xhci_get_endpoint_ext(xfer->xroot->udev, + xfer->endpoint->edesc); + + td_first = xfer->td_transfer_first; + td_last = xfer->td_transfer_last; + addr = pepext->physaddr; + + switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + case UE_INTERRUPT: + /* single buffered */ + trb_limit = 1; + break; + default: + /* multi buffered */ + trb_limit = (XHCI_MAX_TRANSFERS - 2); + break; + } + + if (pepext->trb_used >= trb_limit) { + DPRINTFN(8, "Too many TDs queued.\n"); + return (USB_ERR_NOMEM); + } + + /* check for stopped condition, after putting transfer on interrupt queue */ + if (pepext->trb_running == 0) { + struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus); + + DPRINTFN(8, "Not running\n"); + + /* start configuration */ + (void)usb_proc_msignal(&sc->sc_config_proc, + &sc->sc_config_msg[0], &sc->sc_config_msg[1]); + return (0); + } + + pepext->trb_used++; + + /* get current TRB index */ + i = pepext->trb_index; + + /* get next TRB index */ + inext = (i + 1); + + /* the last entry of the ring is a hardcoded link TRB */ + if (inext >= (XHCI_MAX_TRANSFERS - 1)) + inext = 0; + + /* compute terminating return address */ + addr += inext * sizeof(struct xhci_trb); + + /* update next pointer of last link TRB */ + td_last->td_trb[td_last->ntrb].qwTrb0 = htole64(addr); + td_last->td_trb[td_last->ntrb].dwTrb2 = htole32(XHCI_TRB_2_IRQ_SET(0)); + td_last->td_trb[td_last->ntrb].dwTrb3 = htole32(XHCI_TRB_3_IOC_BIT | + XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK)); + +#ifdef USB_DEBUG + xhci_dump_trb(&td_last->td_trb[td_last->ntrb]); +#endif + usb_pc_cpu_flush(td_last->page_cache); + + /* write ahead chain end marker */ + + pepext->trb[inext].qwTrb0 = 0; + pepext->trb[inext].dwTrb2 = 0; + pepext->trb[inext].dwTrb3 = 0; + + /* update next pointer of link TRB */ + + pepext->trb[i].qwTrb0 = htole64((uint64_t)td_first->td_self); + pepext->trb[i].dwTrb2 = htole32(XHCI_TRB_2_IRQ_SET(0)); + +#ifdef USB_DEBUG + xhci_dump_trb(&pepext->trb[i]); +#endif + usb_pc_cpu_flush(pepext->page_cache); + + /* toggle cycle bit which activates the transfer chain */ + + pepext->trb[i].dwTrb3 = htole32(XHCI_TRB_3_CYCLE_BIT | + XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK)); + + usb_pc_cpu_flush(pepext->page_cache); + + DPRINTF("qh_pos = %u\n", i); + + pepext->xfer[i] = xfer; + + xfer->qh_pos = i; + + xfer->flags_int.bandwidth_reclaimed = 1; + + pepext->trb_index = inext; + + xhci_endpoint_doorbell(xfer); + + return (0); +} + +static void +xhci_root_intr(struct xhci_softc *sc) +{ + uint16_t i; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* clear any old interrupt data */ + memset(sc->sc_hub_idata, 0, sizeof(sc->sc_hub_idata)); + + for (i = 1; i <= sc->sc_noport; i++) { + /* pick out CHANGE bits from the status register */ + if (XREAD4(sc, oper, XHCI_PORTSC(i)) & ( + XHCI_PS_CSC | XHCI_PS_PEC | + XHCI_PS_OCC | XHCI_PS_WRC | + XHCI_PS_PRC | XHCI_PS_PLC | + XHCI_PS_CEC)) { + sc->sc_hub_idata[i / 8] |= 1 << (i % 8); + DPRINTF("port %d changed\n", i); + } + } + uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, + sizeof(sc->sc_hub_idata)); +} + +/*------------------------------------------------------------------------* + * xhci_device_done - XHCI done handler + * + * NOTE: This function can be called two times in a row on + * the same USB transfer. From close and from interrupt. + *------------------------------------------------------------------------*/ +static void +xhci_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); + + /* remove transfer from HW queue */ + xhci_transfer_remove(xfer, error); + + /* dequeue transfer and start next transfer */ + usbd_transfer_done(xfer, error); +} + +/*------------------------------------------------------------------------* + * XHCI data transfer support (generic type) + *------------------------------------------------------------------------*/ +static void +xhci_device_generic_open(struct usb_xfer *xfer) +{ + if (xfer->flags_int.isochronous_xfr) { + switch (xfer->xroot->udev->speed) { + case USB_SPEED_FULL: + break; + default: + usb_hs_bandwidth_alloc(xfer); + break; + } + } +} + +static void +xhci_device_generic_close(struct usb_xfer *xfer) +{ + DPRINTF("\n"); + + xhci_device_done(xfer, USB_ERR_CANCELLED); + + if (xfer->flags_int.isochronous_xfr) { + switch (xfer->xroot->udev->speed) { + case USB_SPEED_FULL: + break; + default: + usb_hs_bandwidth_free(xfer); + break; + } + } +} + +static void +xhci_device_generic_multi_enter(struct usb_endpoint *ep, + struct usb_xfer *enter_xfer) +{ + struct usb_xfer *xfer; + + /* check if there is a current transfer */ + xfer = ep->endpoint_q.curr; + if (xfer == NULL) + return; + + /* + * Check if the current transfer is started and then pickup + * the next one, if any. Else wait for next start event due to + * block on failure feature. + */ + if (!xfer->flags_int.bandwidth_reclaimed) + return; + + xfer = TAILQ_FIRST(&ep->endpoint_q.head); + if (xfer == NULL) { + /* + * In case of enter we have to consider that the + * transfer is queued by the USB core after the enter + * method is called. + */ + xfer = enter_xfer; + + if (xfer == NULL) + return; + } + + /* try to multi buffer */ + xhci_transfer_insert(xfer); +} + +static void +xhci_device_generic_enter(struct usb_xfer *xfer) +{ + DPRINTF("\n"); + + /* setup TD's and QH */ + xhci_setup_generic_chain(xfer); + + xhci_device_generic_multi_enter(xfer->endpoint, xfer); +} + +static void +xhci_device_generic_start(struct usb_xfer *xfer) +{ + DPRINTF("\n"); + + /* try to insert xfer on HW queue */ + xhci_transfer_insert(xfer); + + /* try to multi buffer */ + xhci_device_generic_multi_enter(xfer->endpoint, NULL); + + /* add transfer last on interrupt queue */ + usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); + + /* start timeout, if any */ + if (xfer->timeout != 0) + usbd_transfer_timeout_ms(xfer, &xhci_timeout, xfer->timeout); +} + +struct usb_pipe_methods xhci_device_generic_methods = +{ + .open = xhci_device_generic_open, + .close = xhci_device_generic_close, + .enter = xhci_device_generic_enter, + .start = xhci_device_generic_start, +}; + +/*------------------------------------------------------------------------* + * xhci root HUB support + *------------------------------------------------------------------------* + * Simulate a hardware HUB by handling all the necessary requests. + *------------------------------------------------------------------------*/ + +#define HSETW(ptr, val) ptr[0] = (uint8_t)(val), ptr[1] = (uint8_t)((val) >> 8) + +static const +struct usb_device_descriptor xhci_devd = +{ + .bLength = sizeof(xhci_devd), + .bDescriptorType = UDESC_DEVICE, /* type */ + HSETW(.bcdUSB, 0x0300), /* USB version */ + .bDeviceClass = UDCLASS_HUB, /* class */ + .bDeviceSubClass = UDSUBCLASS_HUB, /* subclass */ + .bDeviceProtocol = UDPROTO_SSHUB, /* protocol */ + .bMaxPacketSize = 9, /* max packet size */ + HSETW(.idVendor, 0x0000), /* vendor */ + HSETW(.idProduct, 0x0000), /* product */ + HSETW(.bcdDevice, 0x0100), /* device version */ + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 0, + .bNumConfigurations = 1, /* # of configurations */ +}; + +static const +struct xhci_bos_desc xhci_bosd = { + .bosd = { + .bLength = sizeof(xhci_bosd.bosd), + .bDescriptorType = UDESC_BOS, + HSETW(.wTotalLength, sizeof(xhci_bosd)), + .bNumDeviceCaps = 3, + }, + .usb2extd = { + .bLength = sizeof(xhci_bosd.usb2extd), + .bDescriptorType = 1, + .bDevCapabilityType = 2, + .bmAttributes[0] = 2, + }, + .usbdcd = { + .bLength = sizeof(xhci_bosd.usbdcd), + .bDescriptorType = UDESC_DEVICE_CAPABILITY, + .bDevCapabilityType = 3, + .bmAttributes = 0, /* XXX */ + HSETW(.wSpeedsSupported, 0x000C), + .bFunctionalitySupport = 8, + .bU1DevExitLat = 255, /* dummy - not used */ + .wU2DevExitLat[0] = 0x00, + .wU2DevExitLat[1] = 0x08, + }, + .cidd = { + .bLength = sizeof(xhci_bosd.cidd), + .bDescriptorType = 1, + .bDevCapabilityType = 4, + .bReserved = 0, + .bContainerID = 0, /* XXX */ + }, +}; + +static const +struct xhci_config_desc xhci_confd = { + .confd = { + .bLength = sizeof(xhci_confd.confd), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(xhci_confd), + .bNumInterface = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = UC_SELF_POWERED, + .bMaxPower = 0 /* max power */ + }, + .ifcd = { + .bLength = sizeof(xhci_confd.ifcd), + .bDescriptorType = UDESC_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = UICLASS_HUB, + .bInterfaceSubClass = UISUBCLASS_HUB, + .bInterfaceProtocol = 0, + }, + .endpd = { + .bLength = sizeof(xhci_confd.endpd), + .bDescriptorType = UDESC_ENDPOINT, + .bEndpointAddress = UE_DIR_IN | XHCI_INTR_ENDPT, + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 2, /* max 15 ports */ + .bInterval = 255, + }, + .endpcd = { + .bLength = sizeof(xhci_confd.endpcd), + .bDescriptorType = UDESC_ENDPOINT_SS_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + }, +}; + +static const +struct usb_hub_ss_descriptor xhci_hubd = { + .bLength = sizeof(xhci_hubd), + .bDescriptorType = UDESC_SS_HUB, +}; + +static usb_error_t +xhci_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); + const char *str_ptr; + const void *ptr; + uint32_t port; + uint32_t v; + uint16_t len; + uint16_t i; + uint16_t value; + uint16_t index; + uint8_t j; + usb_error_t err; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* buffer reset */ + ptr = (const void *)&sc->sc_hub_desc; + len = 0; + err = 0; + + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " + "wValue=0x%04x wIndex=0x%04x\n", + req->bmRequestType, req->bRequest, + UGETW(req->wLength), value, index); + +#define C(x,y) ((x) | ((y) << 8)) + switch (C(req->bRequest, req->bmRequestType)) { + case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): + case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): + case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): + /* + * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops + * for the integrated root hub. + */ + break; + case C(UR_GET_CONFIG, UT_READ_DEVICE): + len = 1; + sc->sc_hub_desc.temp[0] = sc->sc_conf; + break; + case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): + switch (value >> 8) { + case UDESC_DEVICE: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(xhci_devd); + ptr = (const void *)&xhci_devd; + break; + + case UDESC_BOS: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(xhci_bosd); + ptr = (const void *)&xhci_bosd; + break; + + case UDESC_CONFIG: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(xhci_confd); + ptr = (const void *)&xhci_confd; + break; + + case UDESC_STRING: + switch (value & 0xff) { + case 0: /* Language table */ + str_ptr = "\001"; + break; + + case 1: /* Vendor */ + str_ptr = sc->sc_vendor; + break; + + case 2: /* Product */ + str_ptr = "XHCI root HUB"; + break; + + default: + str_ptr = ""; + break; + } + + len = usb_make_str_desc( + sc->sc_hub_desc.temp, + sizeof(sc->sc_hub_desc.temp), + str_ptr); + break; + + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_INTERFACE, UT_READ_INTERFACE): + len = 1; + sc->sc_hub_desc.temp[0] = 0; + break; + case C(UR_GET_STATUS, UT_READ_DEVICE): + len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED); + break; + case C(UR_GET_STATUS, UT_READ_INTERFACE): + case C(UR_GET_STATUS, UT_READ_ENDPOINT): + len = 2; + USETW(sc->sc_hub_desc.stat.wStatus, 0); + break; + case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): + if (value >= XHCI_MAX_DEVICES) { + err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_SET_CONFIG, UT_WRITE_DEVICE): + if (value != 0 && value != 1) { + err = USB_ERR_IOERROR; + goto done; + } + sc->sc_conf = value; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_DEVICE): + case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): + case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): + err = USB_ERR_IOERROR; + goto done; + case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + break; + case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): + break; + /* Hub requests */ + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): + DPRINTFN(9, "UR_CLEAR_PORT_FEATURE\n"); + + if ((index < 1) || + (index > sc->sc_noport)) { + err = USB_ERR_IOERROR; + goto done; + } + port = XHCI_PORTSC(index); + + v = XREAD4(sc, oper, port); + i = XHCI_PS_PLS_GET(v); + v &= ~XHCI_PS_CLEAR; + + switch (value) { + case UHF_C_BH_PORT_RESET: + XWRITE4(sc, oper, port, v | XHCI_PS_WRC); + break; + case UHF_C_PORT_CONFIG_ERROR: + XWRITE4(sc, oper, port, v | XHCI_PS_CEC); + break; + case UHF_C_PORT_SUSPEND: + case UHF_C_PORT_LINK_STATE: + XWRITE4(sc, oper, port, v | XHCI_PS_PLC); + break; + case UHF_C_PORT_CONNECTION: + XWRITE4(sc, oper, port, v | XHCI_PS_CSC); + break; + case UHF_C_PORT_ENABLE: + XWRITE4(sc, oper, port, v | XHCI_PS_PEC); + break; + case UHF_C_PORT_OVER_CURRENT: + XWRITE4(sc, oper, port, v | XHCI_PS_OCC); + break; + case UHF_C_PORT_RESET: + XWRITE4(sc, oper, port, v | XHCI_PS_PRC); + break; + case UHF_PORT_ENABLE: + XWRITE4(sc, oper, port, v | XHCI_PS_PED); + break; + case UHF_PORT_POWER: + XWRITE4(sc, oper, port, v & ~XHCI_PS_PP); + break; + case UHF_PORT_INDICATOR: + XWRITE4(sc, oper, port, v & ~XHCI_PS_PIC_SET(3)); + break; + case UHF_PORT_SUSPEND: + + /* U3 -> U15 */ + if (i == 3) { + XWRITE4(sc, oper, port, v | + XHCI_PS_PLS_SET(0xF) | XHCI_PS_LWS); + } + + /* wait 20ms for resume sequence to complete */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50); + + /* U0 */ + XWRITE4(sc, oper, port, v | + XHCI_PS_PLS_SET(0) | XHCI_PS_LWS); + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + + v = XREAD4(sc, capa, XHCI_HCSPARAMS0); + + sc->sc_hub_desc.hubd = xhci_hubd; + + sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport; + + if (XHCI_HCS0_PPC(v)) + i = UHD_PWR_INDIVIDUAL; + else + i = UHD_PWR_GANGED; + + if (XHCI_HCS0_PIND(v)) + i |= UHD_PORT_IND; + + i |= UHD_OC_INDIVIDUAL; + + USETW(sc->sc_hub_desc.hubd.wHubCharacteristics, i); + + /* see XHCI section 5.4.9: */ + sc->sc_hub_desc.hubd.bPwrOn2PwrGood = 10; + + for (j = 1; j <= sc->sc_noport; j++) { + + v = XREAD4(sc, oper, XHCI_PORTSC(j)); + if (v & XHCI_PS_DR) { + sc->sc_hub_desc.hubd. + DeviceRemovable[j / 8] |= 1U << (j % 8); + } + } + len = sc->sc_hub_desc.hubd.bLength; + break; + + case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): + len = 16; + memset(sc->sc_hub_desc.temp, 0, 16); + break; + + case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): + DPRINTFN(9, "UR_GET_STATUS i=%d\n", index); + + if ((index < 1) || + (index > sc->sc_noport)) { + err = USB_ERR_IOERROR; + goto done; + } + + v = XREAD4(sc, oper, XHCI_PORTSC(index)); + + DPRINTFN(9, "port status=0x%08x\n", v); + + i = UPS_PORT_LINK_STATE_SET(XHCI_PS_PLS_GET(v)); + + switch (XHCI_PS_SPEED_GET(v)) { + case 3: + i |= UPS_HIGH_SPEED; + break; + case 2: + i |= UPS_LOW_SPEED; + break; + case 1: + /* FULL speed */ + break; + default: + i |= UPS_OTHER_SPEED; + break; + } + + if (v & XHCI_PS_CCS) + i |= UPS_CURRENT_CONNECT_STATUS; + if (v & XHCI_PS_PED) + i |= UPS_PORT_ENABLED; + if (v & XHCI_PS_OCA) + i |= UPS_OVERCURRENT_INDICATOR; + if (v & XHCI_PS_PR) + i |= UPS_RESET; + if (v & XHCI_PS_PP) { + /* + * The USB 3.0 RH is using the + * USB 2.0's power bit + */ + i |= UPS_PORT_POWER; + } + USETW(sc->sc_hub_desc.ps.wPortStatus, i); + + i = 0; + if (v & XHCI_PS_CSC) + i |= UPS_C_CONNECT_STATUS; + if (v & XHCI_PS_PEC) + i |= UPS_C_PORT_ENABLED; + if (v & XHCI_PS_OCC) + i |= UPS_C_OVERCURRENT_INDICATOR; + if (v & XHCI_PS_WRC) + i |= UPS_C_BH_PORT_RESET; + if (v & XHCI_PS_PRC) + i |= UPS_C_PORT_RESET; + if (v & XHCI_PS_PLC) + i |= UPS_C_PORT_LINK_STATE; + if (v & XHCI_PS_CEC) + i |= UPS_C_PORT_CONFIG_ERROR; + + USETW(sc->sc_hub_desc.ps.wPortChange, i); + len = sizeof(sc->sc_hub_desc.ps); + break; + + case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): + err = USB_ERR_IOERROR; + goto done; + + case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + + case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): + + i = index >> 8; + index &= 0x00FF; + + if ((index < 1) || + (index > sc->sc_noport)) { + err = USB_ERR_IOERROR; + goto done; + } + + port = XHCI_PORTSC(index); + v = XREAD4(sc, oper, port) & ~XHCI_PS_CLEAR; + + switch (value) { + case UHF_PORT_U1_TIMEOUT: + if (XHCI_PS_SPEED_GET(v) != 4) { + err = USB_ERR_IOERROR; + goto done; + } + port = XHCI_PORTPMSC(index); + v = XREAD4(sc, oper, port); + v &= ~XHCI_PM3_U1TO_SET(0xFF); + v |= XHCI_PM3_U1TO_SET(i); + XWRITE4(sc, oper, port, v); + break; + case UHF_PORT_U2_TIMEOUT: + if (XHCI_PS_SPEED_GET(v) != 4) { + err = USB_ERR_IOERROR; + goto done; + } + port = XHCI_PORTPMSC(index); + v = XREAD4(sc, oper, port); + v &= ~XHCI_PM3_U2TO_SET(0xFF); + v |= XHCI_PM3_U2TO_SET(i); + XWRITE4(sc, oper, port, v); + break; + case UHF_BH_PORT_RESET: + XWRITE4(sc, oper, port, v | XHCI_PS_WPR); + break; + case UHF_PORT_LINK_STATE: + XWRITE4(sc, oper, port, v | + XHCI_PS_PLS_SET(i) | XHCI_PS_LWS); + /* 4ms settle time */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250); + break; + case UHF_PORT_ENABLE: + DPRINTFN(3, "set port enable %d\n", index); + break; + case UHF_PORT_SUSPEND: + DPRINTFN(6, "suspend port %u (LPM=%u)\n", index, i); + j = XHCI_PS_SPEED_GET(v); + if ((j < 1) || (j > 3)) { + /* non-supported speed */ + err = USB_ERR_IOERROR; + goto done; + } + XWRITE4(sc, oper, port, v | + XHCI_PS_PLS_SET(i ? 2 /* LPM */ : 3) | XHCI_PS_LWS); + break; + case UHF_PORT_RESET: + DPRINTFN(6, "reset port %d\n", index); + XWRITE4(sc, oper, port, v | XHCI_PS_PR); + break; + case UHF_PORT_POWER: + DPRINTFN(3, "set port power %d\n", index); + XWRITE4(sc, oper, port, v | XHCI_PS_PP); + break; + case UHF_PORT_TEST: + DPRINTFN(3, "set port test %d\n", index); + break; + case UHF_PORT_INDICATOR: + DPRINTFN(3, "set port indicator %d\n", index); + + v &= ~XHCI_PS_PIC_SET(3); + v |= XHCI_PS_PIC_SET(1); + + XWRITE4(sc, oper, port, v); + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + + case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER): + case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER): + case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER): + case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER): + break; + default: + err = USB_ERR_IOERROR; + goto done; + } +done: + *plength = len; + *pptr = ptr; + return (err); +} + +static void +xhci_xfer_setup(struct usb_setup_params *parm) +{ + struct usb_page_search page_info; + struct usb_page_cache *pc; + struct xhci_softc *sc; + struct usb_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t n; + + sc = XHCI_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + /* + * The proof for the "ntd" formula is illustrated like this: + * + * +------------------------------------+ + * | | + * | |remainder -> | + * | +-----+---+ | + * | | xxx | x | frm 0 | + * | +-----+---++ | + * | | xxx | xx | frm 1 | + * | +-----+----+ | + * | ... | + * +------------------------------------+ + * + * "xxx" means a completely full USB transfer descriptor + * + * "x" and "xx" means a short USB packet + * + * For the remainder of an USB transfer modulo + * "max_data_length" we need two USB transfer descriptors. + * One to transfer the remaining data and one to finalise with + * a zero length packet in case the "force_short_xfer" flag is + * set. We only need two USB transfer descriptors in the case + * where the transfer length of the first one is a factor of + * "max_frame_size". The rest of the needed USB transfer + * descriptors is given by the buffer size divided by the + * maximum data payload. + */ + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 16 * 3; + parm->hc_max_frame_size = XHCI_TD_PAYLOAD_MAX; + + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + if (xfer->flags_int.isochronous_xfr) { + ntd = ((1 * xfer->nframes) + + (xfer->max_data_length / xfer->max_hc_frame_size)); + } else if (xfer->flags_int.control_xfr) { + ntd = ((2 * xfer->nframes) + 1 /* STATUS */ + + (xfer->max_data_length / xfer->max_hc_frame_size)); + } else { + ntd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_hc_frame_size)); + } + +alloc_dma_set: + + if (parm->err) + return; + + /* + * Allocate queue heads and transfer descriptors + */ + last_obj = NULL; + + if (usbd_transfer_setup_sub_malloc( + parm, &pc, sizeof(struct xhci_td), + XHCI_TD_ALIGN, ntd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != ntd; n++) { + struct xhci_td *td; + + usbd_get_page(pc + n, 0, &page_info); + + td = page_info.buffer; + + /* init TD */ + td->td_self = page_info.physaddr; + td->obj_next = last_obj; + td->page_cache = pc + n; + + last_obj = td; + + usb_pc_cpu_flush(pc + n); + } + } + xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj; + + if (!xfer->flags_int.curr_dma_set) { + xfer->flags_int.curr_dma_set = 1; + goto alloc_dma_set; + } +} + +static usb_error_t +xhci_configure_reset_endpoint(struct usb_xfer *xfer) +{ + struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus); + struct usb_page_search buf_inp; + struct usb_device *udev; + struct xhci_endpoint_ext *pepext; + struct usb_endpoint_descriptor *edesc; + struct usb_page_cache *pcinp; + usb_error_t err; + uint8_t index; + uint8_t epno; + + pepext = xhci_get_endpoint_ext(xfer->xroot->udev, + xfer->endpoint->edesc); + + udev = xfer->xroot->udev; + index = udev->controller_slot_id; + + pcinp = &sc->sc_hw.devs[index].input_pc; + + usbd_get_page(pcinp, 0, &buf_inp); + + edesc = xfer->endpoint->edesc; + + epno = edesc->bEndpointAddress; + + if ((edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) + epno |= UE_DIR_IN; + + epno = XHCI_EPNO2EPID(epno); + + if (epno == 0) + return (USB_ERR_NO_PIPE); /* invalid */ + + XHCI_CMD_LOCK(sc); + + /* configure endpoint */ + + err = xhci_configure_endpoint_by_xfer(xfer); + + if (err != 0) { + XHCI_CMD_UNLOCK(sc); + return (err); + } + + /* + * Get the endpoint into the stopped state according to the + * endpoint context state diagram in the XHCI specification: + */ + + err = xhci_cmd_stop_ep(sc, 0, epno, index); + + if (err != 0) + DPRINTF("Could not stop endpoint %u\n", epno); + + err = xhci_cmd_reset_ep(sc, 0, epno, index); + + if (err != 0) + DPRINTF("Could not reset endpoint %u\n", epno); + + err = xhci_cmd_set_tr_dequeue_ptr(sc, pepext->physaddr | + XHCI_EPCTX_2_DCS_SET(1), 0, epno, index); + + if (err != 0) + DPRINTF("Could not set dequeue ptr for endpoint %u\n", epno); + + /* + * Get the endpoint into the running state according to the + * endpoint context state diagram in the XHCI specification: + */ + + xhci_configure_mask(udev, 1U << epno, 0); + + err = xhci_cmd_evaluate_ctx(sc, buf_inp.physaddr, index); + + if (err != 0) + DPRINTF("Could not configure endpoint %u\n", epno); + + err = xhci_cmd_configure_ep(sc, buf_inp.physaddr, 0, index); + + if (err != 0) + DPRINTF("Could not configure endpoint %u\n", epno); + + XHCI_CMD_UNLOCK(sc); + + return (0); +} + +static void +xhci_xfer_unsetup(struct usb_xfer *xfer) +{ + return; +} + +static void +xhci_start_dma_delay(struct usb_xfer *xfer) +{ + struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus); + + /* put transfer on interrupt queue (again) */ + usbd_transfer_enqueue(&sc->sc_bus.intr_q, xfer); + + (void)usb_proc_msignal(&sc->sc_config_proc, + &sc->sc_config_msg[0], &sc->sc_config_msg[1]); +} + +static void +xhci_configure_msg(struct usb_proc_msg *pm) +{ + struct xhci_softc *sc; + struct xhci_endpoint_ext *pepext; + struct usb_xfer *xfer; + + sc = XHCI_BUS2SC(((struct usb_bus_msg *)pm)->bus); + +restart: + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + + pepext = xhci_get_endpoint_ext(xfer->xroot->udev, + xfer->endpoint->edesc); + + if ((pepext->trb_halted != 0) || + (pepext->trb_running == 0)) { + + uint8_t i; + + /* clear halted and running */ + pepext->trb_halted = 0; + pepext->trb_running = 0; + + /* nuke remaining buffered transfers */ + + for (i = 0; i != (XHCI_MAX_TRANSFERS - 1); i++) { + /* + * NOTE: We need to use the timeout + * error code here else existing + * isochronous clients can get + * confused: + */ + if (pepext->xfer[i] != NULL) { + xhci_device_done(pepext->xfer[i], + USB_ERR_TIMEOUT); + } + } + + /* + * NOTE: The USB transfer cannot vanish in + * this state! + */ + + USB_BUS_UNLOCK(&sc->sc_bus); + + xhci_configure_reset_endpoint(xfer); + + USB_BUS_LOCK(&sc->sc_bus); + + /* check if halted is still cleared */ + if (pepext->trb_halted == 0) { + pepext->trb_running = 1; + pepext->trb_index = 0; + } + goto restart; + } + + if (xfer->flags_int.did_dma_delay) { + + /* remove transfer from interrupt queue (again) */ + usbd_transfer_dequeue(xfer); + + /* we are finally done */ + usb_dma_delay_done_cb(xfer); + + /* queue changed - restart */ + goto restart; + } + } + + TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { + + /* try to insert xfer on HW queue */ + xhci_transfer_insert(xfer); + + /* try to multi buffer */ + xhci_device_generic_multi_enter(xfer->endpoint, NULL); + } +} + +static void +xhci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *ep) +{ + struct xhci_endpoint_ext *pepext; + + DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d\n", + ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode); + + if (udev->flags.usb_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + if (udev->parent_hub == NULL) { + /* root HUB has special endpoint handling */ + return; + } + + ep->methods = &xhci_device_generic_methods; + + pepext = xhci_get_endpoint_ext(udev, edesc); + + USB_BUS_LOCK(udev->bus); + pepext->trb_halted = 1; + pepext->trb_running = 0; + USB_BUS_UNLOCK(udev->bus); +} + +static void +xhci_ep_uninit(struct usb_device *udev, struct usb_endpoint *ep) +{ + +} + +static void +xhci_ep_clear_stall(struct usb_device *udev, struct usb_endpoint *ep) +{ + struct xhci_endpoint_ext *pepext; + + DPRINTF("\n"); + + if (udev->flags.usb_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + if (udev->parent_hub == NULL) { + /* root HUB has special endpoint handling */ + return; + } + + pepext = xhci_get_endpoint_ext(udev, ep->edesc); + + USB_BUS_LOCK(udev->bus); + pepext->trb_halted = 1; + pepext->trb_running = 0; + USB_BUS_UNLOCK(udev->bus); +} + +static usb_error_t +xhci_device_init(struct usb_device *udev) +{ + struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); + usb_error_t err; + uint8_t temp; + + /* no init for root HUB */ + if (udev->parent_hub == NULL) + return (0); + + XHCI_CMD_LOCK(sc); + + /* set invalid default */ + + udev->controller_slot_id = sc->sc_noslot + 1; + + /* try to get a new slot ID from the XHCI */ + + err = xhci_cmd_enable_slot(sc, &temp); + + if (err) { + XHCI_CMD_UNLOCK(sc); + return (err); + } + + if (temp > sc->sc_noslot) { + XHCI_CMD_UNLOCK(sc); + return (USB_ERR_BAD_ADDRESS); + } + + if (sc->sc_hw.devs[temp].state != XHCI_ST_DISABLED) { + DPRINTF("slot %u already allocated.\n", temp); + XHCI_CMD_UNLOCK(sc); + return (USB_ERR_BAD_ADDRESS); + } + + /* store slot ID for later reference */ + + udev->controller_slot_id = temp; + + /* reset data structure */ + + memset(&sc->sc_hw.devs[temp], 0, sizeof(sc->sc_hw.devs[0])); + + /* set mark slot allocated */ + + sc->sc_hw.devs[temp].state = XHCI_ST_ENABLED; + + err = xhci_alloc_device_ext(udev); + + XHCI_CMD_UNLOCK(sc); + + /* get device into default state */ + + if (err == 0) + err = xhci_set_address(udev, NULL, 0); + + return (err); +} + +static void +xhci_device_uninit(struct usb_device *udev) +{ + struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); + uint8_t index; + + /* no init for root HUB */ + if (udev->parent_hub == NULL) + return; + + XHCI_CMD_LOCK(sc); + + index = udev->controller_slot_id; + + if (index <= sc->sc_noslot) { + xhci_cmd_disable_slot(sc, index); + sc->sc_hw.devs[index].state = XHCI_ST_DISABLED; + + /* free device extension */ + xhci_free_device_ext(udev); + } + + XHCI_CMD_UNLOCK(sc); +} + +static void +xhci_get_dma_delay(struct usb_device *udev, uint32_t *pus) +{ + /* + * Wait until the hardware has finished any possible use of + * the transfer descriptor(s) + */ + *pus = 2048; /* microseconds */ +} + +static void +xhci_device_resume(struct usb_device *udev) +{ + struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); + uint8_t index; + uint8_t n; + + DPRINTF("\n"); + + /* check for root HUB */ + if (udev->parent_hub == NULL) + return; + + index = udev->controller_slot_id; + + XHCI_CMD_LOCK(sc); + + /* blindly resume all endpoints */ + + USB_BUS_LOCK(udev->bus); + + for (n = 1; n != XHCI_MAX_ENDPOINTS; n++) + XWRITE4(sc, door, XHCI_DOORBELL(index), n | XHCI_DB_SID_SET(0)); + + USB_BUS_UNLOCK(udev->bus); + + XHCI_CMD_UNLOCK(sc); +} + +static void +xhci_device_suspend(struct usb_device *udev) +{ + struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); + uint8_t index; + uint8_t n; + usb_error_t err; + + DPRINTF("\n"); + + /* check for root HUB */ + if (udev->parent_hub == NULL) + return; + + index = udev->controller_slot_id; + + XHCI_CMD_LOCK(sc); + + /* blindly suspend all endpoints */ + + for (n = 1; n != XHCI_MAX_ENDPOINTS; n++) { + err = xhci_cmd_stop_ep(sc, 1, n, index); + if (err != 0) { + DPRINTF("Failed to suspend endpoint " + "%u on slot %u (ignored).\n", n, index); + } + } + + XHCI_CMD_UNLOCK(sc); +} + +static void +xhci_set_hw_power(struct usb_bus *bus) +{ + DPRINTF("\n"); +} + +static void +xhci_device_state_change(struct usb_device *udev) +{ + struct xhci_softc *sc = XHCI_BUS2SC(udev->bus); + struct usb_page_search buf_inp; + usb_error_t err; + uint8_t index; + + /* check for root HUB */ + if (udev->parent_hub == NULL) + return; + + index = udev->controller_slot_id; + + DPRINTF("\n"); + + if (usb_get_device_state(udev) == USB_STATE_CONFIGURED) { + err = uhub_query_info(udev, &sc->sc_hw.devs[index].nports, + &sc->sc_hw.devs[index].tt); + if (err != 0) + sc->sc_hw.devs[index].nports = 0; + } + + XHCI_CMD_LOCK(sc); + + switch (usb_get_device_state(udev)) { + case USB_STATE_POWERED: + if (sc->sc_hw.devs[index].state == XHCI_ST_DEFAULT) + break; + + sc->sc_hw.devs[index].state = XHCI_ST_DEFAULT; + + err = xhci_cmd_reset_dev(sc, index); + + if (err != 0) { + DPRINTF("Device reset failed " + "for slot %u.\n", index); + } + break; + + case USB_STATE_ADDRESSED: + if (sc->sc_hw.devs[index].state == XHCI_ST_ADDRESSED) + break; + + sc->sc_hw.devs[index].state = XHCI_ST_ADDRESSED; + + err = xhci_cmd_configure_ep(sc, 0, 1, index); + + if (err) { + DPRINTF("Failed to deconfigure " + "slot %u.\n", index); + } + break; + + case USB_STATE_CONFIGURED: + if (sc->sc_hw.devs[index].state == XHCI_ST_CONFIGURED) + break; + + sc->sc_hw.devs[index].state = XHCI_ST_CONFIGURED; + + usbd_get_page(&sc->sc_hw.devs[index].input_pc, 0, &buf_inp); + + xhci_configure_mask(udev, 1, 0); + + err = xhci_configure_device(udev); + if (err != 0) { + DPRINTF("Could not configure device " + "at slot %u.\n", index); + } + + err = xhci_cmd_evaluate_ctx(sc, buf_inp.physaddr, index); + if (err != 0) { + DPRINTF("Could not evaluate device " + "context at slot %u.\n", index); + } + break; + + default: + break; + } + XHCI_CMD_UNLOCK(sc); +} + +struct usb_bus_methods xhci_bus_methods = { + .endpoint_init = xhci_ep_init, + .endpoint_uninit = xhci_ep_uninit, + .xfer_setup = xhci_xfer_setup, + .xfer_unsetup = xhci_xfer_unsetup, + .get_dma_delay = xhci_get_dma_delay, + .device_init = xhci_device_init, + .device_uninit = xhci_device_uninit, + .device_resume = xhci_device_resume, + .device_suspend = xhci_device_suspend, + .set_hw_power = xhci_set_hw_power, + .roothub_exec = xhci_roothub_exec, + .xfer_poll = xhci_do_poll, + .start_dma_delay = xhci_start_dma_delay, + .set_address = xhci_set_address, + .clear_stall = xhci_ep_clear_stall, + .device_state_change = xhci_device_state_change, + .set_hw_power_sleep = xhci_set_hw_power_sleep, +}; diff --git a/sys/bus/u4b/controller/xhci.h b/sys/bus/u4b/controller/xhci.h new file mode 100644 index 0000000000..14afcaf0ae --- /dev/null +++ b/sys/bus/u4b/controller/xhci.h @@ -0,0 +1,499 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2010 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _XHCI_H_ +#define _XHCI_H_ + +#define XHCI_MAX_DEVICES MIN(USB_MAX_DEVICES, 128) +#define XHCI_MAX_ENDPOINTS 32 /* hardcoded - do not change */ +#define XHCI_MAX_SCRATCHPADS 32 +#define XHCI_MAX_EVENTS (16 * 13) +#define XHCI_MAX_COMMANDS (16 * 1) +#define XHCI_MAX_RSEG 1 +#define XHCI_MAX_TRANSFERS 4 + +#define XHCI_DEV_CTX_ADDR_ALIGN 64 /* bytes */ +#define XHCI_DEV_CTX_ALIGN 64 /* bytes */ +#define XHCI_INPUT_CTX_ALIGN 64 /* bytes */ +#define XHCI_SLOT_CTX_ALIGN 32 /* bytes */ +#define XHCI_ENDP_CTX_ALIGN 32 /* bytes */ +#define XHCI_STREAM_CTX_ALIGN 16 /* bytes */ +#define XHCI_TRANS_RING_SEG_ALIGN 16 /* bytes */ +#define XHCI_CMD_RING_SEG_ALIGN 64 /* bytes */ +#define XHCI_EVENT_RING_SEG_ALIGN 64 /* bytes */ +#define XHCI_SCRATCH_BUF_ARRAY_ALIGN 64 /* bytes */ +#define XHCI_SCRATCH_BUFFER_ALIGN USB_PAGE_SIZE +#define XHCI_TRB_ALIGN 16 /* bytes */ +#define XHCI_TD_ALIGN 64 /* bytes */ +#define XHCI_PAGE_SIZE 4096 /* bytes */ + +struct xhci_dev_ctx_addr { + volatile uint64_t qwBaaDevCtxAddr[USB_MAX_DEVICES + 1]; + struct { + volatile uint64_t dummy; + } __aligned(64) padding; + volatile uint64_t qwSpBufPtr[XHCI_MAX_SCRATCHPADS]; +}; + +#define XHCI_EPNO2EPID(x) \ + ((((x) & UE_DIR_IN) ? 1 : 0) | (2 * ((x) & UE_ADDR))) + +struct xhci_slot_ctx { + volatile uint32_t dwSctx0; +#define XHCI_SCTX_0_ROUTE_SET(x) ((x) & 0xFFFFF) +#define XHCI_SCTX_0_ROUTE_GET(x) ((x) & 0xFFFFF) +#define XHCI_SCTX_0_SPEED_SET(x) (((x) & 0xF) << 20) +#define XHCI_SCTX_0_SPEED_GET(x) (((x) >> 20) & 0xF) +#define XHCI_SCTX_0_MTT_SET(x) (((x) & 0x1) << 25) +#define XHCI_SCTX_0_MTT_GET(x) (((x) >> 25) & 0x1) +#define XHCI_SCTX_0_HUB_SET(x) (((x) & 0x1) << 26) +#define XHCI_SCTX_0_HUB_GET(x) (((x) >> 26) & 0x1) +#define XHCI_SCTX_0_CTX_NUM_SET(x) (((x) & 0x1F) << 27) +#define XHCI_SCTX_0_CTX_NUM_GET(x) (((x) >> 27) & 0x1F) + volatile uint32_t dwSctx1; +#define XHCI_SCTX_1_MAX_EL_SET(x) ((x) & 0xFFFF) +#define XHCI_SCTX_1_MAX_EL_GET(x) ((x) & 0xFFFF) +#define XHCI_SCTX_1_RH_PORT_SET(x) (((x) & 0xFF) << 16) +#define XHCI_SCTX_1_RH_PORT_GET(x) (((x) >> 16) & 0xFF) +#define XHCI_SCTX_1_NUM_PORTS_SET(x) (((x) & 0xFF) << 24) +#define XHCI_SCTX_1_NUM_PORTS_GET(x) (((x) >> 24) & 0xFF) + volatile uint32_t dwSctx2; +#define XHCI_SCTX_2_TT_HUB_SID_SET(x) ((x) & 0xFF) +#define XHCI_SCTX_2_TT_HUB_SID_GET(x) ((x) & 0xFF) +#define XHCI_SCTX_2_TT_PORT_NUM_SET(x) (((x) & 0xFF) << 8) +#define XHCI_SCTX_2_TT_PORT_NUM_GET(x) (((x) >> 8) & 0xFF) +#define XHCI_SCTX_2_TT_THINK_TIME_SET(x) (((x) & 0x3) << 16) +#define XHCI_SCTX_2_TT_THINK_TIME_GET(x) (((x) >> 16) & 0x3) +#define XHCI_SCTX_2_IRQ_TARGET_SET(x) (((x) & 0x3FF) << 22) +#define XHCI_SCTX_2_IRQ_TARGET_GET(x) (((x) >> 22) & 0x3FF) + volatile uint32_t dwSctx3; +#define XHCI_SCTX_3_DEV_ADDR_SET(x) ((x) & 0xFF) +#define XHCI_SCTX_3_DEV_ADDR_GET(x) ((x) & 0xFF) +#define XHCI_SCTX_3_SLOT_STATE_SET(x) (((x) & 0x1F) << 27) +#define XHCI_SCTX_3_SLOT_STATE_GET(x) (((x) >> 27) & 0x1F) + volatile uint32_t dwSctx4; + volatile uint32_t dwSctx5; + volatile uint32_t dwSctx6; + volatile uint32_t dwSctx7; +}; + +struct xhci_endp_ctx { + volatile uint32_t dwEpCtx0; +#define XHCI_EPCTX_0_EPSTATE_SET(x) ((x) & 0x7) +#define XHCI_EPCTX_0_EPSTATE_GET(x) ((x) & 0x7) +#define XHCI_EPCTX_0_MULT_SET(x) (((x) & 0x3) << 8) +#define XHCI_EPCTX_0_MULT_GET(x) (((x) >> 8) & 0x3) +#define XHCI_EPCTX_0_MAXP_STREAMS_SET(x) (((x) & 0x1F) << 10) +#define XHCI_EPCTX_0_MAXP_STREAMS_GET(x) (((x) >> 10) & 0x1F) +#define XHCI_EPCTX_0_LSA_SET(x) (((x) & 0x1) << 15) +#define XHCI_EPCTX_0_LSA_GET(x) (((x) >> 15) & 0x1) +#define XHCI_EPCTX_0_IVAL_SET(x) (((x) & 0xFF) << 16) +#define XHCI_EPCTX_0_IVAL_GET(x) (((x) >> 16) & 0xFF) + volatile uint32_t dwEpCtx1; +#define XHCI_EPCTX_1_CERR_SET(x) (((x) & 0x3) << 1) +#define XHCI_EPCTX_1_CERR_GET(x) (((x) >> 1) & 0x3) +#define XHCI_EPCTX_1_EPTYPE_SET(x) (((x) & 0x7) << 3) +#define XHCI_EPCTX_1_EPTYPE_GET(x) (((x) >> 3) & 0x7) +#define XHCI_EPCTX_1_HID_SET(x) (((x) & 0x1) << 7) +#define XHCI_EPCTX_1_HID_GET(x) (((x) >> 7) & 0x1) +#define XHCI_EPCTX_1_MAXB_SET(x) (((x) & 0xFF) << 8) +#define XHCI_EPCTX_1_MAXB_GET(x) (((x) >> 8) & 0xFF) +#define XHCI_EPCTX_1_MAXP_SIZE_SET(x) (((x) & 0xFFFF) << 16) +#define XHCI_EPCTX_1_MAXP_SIZE_GET(x) (((x) >> 16) & 0xFFFF) + volatile uint64_t qwEpCtx2; +#define XHCI_EPCTX_2_DCS_SET(x) ((x) & 0x1) +#define XHCI_EPCTX_2_DCS_GET(x) ((x) & 0x1) +#define XHCI_EPCTX_2_TR_DQ_PTR_MASK 0xFFFFFFFFFFFFFFF0U + volatile uint32_t dwEpCtx4; +#define XHCI_EPCTX_4_AVG_TRB_LEN_SET(x) ((x) & 0xFFFF) +#define XHCI_EPCTX_4_AVG_TRB_LEN_GET(x) ((x) & 0xFFFF) +#define XHCI_EPCTX_4_MAX_ESIT_PAYLOAD_SET(x) (((x) & 0xFFFF) << 16) +#define XHCI_EPCTX_4_MAX_ESIT_PAYLOAD_GET(x) (((x) >> 16) & 0xFFFF) + volatile uint32_t dwEpCtx5; + volatile uint32_t dwEpCtx6; + volatile uint32_t dwEpCtx7; +}; + +struct xhci_input_ctx { +#define XHCI_INCTX_NON_CTRL_MASK 0xFFFFFFFCU + volatile uint32_t dwInCtx0; +#define XHCI_INCTX_0_DROP_MASK(n) (1U << (n)) + volatile uint32_t dwInCtx1; +#define XHCI_INCTX_1_ADD_MASK(n) (1U << (n)) + volatile uint32_t dwInCtx2; + volatile uint32_t dwInCtx3; + volatile uint32_t dwInCtx4; + volatile uint32_t dwInCtx5; + volatile uint32_t dwInCtx6; + volatile uint32_t dwInCtx7; +}; + +struct xhci_input_dev_ctx { + struct xhci_input_ctx ctx_input; + struct xhci_slot_ctx ctx_slot; + struct xhci_endp_ctx ctx_ep[XHCI_MAX_ENDPOINTS - 1]; +}; + +struct xhci_dev_ctx { + struct xhci_slot_ctx ctx_slot; + struct xhci_endp_ctx ctx_ep[XHCI_MAX_ENDPOINTS - 1]; +} __aligned(XHCI_DEV_CTX_ALIGN); + +struct xhci_stream_ctx { + volatile uint64_t qwSctx0; +#define XHCI_SCTX_0_DCS_GET(x) ((x) & 0x1) +#define XHCI_SCTX_0_DCS_SET(x) ((x) & 0x1) +#define XHCI_SCTX_0_SCT_SET(x) (((x) & 0x7) << 1) +#define XHCI_SCTX_0_SCT_GET(x) (((x) >> 1) & 0x7) +#define XHCI_SCTX_0_SCT_SEC_TR_RING 0x0 +#define XHCI_SCTX_0_SCT_PRIM_TR_RING 0x1 +#define XHCI_SCTX_0_SCT_PRIM_SSA_8 0x2 +#define XHCI_SCTX_0_SCT_PRIM_SSA_16 0x3 +#define XHCI_SCTX_0_SCT_PRIM_SSA_32 0x4 +#define XHCI_SCTX_0_SCT_PRIM_SSA_64 0x5 +#define XHCI_SCTX_0_SCT_PRIM_SSA_128 0x6 +#define XHCI_SCTX_0_SCT_PRIM_SSA_256 0x7 +#define XHCI_SCTX_0_TR_DQ_PTR_MASK 0xFFFFFFFFFFFFFFF0U + volatile uint32_t dwSctx2; + volatile uint32_t dwSctx3; +}; + +struct xhci_trb { + volatile uint64_t qwTrb0; +#define XHCI_TRB_0_WLENGTH_MASK (0xFFFFULL << 48) + volatile uint32_t dwTrb2; +#define XHCI_TRB_2_ERROR_GET(x) (((x) >> 24) & 0xFF) +#define XHCI_TRB_2_ERROR_SET(x) (((x) & 0xFF) << 24) +#define XHCI_TRB_2_TDSZ_GET(x) (((x) >> 17) & 0x1F) +#define XHCI_TRB_2_TDSZ_SET(x) (((x) & 0x1F) << 17) +#define XHCI_TRB_2_REM_GET(x) ((x) & 0xFFFFFF) +#define XHCI_TRB_2_REM_SET(x) ((x) & 0xFFFFFF) +#define XHCI_TRB_2_BYTES_GET(x) ((x) & 0x1FFFF) +#define XHCI_TRB_2_BYTES_SET(x) ((x) & 0x1FFFF) +#define XHCI_TRB_2_IRQ_GET(x) (((x) >> 22) & 0x3FF) +#define XHCI_TRB_2_IRQ_SET(x) (((x) & 0x3FF) << 22) +#define XHCI_TRB_2_STREAM_GET(x) (((x) >> 16) & 0xFFFF) +#define XHCI_TRB_2_STREAM_SET(x) (((x) & 0xFFFF) << 16) + + volatile uint32_t dwTrb3; +#define XHCI_TRB_3_TYPE_GET(x) (((x) >> 10) & 0x3F) +#define XHCI_TRB_3_TYPE_SET(x) (((x) & 0x3F) << 10) +#define XHCI_TRB_3_CYCLE_BIT (1U << 0) +#define XHCI_TRB_3_TC_BIT (1U << 1) /* command ring only */ +#define XHCI_TRB_3_ENT_BIT (1U << 1) /* transfer ring only */ +#define XHCI_TRB_3_ISP_BIT (1U << 2) +#define XHCI_TRB_3_NSNOOP_BIT (1U << 3) +#define XHCI_TRB_3_CHAIN_BIT (1U << 4) +#define XHCI_TRB_3_IOC_BIT (1U << 5) +#define XHCI_TRB_3_IDT_BIT (1U << 6) +#define XHCI_TRB_3_TBC_GET(x) (((x) >> 7) & 3) +#define XHCI_TRB_3_TBC_SET(x) (((x) & 3) << 7) +#define XHCI_TRB_3_BEI_BIT (1U << 9) +#define XHCI_TRB_3_DCEP_BIT (1U << 9) +#define XHCI_TRB_3_PRSV_BIT (1U << 9) +#define XHCI_TRB_3_BSR_BIT (1U << 9) +#define XHCI_TRB_3_TRT_MASK (3U << 16) +#define XHCI_TRB_3_TRT_NONE (0U << 16) +#define XHCI_TRB_3_TRT_OUT (2U << 16) +#define XHCI_TRB_3_TRT_IN (3U << 16) +#define XHCI_TRB_3_DIR_IN (1U << 16) +#define XHCI_TRB_3_TLBPC_GET(x) (((x) >> 16) & 0xF) +#define XHCI_TRB_3_TLBPC_SET(x) (((x) & 0xF) << 16) +#define XHCI_TRB_3_EP_GET(x) (((x) >> 16) & 0x1F) +#define XHCI_TRB_3_EP_SET(x) (((x) & 0x1F) << 16) +#define XHCI_TRB_3_FRID_GET(x) (((x) >> 20) & 0x7FF) +#define XHCI_TRB_3_FRID_SET(x) (((x) & 0x7FF) << 20) +#define XHCI_TRB_3_ISO_SIA_BIT (1U << 31) +#define XHCI_TRB_3_SUSP_EP_BIT (1U << 23) +#define XHCI_TRB_3_SLOT_GET(x) (((x) >> 24) & 0xFF) +#define XHCI_TRB_3_SLOT_SET(x) (((x) & 0xFF) << 24) + +/* Commands */ +#define XHCI_TRB_TYPE_RESERVED 0x00 +#define XHCI_TRB_TYPE_NORMAL 0x01 +#define XHCI_TRB_TYPE_SETUP_STAGE 0x02 +#define XHCI_TRB_TYPE_DATA_STAGE 0x03 +#define XHCI_TRB_TYPE_STATUS_STAGE 0x04 +#define XHCI_TRB_TYPE_ISOCH 0x05 +#define XHCI_TRB_TYPE_LINK 0x06 +#define XHCI_TRB_TYPE_EVENT_DATA 0x07 +#define XHCI_TRB_TYPE_NOOP 0x08 +#define XHCI_TRB_TYPE_ENABLE_SLOT 0x09 +#define XHCI_TRB_TYPE_DISABLE_SLOT 0x0A +#define XHCI_TRB_TYPE_ADDRESS_DEVICE 0x0B +#define XHCI_TRB_TYPE_CONFIGURE_EP 0x0C +#define XHCI_TRB_TYPE_EVALUATE_CTX 0x0D +#define XHCI_TRB_TYPE_RESET_EP 0x0E +#define XHCI_TRB_TYPE_STOP_EP 0x0F +#define XHCI_TRB_TYPE_SET_TR_DEQUEUE 0x10 +#define XHCI_TRB_TYPE_RESET_DEVICE 0x11 +#define XHCI_TRB_TYPE_FORCE_EVENT 0x12 +#define XHCI_TRB_TYPE_NEGOTIATE_BW 0x13 +#define XHCI_TRB_TYPE_SET_LATENCY_TOL 0x14 +#define XHCI_TRB_TYPE_GET_PORT_BW 0x15 +#define XHCI_TRB_TYPE_FORCE_HEADER 0x16 +#define XHCI_TRB_TYPE_NOOP_CMD 0x17 + +/* Events */ +#define XHCI_TRB_EVENT_TRANSFER 0x20 +#define XHCI_TRB_EVENT_CMD_COMPLETE 0x21 +#define XHCI_TRB_EVENT_PORT_STS_CHANGE 0x22 +#define XHCI_TRB_EVENT_BW_REQUEST 0x23 +#define XHCI_TRB_EVENT_DOORBELL 0x24 +#define XHCI_TRB_EVENT_HOST_CTRL 0x25 +#define XHCI_TRB_EVENT_DEVICE_NOTIFY 0x26 +#define XHCI_TRB_EVENT_MFINDEX_WRAP 0x27 + +/* Error codes */ +#define XHCI_TRB_ERROR_INVALID 0x00 +#define XHCI_TRB_ERROR_SUCCESS 0x01 +#define XHCI_TRB_ERROR_DATA_BUF 0x02 +#define XHCI_TRB_ERROR_BABBLE 0x03 +#define XHCI_TRB_ERROR_XACT 0x04 +#define XHCI_TRB_ERROR_TRB 0x05 +#define XHCI_TRB_ERROR_STALL 0x06 +#define XHCI_TRB_ERROR_RESOURCE 0x07 +#define XHCI_TRB_ERROR_BANDWIDTH 0x08 +#define XHCI_TRB_ERROR_NO_SLOTS 0x09 +#define XHCI_TRB_ERROR_STREAM_TYPE 0x0A +#define XHCI_TRB_ERROR_SLOT_NOT_ON 0x0B +#define XHCI_TRB_ERROR_ENDP_NOT_ON 0x0C +#define XHCI_TRB_ERROR_SHORT_PKT 0x0D +#define XHCI_TRB_ERROR_RING_UNDERRUN 0x0E +#define XHCI_TRB_ERROR_RING_OVERRUN 0x0F +#define XHCI_TRB_ERROR_VF_RING_FULL 0x10 +#define XHCI_TRB_ERROR_PARAMETER 0x11 +#define XHCI_TRB_ERROR_BW_OVERRUN 0x12 +#define XHCI_TRB_ERROR_CONTEXT_STATE 0x13 +#define XHCI_TRB_ERROR_NO_PING_RESP 0x14 +#define XHCI_TRB_ERROR_EV_RING_FULL 0x15 +#define XHCI_TRB_ERROR_INCOMPAT_DEV 0x16 +#define XHCI_TRB_ERROR_MISSED_SERVICE 0x17 +#define XHCI_TRB_ERROR_CMD_RING_STOP 0x18 +#define XHCI_TRB_ERROR_CMD_ABORTED 0x19 +#define XHCI_TRB_ERROR_STOPPED 0x1A +#define XHCI_TRB_ERROR_LENGTH 0x1B +#define XHCI_TRB_ERROR_BAD_MELAT 0x1D +#define XHCI_TRB_ERROR_ISOC_OVERRUN 0x1F +#define XHCI_TRB_ERROR_EVENT_LOST 0x20 +#define XHCI_TRB_ERROR_UNDEFINED 0x21 +#define XHCI_TRB_ERROR_INVALID_SID 0x22 +#define XHCI_TRB_ERROR_SEC_BW 0x23 +#define XHCI_TRB_ERROR_SPLIT_XACT 0x24 +} __aligned(4); + +struct xhci_dev_endpoint_trbs { + struct xhci_trb trb[XHCI_MAX_ENDPOINTS][XHCI_MAX_TRANSFERS]; +}; + +#define XHCI_TD_PAGE_NBUF 17 /* units, room enough for 64Kbytes */ +#define XHCI_TD_PAGE_SIZE 4096 /* bytes */ +#define XHCI_TD_PAYLOAD_MAX (XHCI_TD_PAGE_SIZE * (XHCI_TD_PAGE_NBUF - 1)) + +struct xhci_td { + struct xhci_trb td_trb[XHCI_TD_PAGE_NBUF + 1]; + +/* + * Extra information needed: + */ + uint64_t td_self; + struct xhci_td *next; + struct xhci_td *alt_next; + struct xhci_td *obj_next; + struct usb_page_cache *page_cache; + uint32_t len; + uint32_t remainder; + uint8_t ntrb; + uint8_t status; +} __aligned(XHCI_TRB_ALIGN); + +struct xhci_command { + struct xhci_trb trb; + TAILQ_ENTRY(xhci_command) entry; +}; + +struct xhci_event_ring_seg { + volatile uint64_t qwEvrsTablePtr; + volatile uint32_t dwEvrsTableSize; + volatile uint32_t dwEvrsReserved; +}; + +struct xhci_hw_root { + struct xhci_event_ring_seg hwr_ring_seg[XHCI_MAX_RSEG]; + struct { + volatile uint64_t dummy; + } __aligned(64) padding; + struct xhci_trb hwr_events[XHCI_MAX_EVENTS]; + struct xhci_trb hwr_commands[XHCI_MAX_COMMANDS]; +}; + +struct xhci_endpoint_ext { + struct xhci_trb *trb; + struct usb_xfer *xfer[XHCI_MAX_TRANSFERS - 1]; + struct usb_page_cache *page_cache; + uint64_t physaddr; + uint8_t trb_used; + uint8_t trb_index; + uint8_t trb_halted; + uint8_t trb_running; +}; + +enum { + XHCI_ST_DISABLED, + XHCI_ST_ENABLED, + XHCI_ST_DEFAULT, + XHCI_ST_ADDRESSED, + XHCI_ST_CONFIGURED, + XHCI_ST_MAX +}; + +struct xhci_hw_dev { + struct usb_page_cache device_pc; + struct usb_page_cache input_pc; + struct usb_page_cache endpoint_pc; + + struct usb_page device_pg; + struct usb_page input_pg; + struct usb_page endpoint_pg; + + struct xhci_endpoint_ext endp[XHCI_MAX_ENDPOINTS]; + + uint8_t state; + uint8_t nports; + uint8_t tt; + uint8_t reserved; +}; + +struct xhci_hw_softc { + struct usb_page_cache root_pc; + struct usb_page_cache ctx_pc; + struct usb_page_cache scratch_pc[XHCI_MAX_SCRATCHPADS]; + + struct usb_page root_pg; + struct usb_page ctx_pg; + struct usb_page scratch_pg[XHCI_MAX_SCRATCHPADS]; + + struct xhci_hw_dev devs[XHCI_MAX_DEVICES + 1]; +}; + +struct xhci_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; + struct usb_endpoint_ss_comp_descriptor endpcd; +} __packed; + +struct xhci_bos_desc { + struct usb_bos_descriptor bosd; + struct usb_devcap_usb2ext_descriptor usb2extd; + struct usb_devcap_ss_descriptor usbdcd; + struct usb_devcap_container_id_descriptor cidd; +} __packed; + +union xhci_hub_desc { + struct usb_status stat; + struct usb_port_status ps; + struct usb_hub_ss_descriptor hubd; + uint8_t temp[128]; +}; + +struct xhci_softc { + struct xhci_hw_softc sc_hw; + /* base device */ + struct usb_bus sc_bus; + /* configure process */ + struct usb_process sc_config_proc; + struct usb_bus_msg sc_config_msg[2]; + + union xhci_hub_desc sc_hub_desc; + + struct cv sc_cmd_cv; + struct sx sc_cmd_sx; + + struct usb_device *sc_devices[XHCI_MAX_DEVICES]; + struct resource *sc_io_res; + struct resource *sc_irq_res; + + void *sc_intr_hdl; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + /* last pending command address */ + uint64_t sc_cmd_addr; + /* result of command */ + uint32_t sc_cmd_result[2]; + /* copy of cmd register */ + uint32_t sc_cmd; + /* worst case exit latency */ + uint32_t sc_exit_lat_max; + + /* offset to operational registers */ + uint32_t sc_oper_off; + /* offset to capability registers */ + uint32_t sc_capa_off; + /* offset to runtime registers */ + uint32_t sc_runt_off; + /* offset to doorbell registers */ + uint32_t sc_door_off; + + /* chip specific */ + uint16_t sc_erst_max; + uint16_t sc_event_idx; + uint16_t sc_command_idx; + + uint8_t sc_event_ccs; + uint8_t sc_command_ccs; + /* number of XHCI device slots */ + uint8_t sc_noslot; + /* number of ports on root HUB */ + uint8_t sc_noport; + /* number of scratch pages */ + uint8_t sc_noscratch; + /* root HUB device configuration */ + uint8_t sc_conf; + uint8_t sc_hub_idata[2]; + + /* size of context */ + uint8_t sc_ctx_is_64_byte; + + /* vendor string for root HUB */ + char sc_vendor[16]; +}; + +#define XHCI_CMD_LOCK(sc) sx_xlock(&(sc)->sc_cmd_sx) +#define XHCI_CMD_UNLOCK(sc) sx_xunlock(&(sc)->sc_cmd_sx) +#define XHCI_CMD_ASSERT_LOCKED(sc) sx_assert(&(sc)->sc_cmd_sx, SA_LOCKED) + +/* prototypes */ + +usb_error_t xhci_halt_controller(struct xhci_softc *); +usb_error_t xhci_init(struct xhci_softc *, device_t); +usb_error_t xhci_start_controller(struct xhci_softc *); +void xhci_interrupt(struct xhci_softc *); +void xhci_uninit(struct xhci_softc *); + +#endif /* _XHCI_H_ */ diff --git a/sys/bus/u4b/controller/xhci_pci.c b/sys/bus/u4b/controller/xhci_pci.c new file mode 100644 index 0000000000..80877acabd --- /dev/null +++ b/sys/bus/u4b/controller/xhci_pci.c @@ -0,0 +1,276 @@ +/*- + * Copyright (c) 2010 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "usb_if.h" + +static device_probe_t xhci_pci_probe; +static device_attach_t xhci_pci_attach; +static device_detach_t xhci_pci_detach; +static usb_take_controller_t xhci_pci_take_controller; + +static device_method_t xhci_device_methods[] = { + /* device interface */ + DEVMETHOD(device_probe, xhci_pci_probe), + DEVMETHOD(device_attach, xhci_pci_attach), + DEVMETHOD(device_detach, xhci_pci_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(usb_take_controller, xhci_pci_take_controller), + + DEVMETHOD_END +}; + +static driver_t xhci_driver = { + .name = "xhci", + .methods = xhci_device_methods, + .size = sizeof(struct xhci_softc), +}; + +static devclass_t xhci_devclass; + +DRIVER_MODULE(xhci, pci, xhci_driver, xhci_devclass, 0, 0); +MODULE_DEPEND(xhci, usb, 1, 1, 1); + + +static const char * +xhci_pci_match(device_t self) +{ + if ((pci_get_class(self) == PCIC_SERIALBUS) + && (pci_get_subclass(self) == PCIS_SERIALBUS_USB) + && (pci_get_progif(self) == PCIP_SERIALBUS_USB_XHCI)) { + return ("XHCI (generic) USB 3.0 controller"); + } + return (NULL); /* dunno */ +} + +static int +xhci_pci_probe(device_t self) +{ + const char *desc = xhci_pci_match(self); + + if (desc) { + device_set_desc(self, desc); + return (0); + } else { + return (ENXIO); + } +} + +static int +xhci_pci_attach(device_t self) +{ + struct xhci_softc *sc = device_get_softc(self); + int err; + int rid; + + /* XXX check for 64-bit capability */ + + if (xhci_init(sc, self)) { + device_printf(self, "Could not initialize softc\n"); + goto error; + } + + pci_enable_busmaster(self); + + rid = PCI_XHCI_CBMEM; + sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (!sc->sc_io_res) { + device_printf(self, "Could not map memory\n"); + goto error; + } + sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); + sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); + sc->sc_io_size = rman_get_size(sc->sc_io_res); + + rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + device_printf(self, "Could not allocate IRQ\n"); + goto error; + } + sc->sc_bus.bdev = device_add_child(self, "usbus", -1); + if (sc->sc_bus.bdev == NULL) { + device_printf(self, "Could not add USB device\n"); + goto error; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + sprintf(sc->sc_vendor, "0x%04x", pci_get_vendor(self)); + +#if (__FreeBSD_version >= 700031) + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + NULL, (driver_intr_t *)xhci_interrupt, sc, &sc->sc_intr_hdl); +#else + err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, + (driver_intr_t *)xhci_interrupt, sc, &sc->sc_intr_hdl); +#endif + if (err) { + device_printf(self, "Could not setup IRQ, err=%d\n", err); + sc->sc_intr_hdl = NULL; + goto error; + } + xhci_pci_take_controller(self); + + err = xhci_halt_controller(sc); + + if (err == 0) + err = xhci_start_controller(sc); + + if (err == 0) + err = device_probe_and_attach(sc->sc_bus.bdev); + + if (err) { + device_printf(self, "XHCI halt/start/probe failed err=%d\n", err); + goto error; + } + return (0); + +error: + xhci_pci_detach(self); + return (ENXIO); +} + +static int +xhci_pci_detach(device_t self) +{ + struct xhci_softc *sc = device_get_softc(self); + device_t bdev; + + if (sc->sc_bus.bdev != NULL) { + bdev = sc->sc_bus.bdev; + device_detach(bdev); + device_delete_child(self, bdev); + } + /* during module unload there are lots of children leftover */ + device_delete_children(self); + + pci_disable_busmaster(self); + + if (sc->sc_irq_res && sc->sc_intr_hdl) { + + xhci_halt_controller(sc); + + bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); + sc->sc_intr_hdl = NULL; + } + if (sc->sc_irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); + sc->sc_irq_res = NULL; + } + if (sc->sc_io_res) { + bus_release_resource(self, SYS_RES_MEMORY, PCI_XHCI_CBMEM, + sc->sc_io_res); + sc->sc_io_res = NULL; + } + + xhci_uninit(sc); + + return (0); +} + +static int +xhci_pci_take_controller(device_t self) +{ + struct xhci_softc *sc = device_get_softc(self); + uint32_t cparams; + uint32_t eecp; + uint32_t eec; + uint16_t to; + uint8_t bios_sem; + + cparams = XREAD4(sc, capa, XHCI_HCSPARAMS0); + + eec = -1; + + /* Synchronise with the BIOS if it owns the controller. */ + for (eecp = XHCI_HCS0_XECP(cparams) << 2; eecp != 0 && XHCI_XECP_NEXT(eec); + eecp += XHCI_XECP_NEXT(eec) << 2) { + eec = XREAD4(sc, capa, eecp); + + if (XHCI_XECP_ID(eec) != XHCI_ID_USB_LEGACY) + continue; + bios_sem = XREAD1(sc, capa, eecp + + XHCI_XECP_BIOS_SEM); + if (bios_sem == 0) + continue; + device_printf(sc->sc_bus.bdev, "waiting for BIOS " + "to give up control\n"); + XWRITE1(sc, capa, eecp + + XHCI_XECP_OS_SEM, 1); + to = 500; + while (1) { + bios_sem = XREAD1(sc, capa, eecp + + XHCI_XECP_BIOS_SEM); + if (bios_sem == 0) + break; + + if (--to == 0) { + device_printf(sc->sc_bus.bdev, + "timed out waiting for BIOS\n"); + break; + } + usb_pause_mtx(NULL, hz / 100); /* wait 10ms */ + } + } + return (0); +} diff --git a/sys/bus/u4b/controller/xhcireg.h b/sys/bus/u4b/controller/xhcireg.h new file mode 100644 index 0000000000..b14dabcffa --- /dev/null +++ b/sys/bus/u4b/controller/xhcireg.h @@ -0,0 +1,218 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2010 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _XHCIREG_H_ +#define _XHCIREG_H_ + +/* XHCI PCI config registers */ +#define PCI_XHCI_CBMEM 0x10 /* configuration base MEM */ +#define PCI_XHCI_USBREV 0x60 /* RO USB protocol revision */ +#define PCI_USB_REV_3_0 0x30 /* USB 3.0 */ +#define PCI_XHCI_FLADJ 0x61 /* RW frame length adjust */ + +/* XHCI capability registers */ +#define XHCI_CAPLENGTH 0x00 /* RO capability */ +#define XHCI_RESERVED 0x01 /* Reserved */ +#define XHCI_HCIVERSION 0x02 /* RO Interface version number */ +#define XHCI_HCIVERSION_0_9 0x0090 /* xHCI version 0.9 */ +#define XHCI_HCIVERSION_1_0 0x0100 /* xHCI version 1.0 */ +#define XHCI_HCSPARAMS1 0x04 /* RO structual parameters 1 */ +#define XHCI_HCS1_DEVSLOT_MAX(x)((x) & 0xFF) +#define XHCI_HCS1_IRQ_MAX(x) (((x) >> 8) & 0x3FF) +#define XHCI_HCS1_N_PORTS(x) (((x) >> 24) & 0xFF) +#define XHCI_HCSPARAMS2 0x08 /* RO structual parameters 2 */ +#define XHCI_HCS2_IST(x) ((x) & 0xF) +#define XHCI_HCS2_ERST_MAX(x) (((x) >> 4) & 0xF) +#define XHCI_HCS2_SPR(x) (((x) >> 24) & 0x1) +#define XHCI_HCS2_SPB_MAX(x) (((x) >> 27) & 0x7F) +#define XHCI_HCSPARAMS3 0x0C /* RO structual parameters 3 */ +#define XHCI_HCS3_U1_DEL(x) ((x) & 0xFF) +#define XHCI_HCS3_U2_DEL(x) (((x) >> 16) & 0xFFFF) +#define XHCI_HCSPARAMS0 0x10 /* RO capability parameters */ +#define XHCI_HCS0_AC64(x) ((x) & 0x1) /* 64-bit capable */ +#define XHCI_HCS0_BNC(x) (((x) >> 1) & 0x1) /* BW negotiation */ +#define XHCI_HCS0_CSZ(x) (((x) >> 2) & 0x1) /* context size */ +#define XHCI_HCS0_PPC(x) (((x) >> 3) & 0x1) /* port power control */ +#define XHCI_HCS0_PIND(x) (((x) >> 4) & 0x1) /* port indicators */ +#define XHCI_HCS0_LHRC(x) (((x) >> 5) & 0x1) /* light HC reset */ +#define XHCI_HCS0_LTC(x) (((x) >> 6) & 0x1) /* latency tolerance msg */ +#define XHCI_HCS0_NSS(x) (((x) >> 7) & 0x1) /* no secondary sid */ +#define XHCI_HCS0_PSA_SZ_MAX(x) (((x) >> 12) & 0xF) /* max pri. stream array size */ +#define XHCI_HCS0_XECP(x) (((x) >> 16) & 0xFFFF) /* extended capabilities pointer */ +#define XHCI_DBOFF 0x14 /* RO doorbell offset */ +#define XHCI_RTSOFF 0x18 /* RO runtime register space offset */ + +/* XHCI operational registers. Offset given by XHCI_CAPLENGTH register */ +#define XHCI_USBCMD 0x00 /* XHCI command */ +#define XHCI_CMD_RS 0x00000001 /* RW Run/Stop */ +#define XHCI_CMD_HCRST 0x00000002 /* RW Host Controller Reset */ +#define XHCI_CMD_INTE 0x00000004 /* RW Interrupter Enable */ +#define XHCI_CMD_HSEE 0x00000008 /* RW Host System Error Enable */ +#define XHCI_CMD_LHCRST 0x00000080 /* RO/RW Light Host Controller Reset */ +#define XHCI_CMD_CSS 0x00000100 /* RW Controller Save State */ +#define XHCI_CMD_CRS 0x00000200 /* RW Controller Restore State */ +#define XHCI_CMD_EWE 0x00000400 /* RW Enable Wrap Event */ +#define XHCI_CMD_EU3S 0x00000800 /* RW Enable U3 MFINDEX Stop */ +#define XHCI_USBSTS 0x04 /* XHCI status */ +#define XHCI_STS_HCH 0x00000001 /* RO - Host Controller Halted */ +#define XHCI_STS_HSE 0x00000004 /* RW - Host System Error */ +#define XHCI_STS_EINT 0x00000008 /* RW - Event Interrupt */ +#define XHCI_STS_PCD 0x00000010 /* RW - Port Change Detect */ +#define XHCI_STS_SSS 0x00000100 /* RO - Save State Status */ +#define XHCI_STS_RSS 0x00000200 /* RO - Restore State Status */ +#define XHCI_STS_SRE 0x00000400 /* RW - Save/Restore Error */ +#define XHCI_STS_CNR 0x00000800 /* RO - Controller Not Ready */ +#define XHCI_STS_HCE 0x00001000 /* RO - Host Controller Error */ +#define XHCI_PAGESIZE 0x08 /* XHCI page size mask */ +#define XHCI_PAGESIZE_4K 0x00000001 /* 4K Page Size */ +#define XHCI_PAGESIZE_8K 0x00000002 /* 8K Page Size */ +#define XHCI_PAGESIZE_16K 0x00000004 /* 16K Page Size */ +#define XHCI_PAGESIZE_32K 0x00000008 /* 32K Page Size */ +#define XHCI_PAGESIZE_64K 0x00000010 /* 64K Page Size */ +#define XHCI_DNCTRL 0x14 /* XHCI device notification control */ +#define XHCI_DNCTRL_MASK(n) (1U << (n)) +#define XHCI_CRCR_LO 0x18 /* XHCI command ring control */ +#define XHCI_CRCR_LO_RCS 0x00000001 /* RW - consumer cycle state */ +#define XHCI_CRCR_LO_CS 0x00000002 /* RW - command stop */ +#define XHCI_CRCR_LO_CA 0x00000004 /* RW - command abort */ +#define XHCI_CRCR_LO_CRR 0x00000008 /* RW - command ring running */ +#define XHCI_CRCR_LO_MASK 0x0000000F +#define XHCI_CRCR_HI 0x1C /* XHCI command ring control */ +#define XHCI_DCBAAP_LO 0x30 /* XHCI dev context BA pointer */ +#define XHCI_DCBAAP_HI 0x34 /* XHCI dev context BA pointer */ +#define XHCI_CONFIG 0x38 +#define XHCI_CONFIG_SLOTS_MASK 0x000000FF /* RW - number of device slots enabled */ + +/* XHCI port status registers */ +#define XHCI_PORTSC(n) (0x3F0 + (0x10 * (n))) /* XHCI port status */ +#define XHCI_PS_CCS 0x00000001 /* RO - current connect status */ +#define XHCI_PS_PED 0x00000002 /* RW - port enabled / disabled */ +#define XHCI_PS_OCA 0x00000008 /* RO - over current active */ +#define XHCI_PS_PR 0x00000010 /* RW - port reset */ +#define XHCI_PS_PLS_GET(x) (((x) >> 5) & 0xF) /* RW - port link state */ +#define XHCI_PS_PLS_SET(x) (((x) & 0xF) << 5) /* RW - port link state */ +#define XHCI_PS_PP 0x00000200 /* RW - port power */ +#define XHCI_PS_SPEED_GET(x) (((x) >> 10) & 0xF) /* RO - port speed */ +#define XHCI_PS_PIC_GET(x) (((x) >> 14) & 0x3) /* RW - port indicator */ +#define XHCI_PS_PIC_SET(x) (((x) & 0x3) << 14) /* RW - port indicator */ +#define XHCI_PS_LWS 0x00010000 /* RW - port link state write strobe */ +#define XHCI_PS_CSC 0x00020000 /* RW - connect status change */ +#define XHCI_PS_PEC 0x00040000 /* RW - port enable/disable change */ +#define XHCI_PS_WRC 0x00080000 /* RW - warm port reset change */ +#define XHCI_PS_OCC 0x00100000 /* RW - over-current change */ +#define XHCI_PS_PRC 0x00200000 /* RW - port reset change */ +#define XHCI_PS_PLC 0x00400000 /* RW - port link state change */ +#define XHCI_PS_CEC 0x00800000 /* RW - config error change */ +#define XHCI_PS_CAS 0x01000000 /* RO - cold attach status */ +#define XHCI_PS_WCE 0x02000000 /* RW - wake on connect enable */ +#define XHCI_PS_WDE 0x04000000 /* RW - wake on disconnect enable */ +#define XHCI_PS_WOE 0x08000000 /* RW - wake on over-current enable */ +#define XHCI_PS_DR 0x40000000 /* RO - device removable */ +#define XHCI_PS_WPR 0x80000000U /* RW - warm port reset */ +#define XHCI_PS_CLEAR 0x80FF01FFU /* command bits */ + +#define XHCI_PORTPMSC(n) (0x3F4 + (0x10 * (n))) /* XHCI status and control */ +#define XHCI_PM3_U1TO_GET(x) (((x) >> 0) & 0xFF) /* RW - U1 timeout */ +#define XHCI_PM3_U1TO_SET(x) (((x) & 0xFF) << 0) /* RW - U1 timeout */ +#define XHCI_PM3_U2TO_GET(x) (((x) >> 8) & 0xFF) /* RW - U2 timeout */ +#define XHCI_PM3_U2TO_SET(x) (((x) & 0xFF) << 8) /* RW - U2 timeout */ +#define XHCI_PM3_FLA 0x00010000 /* RW - Force Link PM Accept */ +#define XHCI_PM2_L1S_GET(x) (((x) >> 0) & 0x7) /* RO - L1 status */ +#define XHCI_PM2_RWE 0x00000008 /* RW - remote wakup enable */ +#define XHCI_PM2_HIRD_GET(x) (((x) >> 4) & 0xF) /* RW - host initiated resume duration */ +#define XHCI_PM2_HIRD_SET(x) (((x) & 0xF) << 4) /* RW - host initiated resume duration */ +#define XHCI_PM2_L1SLOT_GET(x) (((x) >> 8) & 0xFF) /* RW - L1 device slot */ +#define XHCI_PM2_L1SLOT_SET(x) (((x) & 0xFF) << 8) /* RW - L1 device slot */ +#define XHCI_PM2_HLE 0x00010000 /* RW - hardware LPM enable */ +#define XHCI_PORTLI(n) (0x3F8 + (0x10 * (n))) /* XHCI port link info */ +#define XHCI_PLI3_ERR_GET(x) (((x) >> 0) & 0xFFFF) /* RO - port link errors */ +#define XHCI_PORTRSV(n) (0x3FC + (0x10 * (n))) /* XHCI port reserved */ + +/* XHCI runtime registers. Offset given by XHCI_CAPLENGTH + XHCI_RTSOFF registers */ +#define XHCI_MFINDEX 0x0000 /* RO - microframe index */ +#define XHCI_MFINDEX_GET(x) ((x) & 0x3FFF) +#define XHCI_IMAN(n) (0x0020 + (0x20 * (n))) /* XHCI interrupt management */ +#define XHCI_IMAN_INTR_PEND 0x00000001 /* RW - interrupt pending */ +#define XHCI_IMAN_INTR_ENA 0x00000002 /* RW - interrupt enable */ +#define XHCI_IMOD(n) (0x0024 + (0x20 * (n))) /* XHCI interrupt moderation */ +#define XHCI_IMOD_IVAL_GET(x) (((x) >> 0) & 0xFFFF) /* 250ns unit */ +#define XHCI_IMOD_IVAL_SET(x) (((x) & 0xFFFF) << 0) /* 250ns unit */ +#define XHCI_IMOD_ICNT_GET(x) (((x) >> 16) & 0xFFFF) /* 250ns unit */ +#define XHCI_IMOD_ICNT_SET(x) (((x) & 0xFFFF) << 16) /* 250ns unit */ +#define XHCI_IMOD_DEFAULT 0x000001F4U /* 8000 IRQ/second */ +#define XHCI_ERSTSZ(n) (0x0028 + (0x20 * (n))) /* XHCI event ring segment table size */ +#define XHCI_ERSTS_GET(x) ((x) & 0xFFFF) +#define XHCI_ERSTS_SET(x) ((x) & 0xFFFF) +#define XHCI_ERSTBA_LO(n) (0x0030 + (0x20 * (n))) /* XHCI event ring segment table BA */ +#define XHCI_ERSTBA_HI(n) (0x0034 + (0x20 * (n))) /* XHCI event ring segment table BA */ +#define XHCI_ERDP_LO(n) (0x0038 + (0x20 * (n))) /* XHCI event ring dequeue pointer */ +#define XHCI_ERDP_LO_SINDEX(x) ((x) & 0x7) /* RO - dequeue segment index */ +#define XHCI_ERDP_LO_BUSY 0x00000008 /* RW - event handler busy */ +#define XHCI_ERDP_HI(n) (0x003C + (0x20 * (n))) /* XHCI event ring dequeue pointer */ + +/* XHCI doorbell registers. Offset given by XHCI_CAPLENGTH + XHCI_DBOFF registers */ +#define XHCI_DOORBELL(n) (0x0000 + (4 * (n))) +#define XHCI_DB_TARGET_GET(x) ((x) & 0xFF) /* RW - doorbell target */ +#define XHCI_DB_TARGET_SET(x) ((x) & 0xFF) /* RW - doorbell target */ +#define XHCI_DB_SID_GET(x) (((x) >> 16) & 0xFFFF) /* RW - doorbell stream ID */ +#define XHCI_DB_SID_SET(x) (((x) & 0xFFFF) << 16) /* RW - doorbell stream ID */ + +/* XHCI legacy support */ +#define XHCI_XECP_ID(x) ((x) & 0xFF) +#define XHCI_XECP_NEXT(x) (((x) >> 8) & 0xFF) +#define XHCI_XECP_BIOS_SEM 0x0002 +#define XHCI_XECP_OS_SEM 0x0003 + +/* XHCI capability ID's */ +#define XHCI_ID_USB_LEGACY 0x0001 +#define XHCI_ID_PROTOCOLS 0x0002 +#define XHCI_ID_POWER_MGMT 0x0003 +#define XHCI_ID_VIRTUALIZATION 0x0004 +#define XHCI_ID_MSG_IRQ 0x0005 +#define XHCI_ID_USB_LOCAL_MEM 0x0006 + +/* XHCI register R/W wrappers */ +#define XREAD1(sc, what, a) \ + bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, \ + (a) + (sc)->sc_##what##_off) +#define XREAD2(sc, what, a) \ + bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, \ + (a) + (sc)->sc_##what##_off) +#define XREAD4(sc, what, a) \ + bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, \ + (a) + (sc)->sc_##what##_off) +#define XWRITE1(sc, what, a, x) \ + bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, \ + (a) + (sc)->sc_##what##_off, (x)) +#define XWRITE2(sc, what, a, x) \ + bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, \ + (a) + (sc)->sc_##what##_off, (x)) +#define XWRITE4(sc, what, a, x) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, \ + (a) + (sc)->sc_##what##_off, (x)) + +#endif /* _XHCIREG_H_ */ diff --git a/sys/bus/u4b/devlist2h.awk b/sys/bus/u4b/devlist2h.awk new file mode 100644 index 0000000000..724b5a2576 --- /dev/null +++ b/sys/bus/u4b/devlist2h.awk @@ -0,0 +1,213 @@ +#! /usr/bin/awk -f +# $DragonFly: src/sys/bus/pci/devlist2h.awk,v 1.2 2004/02/19 20:47:56 joerg Exp $ +# $NetBSD: devlist2h.awk,v 1.7 2003/12/05 04:33:27 grant Exp $ +# +# Copyright (c) 1995, 1996 Christopher G. Demetriou +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. All advertising materials mentioning features or use of this software +# must display the following acknowledgement: +# This product includes software developed by Christopher G. Demetriou. +# 4. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +BEGIN { + nproducts = nvendors = blanklines = 0 + dfile="pcidevs_data.h" + hfile="pcidevs.h" +} +NR == 1 { + printf("/*\n") > dfile + printf(" * THIS FILE AUTOMATICALLY GENERATED. DO NOT EDIT.\n") \ + > dfile + printf(" */\n") > dfile + + printf("/*\n") > hfile + printf(" * THIS FILE AUTOMATICALLY GENERATED. DO NOT EDIT.\n") \ + > hfile + printf(" */\n") > hfile + + next +} +NF > 0 && $1 == "vendor" { + nvendors++ + + vendorindex[$2] = nvendors; # record index for this name, for later. + vendors[nvendors, 1] = $2; # name + vendors[nvendors, 2] = $3; # id + printf("#define\tPCI_VENDOR_%s\t%s\t", vendors[nvendors, 1], + vendors[nvendors, 2]) > hfile + + i = 3; f = 4; + + # comments + ocomment = oparen = 0 + if (f <= NF) { + printf("\t/* ") > hfile + ocomment = 1; + } + while (f <= NF) { + if ($f == "#") { + printf("(") > hfile + oparen = 1 + f++ + continue + } + if (oparen) { + printf("%s", $f) > hfile + if (f < NF) + printf(" ") > hfile + f++ + continue + } + vendors[nvendors, i] = $f + printf("%s", vendors[nvendors, i]) > hfile + if (f < NF) + printf(" ") > hfile + i++; f++; + } + if (oparen) + printf(")") > hfile + if (ocomment) + printf(" */") > hfile + printf("\n") > hfile + + next +} +NF > 0 && $1 == "product" { + nproducts++ + + products[nproducts, 1] = $2; # vendor name + products[nproducts, 2] = $3; # product id + products[nproducts, 3] = $4; # id + printf("#define\tPCI_PRODUCT_%s_%s\t%s\t", products[nproducts, 1], + products[nproducts, 2], products[nproducts, 3]) > hfile + + i=4; f = 5; + + # comments + ocomment = oparen = 0 + if (f <= NF) { + printf("\t/* ") > hfile + ocomment = 1; + } + while (f <= NF) { + if ($f == "#") { + printf("(") > hfile + oparen = 1 + f++ + continue + } + if (oparen) { + printf("%s", $f) > hfile + if (f < NF) + printf(" ") > hfile + f++ + continue + } + products[nproducts, i] = $f + printf("%s", products[nproducts, i]) > hfile + if (f < NF) + printf(" ") > hfile + i++; f++; + } + if (oparen) + printf(")") > hfile + if (ocomment) + printf(" */") > hfile + printf("\n") > hfile + + next +} +{ + if ($0 == "") + blanklines++ + print $0 > hfile + if (blanklines < 2) + print $0 > dfile +} +END { + # print out the match tables + + printf("\n") > dfile + + printf("const struct pci_knowndev pci_knowndevs[] = {\n") > dfile + for (i = 1; i <= nproducts; i++) { + printf("\t{\n") > dfile + printf("\t PCI_VENDOR_%s, PCI_PRODUCT_%s_%s,\n", + products[i, 1], products[i, 1], products[i, 2]) \ + > dfile + printf("\t ") > dfile + printf("0") > dfile + printf(",\n") > dfile + + vendi = vendorindex[products[i, 1]]; + printf("\t \"") > dfile + j = 3; + needspace = 0; + while ((vendi, j) in vendors) { + if (needspace) + printf(" ") > dfile + printf("%s", vendors[vendi, j]) > dfile + needspace = 1 + j++ + } + printf("\",\n") > dfile + + printf("\t \"") > dfile + j = 4; + needspace = 0; + while ((i, j) in products) { + if (needspace) + printf(" ") > dfile + printf("%s", products[i, j]) > dfile + needspace = 1 + j++ + } + printf("\",\n") > dfile + printf("\t},\n") > dfile + } + for (i = 1; i <= nvendors; i++) { + printf("\t{\n") > dfile + printf("\t PCI_VENDOR_%s, 0,\n", vendors[i, 1]) \ + > dfile + printf("\t PCI_KNOWNDEV_NOPROD,\n") \ + > dfile + printf("\t \"") > dfile + j = 3; + needspace = 0; + while ((i, j) in vendors) { + if (needspace) + printf(" ") > dfile + printf("%s", vendors[i, j]) > dfile + needspace = 1 + j++ + } + printf("\",\n") > dfile + printf("\t NULL,\n") > dfile + printf("\t},\n") > dfile + } + printf("\t{ 0, 0, 0, NULL, NULL, }\n") > dfile + printf("};\n") > dfile + close(dfile) + close(hfile) +} diff --git a/sys/bus/u4b/input/atp.c b/sys/bus/u4b/input/atp.c new file mode 100644 index 0000000000..6cfba78161 --- /dev/null +++ b/sys/bus/u4b/input/atp.c @@ -0,0 +1,2225 @@ +/*- + * Copyright (c) 2009 Rohit Grover + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR atp_debug +#include + +#include + +#define ATP_DRIVER_NAME "atp" + +/* + * Driver specific options: the following options may be set by + * `options' statements in the kernel configuration file. + */ + +/* The multiplier used to translate sensor reported positions to mickeys. */ +#ifndef ATP_SCALE_FACTOR +#define ATP_SCALE_FACTOR 48 +#endif + +/* + * This is the age (in microseconds) beyond which a touch is + * considered to be a slide; and therefore a tap event isn't registered. + */ +#ifndef ATP_TOUCH_TIMEOUT +#define ATP_TOUCH_TIMEOUT 125000 +#endif + +/* + * A double-tap followed by a single-finger slide is treated as a + * special gesture. The driver responds to this gesture by assuming a + * virtual button-press for the lifetime of the slide. The following + * threshold is the maximum time gap (in microseconds) between the two + * tap events preceding the slide for such a gesture. + */ +#ifndef ATP_DOUBLE_TAP_N_DRAG_THRESHOLD +#define ATP_DOUBLE_TAP_N_DRAG_THRESHOLD 200000 +#endif + +/* + * The device provides us only with pressure readings from an array of + * X and Y sensors; for our algorithms, we need to interpret groups + * (typically pairs) of X and Y readings as being related to a single + * finger stroke. We can relate X and Y readings based on their times + * of incidence. The coincidence window should be at least 10000us + * since it is used against values from getmicrotime(), which has a + * precision of around 10ms. + */ +#ifndef ATP_COINCIDENCE_THRESHOLD +#define ATP_COINCIDENCE_THRESHOLD 40000 /* unit: microseconds */ +#if ATP_COINCIDENCE_THRESHOLD > 100000 +#error "ATP_COINCIDENCE_THRESHOLD too large" +#endif +#endif /* #ifndef ATP_COINCIDENCE_THRESHOLD */ + +/* + * The wait duration (in microseconds) after losing a touch contact + * before zombied strokes are reaped and turned into button events. + */ +#define ATP_ZOMBIE_STROKE_REAP_WINDOW 50000 +#if ATP_ZOMBIE_STROKE_REAP_WINDOW > 100000 +#error "ATP_ZOMBIE_STROKE_REAP_WINDOW too large" +#endif + +/* end of driver specific options */ + + +/* Tunables */ +static SYSCTL_NODE(_hw_usb, OID_AUTO, atp, CTLFLAG_RW, 0, "USB atp"); + +#ifdef USB_DEBUG +enum atp_log_level { + ATP_LLEVEL_DISABLED = 0, + ATP_LLEVEL_ERROR, + ATP_LLEVEL_DEBUG, /* for troubleshooting */ + ATP_LLEVEL_INFO, /* for diagnostics */ +}; +static int atp_debug = ATP_LLEVEL_ERROR; /* the default is to only log errors */ +SYSCTL_INT(_hw_usb_atp, OID_AUTO, debug, CTLFLAG_RW, + &atp_debug, ATP_LLEVEL_ERROR, "ATP debug level"); +#endif /* USB_DEBUG */ + +static u_int atp_touch_timeout = ATP_TOUCH_TIMEOUT; +SYSCTL_UINT(_hw_usb_atp, OID_AUTO, touch_timeout, CTLFLAG_RW, + &atp_touch_timeout, 125000, "age threshold (in micros) for a touch"); + +static u_int atp_double_tap_threshold = ATP_DOUBLE_TAP_N_DRAG_THRESHOLD; +SYSCTL_UINT(_hw_usb_atp, OID_AUTO, double_tap_threshold, CTLFLAG_RW, + &atp_double_tap_threshold, ATP_DOUBLE_TAP_N_DRAG_THRESHOLD, + "maximum time (in micros) between a double-tap"); + +static u_int atp_mickeys_scale_factor = ATP_SCALE_FACTOR; +static int atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS); +SYSCTL_PROC(_hw_usb_atp, OID_AUTO, scale_factor, CTLTYPE_UINT | CTLFLAG_RW, + &atp_mickeys_scale_factor, sizeof(atp_mickeys_scale_factor), + atp_sysctl_scale_factor_handler, "IU", "movement scale factor"); + +static u_int atp_small_movement_threshold = ATP_SCALE_FACTOR >> 3; +SYSCTL_UINT(_hw_usb_atp, OID_AUTO, small_movement, CTLFLAG_RW, + &atp_small_movement_threshold, ATP_SCALE_FACTOR >> 3, + "the small movement black-hole for filtering noise"); +/* + * The movement threshold for a stroke; this is the maximum difference + * in position which will be resolved as a continuation of a stroke + * component. + */ +static u_int atp_max_delta_mickeys = ((3 * ATP_SCALE_FACTOR) >> 1); +SYSCTL_UINT(_hw_usb_atp, OID_AUTO, max_delta_mickeys, CTLFLAG_RW, + &atp_max_delta_mickeys, ((3 * ATP_SCALE_FACTOR) >> 1), + "max. mickeys-delta which will match against an existing stroke"); +/* + * Strokes which accumulate at least this amount of absolute movement + * from the aggregate of their components are considered as + * slides. Unit: mickeys. + */ +static u_int atp_slide_min_movement = (ATP_SCALE_FACTOR >> 3); +SYSCTL_UINT(_hw_usb_atp, OID_AUTO, slide_min_movement, CTLFLAG_RW, + &atp_slide_min_movement, (ATP_SCALE_FACTOR >> 3), + "strokes with at least this amt. of movement are considered slides"); + +/* + * The minimum age of a stroke for it to be considered mature; this + * helps filter movements (noise) from immature strokes. Units: interrupts. + */ +static u_int atp_stroke_maturity_threshold = 2; +SYSCTL_UINT(_hw_usb_atp, OID_AUTO, stroke_maturity_threshold, CTLFLAG_RW, + &atp_stroke_maturity_threshold, 2, + "the minimum age of a stroke for it to be considered mature"); + +/* Accept pressure readings from sensors only if above this value. */ +static u_int atp_sensor_noise_threshold = 2; +SYSCTL_UINT(_hw_usb_atp, OID_AUTO, sensor_noise_threshold, CTLFLAG_RW, + &atp_sensor_noise_threshold, 2, + "accept pressure readings from sensors only if above this value"); + +/* Ignore pressure spans with cumulative press. below this value. */ +static u_int atp_pspan_min_cum_pressure = 10; +SYSCTL_UINT(_hw_usb_atp, OID_AUTO, pspan_min_cum_pressure, CTLFLAG_RW, + &atp_pspan_min_cum_pressure, 10, + "ignore pressure spans with cumulative press. below this value"); + +/* Maximum allowed width for pressure-spans.*/ +static u_int atp_pspan_max_width = 4; +SYSCTL_UINT(_hw_usb_atp, OID_AUTO, pspan_max_width, CTLFLAG_RW, + &atp_pspan_max_width, 4, + "maximum allowed width (in sensors) for pressure-spans"); + +/* We support three payload protocols */ +typedef enum { + ATP_PROT_GEYSER1, + ATP_PROT_GEYSER2, + ATP_PROT_GEYSER3, +} atp_protocol; + +/* Define the various flavours of devices supported by this driver. */ +enum { + ATP_DEV_PARAMS_0, + ATP_DEV_PARAMS_PBOOK, + ATP_DEV_PARAMS_PBOOK_15A, + ATP_DEV_PARAMS_PBOOK_17, + ATP_N_DEV_PARAMS +}; +struct atp_dev_params { + u_int data_len; /* for sensor data */ + u_int n_xsensors; + u_int n_ysensors; + atp_protocol prot; +} atp_dev_params[ATP_N_DEV_PARAMS] = { + [ATP_DEV_PARAMS_0] = { + .data_len = 64, + .n_xsensors = 20, + .n_ysensors = 10, + .prot = ATP_PROT_GEYSER3 + }, + [ATP_DEV_PARAMS_PBOOK] = { + .data_len = 81, + .n_xsensors = 16, + .n_ysensors = 16, + .prot = ATP_PROT_GEYSER1 + }, + [ATP_DEV_PARAMS_PBOOK_15A] = { + .data_len = 64, + .n_xsensors = 15, + .n_ysensors = 9, + .prot = ATP_PROT_GEYSER2 + }, + [ATP_DEV_PARAMS_PBOOK_17] = { + .data_len = 81, + .n_xsensors = 26, + .n_ysensors = 16, + .prot = ATP_PROT_GEYSER1 + }, +}; + +static const STRUCT_USB_HOST_ID atp_devs[] = { + /* Core Duo MacBook & MacBook Pro */ + { USB_VPI(USB_VENDOR_APPLE, 0x0217, ATP_DEV_PARAMS_0) }, + { USB_VPI(USB_VENDOR_APPLE, 0x0218, ATP_DEV_PARAMS_0) }, + { USB_VPI(USB_VENDOR_APPLE, 0x0219, ATP_DEV_PARAMS_0) }, + + /* Core2 Duo MacBook & MacBook Pro */ + { USB_VPI(USB_VENDOR_APPLE, 0x021a, ATP_DEV_PARAMS_0) }, + { USB_VPI(USB_VENDOR_APPLE, 0x021b, ATP_DEV_PARAMS_0) }, + { USB_VPI(USB_VENDOR_APPLE, 0x021c, ATP_DEV_PARAMS_0) }, + + /* Core2 Duo MacBook3,1 */ + { USB_VPI(USB_VENDOR_APPLE, 0x0229, ATP_DEV_PARAMS_0) }, + { USB_VPI(USB_VENDOR_APPLE, 0x022a, ATP_DEV_PARAMS_0) }, + { USB_VPI(USB_VENDOR_APPLE, 0x022b, ATP_DEV_PARAMS_0) }, + + /* 12 inch PowerBook and iBook */ + { USB_VPI(USB_VENDOR_APPLE, 0x030a, ATP_DEV_PARAMS_PBOOK) }, + { USB_VPI(USB_VENDOR_APPLE, 0x030b, ATP_DEV_PARAMS_PBOOK) }, + + /* 15 inch PowerBook */ + { USB_VPI(USB_VENDOR_APPLE, 0x020e, ATP_DEV_PARAMS_PBOOK) }, + { USB_VPI(USB_VENDOR_APPLE, 0x020f, ATP_DEV_PARAMS_PBOOK) }, + { USB_VPI(USB_VENDOR_APPLE, 0x0215, ATP_DEV_PARAMS_PBOOK_15A) }, + + /* 17 inch PowerBook */ + { USB_VPI(USB_VENDOR_APPLE, 0x020d, ATP_DEV_PARAMS_PBOOK_17) }, + +}; + +/* + * The following structure captures the state of a pressure span along + * an axis. Each contact with the touchpad results in separate + * pressure spans along the two axes. + */ +typedef struct atp_pspan { + u_int width; /* in units of sensors */ + u_int cum; /* cumulative compression (from all sensors) */ + u_int cog; /* center of gravity */ + u_int loc; /* location (scaled using the mickeys factor) */ + boolean_t matched; /* to track pspans as they match against strokes. */ +} atp_pspan; + +typedef enum atp_stroke_type { + ATP_STROKE_TOUCH, + ATP_STROKE_SLIDE, +} atp_stroke_type; + +#define ATP_MAX_PSPANS_PER_AXIS 3 + +typedef struct atp_stroke_component { + /* Fields encapsulating the pressure-span. */ + u_int loc; /* location (scaled) */ + u_int cum_pressure; /* cumulative compression */ + u_int max_cum_pressure; /* max cumulative compression */ + boolean_t matched; /*to track components as they match against pspans.*/ + + /* Fields containing information about movement. */ + int delta_mickeys; /* change in location (un-smoothened movement)*/ + int pending; /* cum. of pending short movements */ + int movement; /* current smoothened movement */ +} atp_stroke_component; + +typedef enum atp_axis { + X = 0, + Y = 1 +} atp_axis; + +#define ATP_MAX_STROKES (2 * ATP_MAX_PSPANS_PER_AXIS) + +/* + * The following structure captures a finger contact with the + * touchpad. A stroke comprises two p-span components and some state. + */ +typedef struct atp_stroke { + atp_stroke_type type; + struct timeval ctime; /* create time; for coincident siblings. */ + u_int age; /* + * Unit: interrupts; we maintain + * this value in addition to + * 'ctime' in order to avoid the + * expensive call to microtime() + * at every interrupt. + */ + + atp_stroke_component components[2]; + u_int velocity_squared; /* + * Average magnitude (squared) + * of recent velocity. + */ + u_int cum_movement; /* cum. absolute movement so far */ + + uint32_t flags; /* the state of this stroke */ +#define ATSF_ZOMBIE 0x1 +} atp_stroke; + +#define ATP_FIFO_BUF_SIZE 8 /* bytes */ +#define ATP_FIFO_QUEUE_MAXLEN 50 /* units */ + +enum { + ATP_INTR_DT, + ATP_RESET, + ATP_N_TRANSFER, +}; + +struct atp_softc { + device_t sc_dev; + struct usb_device *sc_usb_device; +#define MODE_LENGTH 8 + char sc_mode_bytes[MODE_LENGTH]; /* device mode */ + struct mtx sc_mutex; /* for synchronization */ + struct usb_xfer *sc_xfer[ATP_N_TRANSFER]; + struct usb_fifo_sc sc_fifo; + + struct atp_dev_params *sc_params; + + mousehw_t sc_hw; + mousemode_t sc_mode; + u_int sc_pollrate; + mousestatus_t sc_status; + u_int sc_state; +#define ATP_ENABLED 0x01 +#define ATP_ZOMBIES_EXIST 0x02 +#define ATP_DOUBLE_TAP_DRAG 0x04 +#define ATP_VALID 0x08 + + u_int sc_left_margin; + u_int sc_right_margin; + + atp_stroke sc_strokes[ATP_MAX_STROKES]; + u_int sc_n_strokes; + + int8_t *sensor_data; /* from interrupt packet */ + int *base_x; /* base sensor readings */ + int *base_y; + int *cur_x; /* current sensor readings */ + int *cur_y; + int *pressure_x; /* computed pressures */ + int *pressure_y; + + u_int sc_idlecount; /* preceding idle interrupts */ +#define ATP_IDLENESS_THRESHOLD 10 + + struct timeval sc_reap_time; + struct timeval sc_reap_ctime; /*ctime of siblings to be reaped*/ +}; + +/* + * The last byte of the sensor data contains status bits; the + * following values define the meanings of these bits. + */ +enum atp_status_bits { + ATP_STATUS_BUTTON = (uint8_t)0x01, /* The button was pressed */ + ATP_STATUS_BASE_UPDATE = (uint8_t)0x04, /* Data from an untouched pad.*/ +}; + +typedef enum interface_mode { + RAW_SENSOR_MODE = (uint8_t)0x04, + HID_MODE = (uint8_t)0x08 +} interface_mode; + +/* + * function prototypes + */ +static usb_fifo_cmd_t atp_start_read; +static usb_fifo_cmd_t atp_stop_read; +static usb_fifo_open_t atp_open; +static usb_fifo_close_t atp_close; +static usb_fifo_ioctl_t atp_ioctl; + +static struct usb_fifo_methods atp_fifo_methods = { + .f_open = &atp_open, + .f_close = &atp_close, + .f_ioctl = &atp_ioctl, + .f_start_read = &atp_start_read, + .f_stop_read = &atp_stop_read, + .basename[0] = ATP_DRIVER_NAME, +}; + +/* device initialization and shutdown */ +static usb_error_t atp_req_get_report(struct usb_device *udev, void *data); +static int atp_set_device_mode(device_t dev, interface_mode mode); +static void atp_reset_callback(struct usb_xfer *, usb_error_t); +static int atp_enable(struct atp_softc *sc); +static void atp_disable(struct atp_softc *sc); +static int atp_softc_populate(struct atp_softc *); +static void atp_softc_unpopulate(struct atp_softc *); + +/* sensor interpretation */ +static __inline void atp_interpret_sensor_data(const int8_t *, u_int, atp_axis, + int *, atp_protocol); +static __inline void atp_get_pressures(int *, const int *, const int *, int); +static void atp_detect_pspans(int *, u_int, u_int, atp_pspan *, + u_int *); + +/* movement detection */ +static boolean_t atp_match_stroke_component(atp_stroke_component *, + const atp_pspan *, atp_stroke_type); +static void atp_match_strokes_against_pspans(struct atp_softc *, + atp_axis, atp_pspan *, u_int, u_int); +static boolean_t atp_update_strokes(struct atp_softc *, + atp_pspan *, u_int, atp_pspan *, u_int); +static __inline void atp_add_stroke(struct atp_softc *, const atp_pspan *, + const atp_pspan *); +static void atp_add_new_strokes(struct atp_softc *, atp_pspan *, + u_int, atp_pspan *, u_int); +static void atp_advance_stroke_state(struct atp_softc *, + atp_stroke *, boolean_t *); +static void atp_terminate_stroke(struct atp_softc *, u_int); +static __inline boolean_t atp_stroke_has_small_movement(const atp_stroke *); +static __inline void atp_update_pending_mickeys(atp_stroke_component *); +static void atp_compute_smoothening_scale_ratio(atp_stroke *, int *, + int *); +static boolean_t atp_compute_stroke_movement(atp_stroke *); + +/* tap detection */ +static __inline void atp_setup_reap_time(struct atp_softc *, struct timeval *); +static void atp_reap_zombies(struct atp_softc *, u_int *, u_int *); +static void atp_convert_to_slide(struct atp_softc *, atp_stroke *); + +/* updating fifo */ +static void atp_reset_buf(struct atp_softc *sc); +static void atp_add_to_queue(struct atp_softc *, int, int, uint32_t); + + +usb_error_t +atp_req_get_report(struct usb_device *udev, void *data) +{ + struct usb_device_request req; + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_REPORT; + USETW2(req.wValue, (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */); + USETW(req.wIndex, 0); + USETW(req.wLength, MODE_LENGTH); + + return (usbd_do_request(udev, NULL /* mutex */, &req, data)); +} + +static int +atp_set_device_mode(device_t dev, interface_mode mode) +{ + struct atp_softc *sc; + usb_device_request_t req; + usb_error_t err; + + if ((mode != RAW_SENSOR_MODE) && (mode != HID_MODE)) + return (ENXIO); + + sc = device_get_softc(dev); + + sc->sc_mode_bytes[0] = mode; + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */); + USETW(req.wIndex, 0); + USETW(req.wLength, MODE_LENGTH); + err = usbd_do_request(sc->sc_usb_device, NULL, &req, sc->sc_mode_bytes); + if (err != USB_ERR_NORMAL_COMPLETION) + return (ENXIO); + + return (0); +} + +void +atp_reset_callback(struct usb_xfer *xfer, usb_error_t error) +{ + usb_device_request_t req; + struct usb_page_cache *pc; + struct atp_softc *sc = usbd_xfer_softc(xfer); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + sc->sc_mode_bytes[0] = RAW_SENSOR_MODE; + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, + (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */); + USETW(req.wIndex, 0); + USETW(req.wLength, MODE_LENGTH); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + pc = usbd_xfer_get_frame(xfer, 1); + usbd_copy_in(pc, 0, sc->sc_mode_bytes, MODE_LENGTH); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, MODE_LENGTH); + usbd_xfer_set_frames(xfer, 2); + usbd_transfer_submit(xfer); + break; + + case USB_ST_TRANSFERRED: + default: + break; + } +} + +static int +atp_enable(struct atp_softc *sc) +{ + /* Allocate the dynamic buffers */ + if (atp_softc_populate(sc) != 0) { + atp_softc_unpopulate(sc); + return (ENOMEM); + } + + /* reset status */ + memset(sc->sc_strokes, 0, sizeof(sc->sc_strokes)); + sc->sc_n_strokes = 0; + memset(&sc->sc_status, 0, sizeof(sc->sc_status)); + sc->sc_idlecount = 0; + sc->sc_state |= ATP_ENABLED; + + DPRINTFN(ATP_LLEVEL_INFO, "enabled atp\n"); + return (0); +} + +static void +atp_disable(struct atp_softc *sc) +{ + atp_softc_unpopulate(sc); + + sc->sc_state &= ~(ATP_ENABLED | ATP_VALID); + DPRINTFN(ATP_LLEVEL_INFO, "disabled atp\n"); +} + +/* Allocate dynamic memory for some fields in softc. */ +static int +atp_softc_populate(struct atp_softc *sc) +{ + const struct atp_dev_params *params = sc->sc_params; + + if (params == NULL) { + DPRINTF("params uninitialized!\n"); + return (ENXIO); + } + if (params->data_len) { + sc->sensor_data = malloc(params->data_len * sizeof(int8_t), + M_USB, M_WAITOK); + if (sc->sensor_data == NULL) { + DPRINTF("mem for sensor_data\n"); + return (ENXIO); + } + } + + if (params->n_xsensors != 0) { + sc->base_x = malloc(params->n_xsensors * sizeof(*(sc->base_x)), + M_USB, M_WAITOK); + if (sc->base_x == NULL) { + DPRINTF("mem for sc->base_x\n"); + return (ENXIO); + } + + sc->cur_x = malloc(params->n_xsensors * sizeof(*(sc->cur_x)), + M_USB, M_WAITOK); + if (sc->cur_x == NULL) { + DPRINTF("mem for sc->cur_x\n"); + return (ENXIO); + } + + sc->pressure_x = + malloc(params->n_xsensors * sizeof(*(sc->pressure_x)), + M_USB, M_WAITOK); + if (sc->pressure_x == NULL) { + DPRINTF("mem. for pressure_x\n"); + return (ENXIO); + } + } + + if (params->n_ysensors != 0) { + sc->base_y = malloc(params->n_ysensors * sizeof(*(sc->base_y)), + M_USB, M_WAITOK); + if (sc->base_y == NULL) { + DPRINTF("mem for base_y\n"); + return (ENXIO); + } + + sc->cur_y = malloc(params->n_ysensors * sizeof(*(sc->cur_y)), + M_USB, M_WAITOK); + if (sc->cur_y == NULL) { + DPRINTF("mem for cur_y\n"); + return (ENXIO); + } + + sc->pressure_y = + malloc(params->n_ysensors * sizeof(*(sc->pressure_y)), + M_USB, M_WAITOK); + if (sc->pressure_y == NULL) { + DPRINTF("mem. for pressure_y\n"); + return (ENXIO); + } + } + + return (0); +} + +/* Free dynamic memory allocated for some fields in softc. */ +static void +atp_softc_unpopulate(struct atp_softc *sc) +{ + const struct atp_dev_params *params = sc->sc_params; + + if (params == NULL) { + return; + } + if (params->n_xsensors != 0) { + if (sc->base_x != NULL) { + free(sc->base_x, M_USB); + sc->base_x = NULL; + } + + if (sc->cur_x != NULL) { + free(sc->cur_x, M_USB); + sc->cur_x = NULL; + } + + if (sc->pressure_x != NULL) { + free(sc->pressure_x, M_USB); + sc->pressure_x = NULL; + } + } + if (params->n_ysensors != 0) { + if (sc->base_y != NULL) { + free(sc->base_y, M_USB); + sc->base_y = NULL; + } + + if (sc->cur_y != NULL) { + free(sc->cur_y, M_USB); + sc->cur_y = NULL; + } + + if (sc->pressure_y != NULL) { + free(sc->pressure_y, M_USB); + sc->pressure_y = NULL; + } + } + if (sc->sensor_data != NULL) { + free(sc->sensor_data, M_USB); + sc->sensor_data = NULL; + } +} + +/* + * Interpret the data from the X and Y pressure sensors. This function + * is called separately for the X and Y sensor arrays. The data in the + * USB packet is laid out in the following manner: + * + * sensor_data: + * --,--,Y1,Y2,--,Y3,Y4,--,Y5,...,Y10, ... X1,X2,--,X3,X4 + * indices: 0 1 2 3 4 5 6 7 8 ... 15 ... 20 21 22 23 24 + * + * '--' (in the above) indicates that the value is unimportant. + * + * Information about the above layout was obtained from the + * implementation of the AppleTouch driver in Linux. + * + * parameters: + * sensor_data + * raw sensor data from the USB packet. + * num + * The number of elements in the array 'arr'. + * axis + * Axis of data to fetch + * arr + * The array to be initialized with the readings. + * prot + * The protocol to use to interpret the data + */ +static __inline void +atp_interpret_sensor_data(const int8_t *sensor_data, u_int num, atp_axis axis, + int *arr, atp_protocol prot) +{ + u_int i; + u_int di; /* index into sensor data */ + + switch (prot) { + case ATP_PROT_GEYSER1: + /* + * For Geyser 1, the sensors are laid out in pairs + * every 5 bytes. + */ + for (i = 0, di = (axis == Y) ? 1 : 2; i < 8; di += 5, i++) { + arr[i] = sensor_data[di]; + arr[i+8] = sensor_data[di+2]; + if (axis == X && num > 16) + arr[i+16] = sensor_data[di+40]; + } + + break; + case ATP_PROT_GEYSER2: + case ATP_PROT_GEYSER3: + for (i = 0, di = (axis == Y) ? 2 : 20; i < num; /* empty */ ) { + arr[i++] = sensor_data[di++]; + arr[i++] = sensor_data[di++]; + di++; + } + break; + } +} + +static __inline void +atp_get_pressures(int *p, const int *cur, const int *base, int n) +{ + int i; + + for (i = 0; i < n; i++) { + p[i] = cur[i] - base[i]; + if (p[i] > 127) + p[i] -= 256; + if (p[i] < -127) + p[i] += 256; + if (p[i] < 0) + p[i] = 0; + + /* + * Shave off pressures below the noise-pressure + * threshold; this will reduce the contribution from + * lower pressure readings. + */ + if (p[i] <= atp_sensor_noise_threshold) + p[i] = 0; /* filter away noise */ + else + p[i] -= atp_sensor_noise_threshold; + } +} + +static void +atp_detect_pspans(int *p, u_int num_sensors, + u_int max_spans, /* max # of pspans permitted */ + atp_pspan *spans, /* finger spans */ + u_int *nspans_p) /* num spans detected */ +{ + u_int i; + int maxp; /* max pressure seen within a span */ + u_int num_spans = 0; + + enum atp_pspan_state { + ATP_PSPAN_INACTIVE, + ATP_PSPAN_INCREASING, + ATP_PSPAN_DECREASING, + } state; /* state of the pressure span */ + + /* + * The following is a simple state machine to track + * the phase of the pressure span. + */ + memset(spans, 0, max_spans * sizeof(atp_pspan)); + maxp = 0; + state = ATP_PSPAN_INACTIVE; + for (i = 0; i < num_sensors; i++) { + if (num_spans >= max_spans) + break; + + if (p[i] == 0) { + if (state == ATP_PSPAN_INACTIVE) { + /* + * There is no pressure information for this + * sensor, and we aren't tracking a finger. + */ + continue; + } else { + state = ATP_PSPAN_INACTIVE; + maxp = 0; + num_spans++; + } + } else { + switch (state) { + case ATP_PSPAN_INACTIVE: + state = ATP_PSPAN_INCREASING; + maxp = p[i]; + break; + + case ATP_PSPAN_INCREASING: + if (p[i] > maxp) + maxp = p[i]; + else if (p[i] <= (maxp >> 1)) + state = ATP_PSPAN_DECREASING; + break; + + case ATP_PSPAN_DECREASING: + if (p[i] > p[i - 1]) { + /* + * This is the beginning of + * another span; change state + * to give the appearance that + * we're starting from an + * inactive span, and then + * re-process this reading in + * the next iteration. + */ + num_spans++; + state = ATP_PSPAN_INACTIVE; + maxp = 0; + i--; + continue; + } + break; + } + + /* Update the finger span with this reading. */ + spans[num_spans].width++; + spans[num_spans].cum += p[i]; + spans[num_spans].cog += p[i] * (i + 1); + } + } + if (state != ATP_PSPAN_INACTIVE) + num_spans++; /* close the last finger span */ + + /* post-process the spans */ + for (i = 0; i < num_spans; i++) { + /* filter away unwanted pressure spans */ + if ((spans[i].cum < atp_pspan_min_cum_pressure) || + (spans[i].width > atp_pspan_max_width)) { + if ((i + 1) < num_spans) { + memcpy(&spans[i], &spans[i + 1], + (num_spans - i - 1) * sizeof(atp_pspan)); + i--; + } + num_spans--; + continue; + } + + /* compute this span's representative location */ + spans[i].loc = spans[i].cog * atp_mickeys_scale_factor / + spans[i].cum; + + spans[i].matched = FALSE; /* not yet matched against a stroke */ + } + + *nspans_p = num_spans; +} + +/* + * Match a pressure-span against a stroke-component. If there is a + * match, update the component's state and return TRUE. + */ +static boolean_t +atp_match_stroke_component(atp_stroke_component *component, + const atp_pspan *pspan, atp_stroke_type stroke_type) +{ + int delta_mickeys; + u_int min_pressure; + + delta_mickeys = pspan->loc - component->loc; + + if (abs(delta_mickeys) > atp_max_delta_mickeys) + return (FALSE); /* the finger span is too far out; no match */ + + component->loc = pspan->loc; + + /* + * A sudden and significant increase in a pspan's cumulative + * pressure indicates the incidence of a new finger + * contact. This usually revises the pspan's + * centre-of-gravity, and hence the location of any/all + * matching stroke component(s). But such a change should + * *not* be interpreted as a movement. + */ + if (pspan->cum > ((3 * component->cum_pressure) >> 1)) + delta_mickeys = 0; + + component->cum_pressure = pspan->cum; + if (pspan->cum > component->max_cum_pressure) + component->max_cum_pressure = pspan->cum; + + /* + * Disregard the component's movement if its cumulative + * pressure drops below a fraction of the maximum; this + * fraction is determined based on the stroke's type. + */ + if (stroke_type == ATP_STROKE_TOUCH) + min_pressure = (3 * component->max_cum_pressure) >> 2; + else + min_pressure = component->max_cum_pressure >> 2; + if (component->cum_pressure < min_pressure) + delta_mickeys = 0; + + component->delta_mickeys = delta_mickeys; + return (TRUE); +} + +static void +atp_match_strokes_against_pspans(struct atp_softc *sc, atp_axis axis, + atp_pspan *pspans, u_int n_pspans, u_int repeat_count) +{ + u_int i, j; + u_int repeat_index = 0; + + /* Determine the index of the multi-span. */ + if (repeat_count) { + u_int cum = 0; + for (i = 0; i < n_pspans; i++) { + if (pspans[i].cum > cum) { + repeat_index = i; + cum = pspans[i].cum; + } + } + } + + for (i = 0; i < sc->sc_n_strokes; i++) { + atp_stroke *stroke = &sc->sc_strokes[i]; + if (stroke->components[axis].matched) + continue; /* skip matched components */ + + for (j = 0; j < n_pspans; j++) { + if (pspans[j].matched) + continue; /* skip matched pspans */ + + if (atp_match_stroke_component( + &stroke->components[axis], &pspans[j], + stroke->type)) { + /* There is a match. */ + stroke->components[axis].matched = TRUE; + + /* Take care to repeat at the multi-span. */ + if ((repeat_count > 0) && (j == repeat_index)) + repeat_count--; + else + pspans[j].matched = TRUE; + + break; /* skip to the next stroke */ + } + } /* loop over pspans */ + } /* loop over strokes */ +} + +/* + * Update strokes by matching against current pressure-spans. + * Return TRUE if any movement is detected. + */ +static boolean_t +atp_update_strokes(struct atp_softc *sc, atp_pspan *pspans_x, + u_int n_xpspans, atp_pspan *pspans_y, u_int n_ypspans) +{ + u_int i, j; + atp_stroke *stroke; + boolean_t movement = FALSE; + u_int repeat_count = 0; + + /* Reset X and Y components of all strokes as unmatched. */ + for (i = 0; i < sc->sc_n_strokes; i++) { + stroke = &sc->sc_strokes[i]; + stroke->components[X].matched = FALSE; + stroke->components[Y].matched = FALSE; + } + + /* + * Usually, the X and Y pspans come in pairs (the common case + * being a single pair). It is possible, however, that + * multiple contacts resolve to a single pspan along an + * axis, as illustrated in the following: + * + * F = finger-contact + * + * pspan pspan + * +-----------------------+ + * | . . | + * | . . | + * | . . | + * | . . | + * pspan |.........F......F | + * | | + * | | + * | | + * +-----------------------+ + * + * + * The above case can be detected by a difference in the + * number of X and Y pspans. When this happens, X and Y pspans + * aren't easy to pair or match against strokes. + * + * When X and Y pspans differ in number, the axis with the + * smaller number of pspans is regarded as having a repeating + * pspan (or a multi-pspan)--in the above illustration, the + * Y-axis has a repeating pspan. Our approach is to try to + * match the multi-pspan repeatedly against strokes. The + * difference between the number of X and Y pspans gives us a + * crude repeat_count for matching multi-pspans--i.e. the + * multi-pspan along the Y axis (above) has a repeat_count of 1. + */ + repeat_count = abs(n_xpspans - n_ypspans); + + atp_match_strokes_against_pspans(sc, X, pspans_x, n_xpspans, + (((repeat_count != 0) && ((n_xpspans < n_ypspans))) ? + repeat_count : 0)); + atp_match_strokes_against_pspans(sc, Y, pspans_y, n_ypspans, + (((repeat_count != 0) && (n_ypspans < n_xpspans)) ? + repeat_count : 0)); + + /* Update the state of strokes based on the above pspan matches. */ + for (i = 0; i < sc->sc_n_strokes; i++) { + stroke = &sc->sc_strokes[i]; + if (stroke->components[X].matched && + stroke->components[Y].matched) { + atp_advance_stroke_state(sc, stroke, &movement); + } else { + /* + * At least one component of this stroke + * didn't match against current pspans; + * terminate it. + */ + atp_terminate_stroke(sc, i); + } + } + + /* Add new strokes for pairs of unmatched pspans */ + for (i = 0; i < n_xpspans; i++) { + if (pspans_x[i].matched == FALSE) break; + } + for (j = 0; j < n_ypspans; j++) { + if (pspans_y[j].matched == FALSE) break; + } + if ((i < n_xpspans) && (j < n_ypspans)) { +#ifdef USB_DEBUG + if (atp_debug >= ATP_LLEVEL_INFO) { + printf("unmatched pspans:"); + for (; i < n_xpspans; i++) { + if (pspans_x[i].matched) + continue; + printf(" X:[loc:%u,cum:%u]", + pspans_x[i].loc, pspans_x[i].cum); + } + for (; j < n_ypspans; j++) { + if (pspans_y[j].matched) + continue; + printf(" Y:[loc:%u,cum:%u]", + pspans_y[j].loc, pspans_y[j].cum); + } + printf("\n"); + } +#endif /* USB_DEBUG */ + if ((n_xpspans == 1) && (n_ypspans == 1)) + /* The common case of a single pair of new pspans. */ + atp_add_stroke(sc, &pspans_x[0], &pspans_y[0]); + else + atp_add_new_strokes(sc, + pspans_x, n_xpspans, + pspans_y, n_ypspans); + } + +#ifdef USB_DEBUG + if (atp_debug >= ATP_LLEVEL_INFO) { + for (i = 0; i < sc->sc_n_strokes; i++) { + atp_stroke *stroke = &sc->sc_strokes[i]; + + printf(" %s%clc:%u,dm:%d,pnd:%d,cum:%d,max:%d,mv:%d%c" + ",%clc:%u,dm:%d,pnd:%d,cum:%d,max:%d,mv:%d%c", + (stroke->flags & ATSF_ZOMBIE) ? "zomb:" : "", + (stroke->type == ATP_STROKE_TOUCH) ? '[' : '<', + stroke->components[X].loc, + stroke->components[X].delta_mickeys, + stroke->components[X].pending, + stroke->components[X].cum_pressure, + stroke->components[X].max_cum_pressure, + stroke->components[X].movement, + (stroke->type == ATP_STROKE_TOUCH) ? ']' : '>', + (stroke->type == ATP_STROKE_TOUCH) ? '[' : '<', + stroke->components[Y].loc, + stroke->components[Y].delta_mickeys, + stroke->components[Y].pending, + stroke->components[Y].cum_pressure, + stroke->components[Y].max_cum_pressure, + stroke->components[Y].movement, + (stroke->type == ATP_STROKE_TOUCH) ? ']' : '>'); + } + if (sc->sc_n_strokes) + printf("\n"); + } +#endif /* USB_DEBUG */ + + return (movement); +} + +/* Initialize a stroke using a pressure-span. */ +static __inline void +atp_add_stroke(struct atp_softc *sc, const atp_pspan *pspan_x, + const atp_pspan *pspan_y) +{ + atp_stroke *stroke; + + if (sc->sc_n_strokes >= ATP_MAX_STROKES) + return; + stroke = &sc->sc_strokes[sc->sc_n_strokes]; + + memset(stroke, 0, sizeof(atp_stroke)); + + /* + * Strokes begin as potential touches. If a stroke survives + * longer than a threshold, or if it records significant + * cumulative movement, then it is considered a 'slide'. + */ + stroke->type = ATP_STROKE_TOUCH; + microtime(&stroke->ctime); + stroke->age = 1; /* Unit: interrupts */ + + stroke->components[X].loc = pspan_x->loc; + stroke->components[X].cum_pressure = pspan_x->cum; + stroke->components[X].max_cum_pressure = pspan_x->cum; + stroke->components[X].matched = TRUE; + + stroke->components[Y].loc = pspan_y->loc; + stroke->components[Y].cum_pressure = pspan_y->cum; + stroke->components[Y].max_cum_pressure = pspan_y->cum; + stroke->components[Y].matched = TRUE; + + sc->sc_n_strokes++; + if (sc->sc_n_strokes > 1) { + /* Reset double-tap-n-drag if we have more than one strokes. */ + sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG; + } + + DPRINTFN(ATP_LLEVEL_INFO, "[%u,%u], time: %u,%ld\n", + stroke->components[X].loc, + stroke->components[Y].loc, + (unsigned int)stroke->ctime.tv_sec, + (unsigned long int)stroke->ctime.tv_usec); +} + +static void +atp_add_new_strokes(struct atp_softc *sc, atp_pspan *pspans_x, + u_int n_xpspans, atp_pspan *pspans_y, u_int n_ypspans) +{ + int i, j; + atp_pspan spans[2][ATP_MAX_PSPANS_PER_AXIS]; + u_int nspans[2]; + + /* Copy unmatched pspans into the local arrays. */ + for (i = 0, nspans[X] = 0; i < n_xpspans; i++) { + if (pspans_x[i].matched == FALSE) { + spans[X][nspans[X]] = pspans_x[i]; + nspans[X]++; + } + } + for (j = 0, nspans[Y] = 0; j < n_ypspans; j++) { + if (pspans_y[j].matched == FALSE) { + spans[Y][nspans[Y]] = pspans_y[j]; + nspans[Y]++; + } + } + + if (nspans[X] == nspans[Y]) { + /* Create new strokes from pairs of unmatched pspans */ + for (i = 0, j = 0; (i < nspans[X]) && (j < nspans[Y]); i++, j++) + atp_add_stroke(sc, &spans[X][i], &spans[Y][j]); + } else { + u_int cum = 0; + atp_axis repeat_axis; /* axis with multi-pspans */ + u_int repeat_count; /* repeat count for the multi-pspan*/ + u_int repeat_index = 0; /* index of the multi-span */ + + repeat_axis = (nspans[X] > nspans[Y]) ? Y : X; + repeat_count = abs(nspans[X] - nspans[Y]); + for (i = 0; i < nspans[repeat_axis]; i++) { + if (spans[repeat_axis][i].cum > cum) { + repeat_index = i; + cum = spans[repeat_axis][i].cum; + } + } + + /* Create new strokes from pairs of unmatched pspans */ + i = 0, j = 0; + for (; (i < nspans[X]) && (j < nspans[Y]); i++, j++) { + atp_add_stroke(sc, &spans[X][i], &spans[Y][j]); + + /* Take care to repeat at the multi-pspan. */ + if (repeat_count > 0) { + if ((repeat_axis == X) && + (repeat_index == i)) { + i--; /* counter loop increment */ + repeat_count--; + } else if ((repeat_axis == Y) && + (repeat_index == j)) { + j--; /* counter loop increment */ + repeat_count--; + } + } + } + } +} + +/* + * Advance the state of this stroke--and update the out-parameter + * 'movement' as a side-effect. + */ +void +atp_advance_stroke_state(struct atp_softc *sc, atp_stroke *stroke, + boolean_t *movement) +{ + stroke->age++; + if (stroke->age <= atp_stroke_maturity_threshold) { + /* Avoid noise from immature strokes. */ + stroke->components[X].delta_mickeys = 0; + stroke->components[Y].delta_mickeys = 0; + } + + /* Revitalize stroke if it had previously been marked as a zombie. */ + if (stroke->flags & ATSF_ZOMBIE) + stroke->flags &= ~ATSF_ZOMBIE; + + if (atp_compute_stroke_movement(stroke)) + *movement = TRUE; + + if (stroke->type != ATP_STROKE_TOUCH) + return; + + /* Convert touch strokes to slides upon detecting movement or age. */ + if (stroke->cum_movement >= atp_slide_min_movement) { + atp_convert_to_slide(sc, stroke); + } else { + /* If a touch stroke is found to be older than the + * touch-timeout threshold, it should be converted to + * a slide; except if there is a co-incident sibling + * with a later creation time. + * + * When multiple fingers make contact with the + * touchpad, they are likely to be separated in their + * times of incidence. During a multi-finger tap, + * therefore, the last finger to make + * contact--i.e. the one with the latest + * 'ctime'--should be used to determine how the + * touch-siblings get treated; otherwise older + * siblings may lapse the touch-timeout and get + * converted into slides prematurely. The following + * loop determines if there exists another touch + * stroke with a larger 'ctime' than the current + * stroke (NOTE: zombies with a larger 'ctime' are + * also considered) . + */ + + u_int i; + for (i = 0; i < sc->sc_n_strokes; i++) { + if ((&sc->sc_strokes[i] == stroke) || + (sc->sc_strokes[i].type != ATP_STROKE_TOUCH)) + continue; + + if (timevalcmp(&sc->sc_strokes[i].ctime, + &stroke->ctime, >)) + break; + } + if (i == sc->sc_n_strokes) { + /* Found no other touch stroke with a larger 'ctime'. */ + struct timeval tdiff; + + /* Compute the stroke's age. */ + getmicrotime(&tdiff); + if (timevalcmp(&tdiff, &stroke->ctime, >)) + timevalsub(&tdiff, &stroke->ctime); + else { + /* + * If we are here, it is because getmicrotime + * reported the current time as being behind + * the stroke's start time; getmicrotime can + * be imprecise. + */ + tdiff.tv_sec = 0; + tdiff.tv_usec = 0; + } + + if ((tdiff.tv_sec > (atp_touch_timeout / 1000000)) || + ((tdiff.tv_sec == (atp_touch_timeout / 1000000)) && + (tdiff.tv_usec >= + (atp_touch_timeout % 1000000)))) + atp_convert_to_slide(sc, stroke); + } + } +} + +/* Switch a given touch stroke to being a slide. */ +void +atp_convert_to_slide(struct atp_softc *sc, atp_stroke *stroke) +{ + stroke->type = ATP_STROKE_SLIDE; + + /* Are we at the beginning of a double-click-n-drag? */ + if ((sc->sc_n_strokes == 1) && + ((sc->sc_state & ATP_ZOMBIES_EXIST) == 0) && + timevalcmp(&stroke->ctime, &sc->sc_reap_time, >)) { + struct timeval delta; + struct timeval window = { + atp_double_tap_threshold / 1000000, + atp_double_tap_threshold % 1000000 + }; + + delta = stroke->ctime; + timevalsub(&delta, &sc->sc_reap_time); + if (timevalcmp(&delta, &window, <=)) + sc->sc_state |= ATP_DOUBLE_TAP_DRAG; + } +} + +/* + * Terminate a stroke. While SLIDE strokes are dropped, TOUCH strokes + * are retained as zombies so as to reap all their siblings together; + * this helps establish the number of fingers involved in the tap. + */ +static void +atp_terminate_stroke(struct atp_softc *sc, + u_int index) /* index of the stroke to be terminated */ +{ + atp_stroke *s = &sc->sc_strokes[index]; + + if (s->flags & ATSF_ZOMBIE) { + return; + } + + if ((s->type == ATP_STROKE_TOUCH) && + (s->age > atp_stroke_maturity_threshold)) { + s->flags |= ATSF_ZOMBIE; + + /* If no zombies exist, then prepare to reap zombies later. */ + if ((sc->sc_state & ATP_ZOMBIES_EXIST) == 0) { + atp_setup_reap_time(sc, &s->ctime); + sc->sc_state |= ATP_ZOMBIES_EXIST; + } + } else { + /* Drop this stroke. */ + memcpy(&sc->sc_strokes[index], &sc->sc_strokes[index + 1], + (sc->sc_n_strokes - index - 1) * sizeof(atp_stroke)); + sc->sc_n_strokes--; + + /* + * Reset the double-click-n-drag at the termination of + * any slide stroke. + */ + sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG; + } +} + +static __inline boolean_t +atp_stroke_has_small_movement(const atp_stroke *stroke) +{ + return ((abs(stroke->components[X].delta_mickeys) <= + atp_small_movement_threshold) && + (abs(stroke->components[Y].delta_mickeys) <= + atp_small_movement_threshold)); +} + +/* + * Accumulate delta_mickeys into the component's 'pending' bucket; if + * the aggregate exceeds the small_movement_threshold, then retain + * delta_mickeys for later. + */ +static __inline void +atp_update_pending_mickeys(atp_stroke_component *component) +{ + component->pending += component->delta_mickeys; + if (abs(component->pending) <= atp_small_movement_threshold) + component->delta_mickeys = 0; + else { + /* + * Penalise pending mickeys for having accumulated + * over short deltas. This operation has the effect of + * scaling down the cumulative contribution of short + * movements. + */ + component->pending -= (component->delta_mickeys << 1); + } +} + + +static void +atp_compute_smoothening_scale_ratio(atp_stroke *stroke, int *numerator, + int *denominator) +{ + int dxdt; + int dydt; + u_int vel_squared; /* Square of the velocity vector's magnitude. */ + u_int vel_squared_smooth; + + /* Table holding (10 * sqrt(x)) for x between 1 and 256. */ + static uint8_t sqrt_table[256] = { + 10, 14, 17, 20, 22, 24, 26, 28, + 30, 31, 33, 34, 36, 37, 38, 40, + 41, 42, 43, 44, 45, 46, 47, 48, + 50, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 60, 61, 62, 63, + 64, 64, 65, 66, 67, 67, 68, 69, + 70, 70, 71, 72, 72, 73, 74, 74, + 75, 76, 76, 77, 78, 78, 79, 80, + 80, 81, 81, 82, 83, 83, 84, 84, + 85, 86, 86, 87, 87, 88, 88, 89, + 90, 90, 91, 91, 92, 92, 93, 93, + 94, 94, 95, 95, 96, 96, 97, 97, + 98, 98, 99, 100, 100, 100, 101, 101, + 102, 102, 103, 103, 104, 104, 105, 105, + 106, 106, 107, 107, 108, 108, 109, 109, + 110, 110, 110, 111, 111, 112, 112, 113, + 113, 114, 114, 114, 115, 115, 116, 116, + 117, 117, 117, 118, 118, 119, 119, 120, + 120, 120, 121, 121, 122, 122, 122, 123, + 123, 124, 124, 124, 125, 125, 126, 126, + 126, 127, 127, 128, 128, 128, 129, 129, + 130, 130, 130, 131, 131, 131, 132, 132, + 133, 133, 133, 134, 134, 134, 135, 135, + 136, 136, 136, 137, 137, 137, 138, 138, + 138, 139, 139, 140, 140, 140, 141, 141, + 141, 142, 142, 142, 143, 143, 143, 144, + 144, 144, 145, 145, 145, 146, 146, 146, + 147, 147, 147, 148, 148, 148, 149, 149, + 150, 150, 150, 150, 151, 151, 151, 152, + 152, 152, 153, 153, 153, 154, 154, 154, + 155, 155, 155, 156, 156, 156, 157, 157, + 157, 158, 158, 158, 159, 159, 159, 160 + }; + const u_int N = sizeof(sqrt_table) / sizeof(sqrt_table[0]); + + dxdt = stroke->components[X].delta_mickeys; + dydt = stroke->components[Y].delta_mickeys; + + *numerator = 0, *denominator = 0; /* default values. */ + + /* Compute a smoothened magnitude_squared of the stroke's velocity. */ + vel_squared = dxdt * dxdt + dydt * dydt; + vel_squared_smooth = (3 * stroke->velocity_squared + vel_squared) >> 2; + stroke->velocity_squared = vel_squared_smooth; /* retained as history */ + if ((vel_squared == 0) || (vel_squared_smooth == 0)) + return; /* returning (numerator == 0) will imply zero movement*/ + + /* + * In order to determine the overall movement scale factor, + * we're actually interested in the effect of smoothening upon + * the *magnitude* of velocity; i.e. we need to compute the + * square-root of (vel_squared_smooth / vel_squared) in the + * form of a numerator and denominator. + */ + + /* Keep within the bounds of the square-root table. */ + while ((vel_squared > N) || (vel_squared_smooth > N)) { + /* Dividing uniformly by 2 won't disturb the final ratio. */ + vel_squared >>= 1; + vel_squared_smooth >>= 1; + } + + *numerator = sqrt_table[vel_squared_smooth - 1]; + *denominator = sqrt_table[vel_squared - 1]; +} + +/* + * Compute a smoothened value for the stroke's movement from + * delta_mickeys in the X and Y components. + */ +static boolean_t +atp_compute_stroke_movement(atp_stroke *stroke) +{ + int num; /* numerator of scale ratio */ + int denom; /* denominator of scale ratio */ + + /* + * Short movements are added first to the 'pending' bucket, + * and then acted upon only when their aggregate exceeds a + * threshold. This has the effect of filtering away movement + * noise. + */ + if (atp_stroke_has_small_movement(stroke)) { + atp_update_pending_mickeys(&stroke->components[X]); + atp_update_pending_mickeys(&stroke->components[Y]); + } else { /* large movement */ + /* clear away any pending mickeys if there are large movements*/ + stroke->components[X].pending = 0; + stroke->components[Y].pending = 0; + } + + /* Get the scale ratio and smoothen movement. */ + atp_compute_smoothening_scale_ratio(stroke, &num, &denom); + if ((num == 0) || (denom == 0)) { + stroke->components[X].movement = 0; + stroke->components[Y].movement = 0; + stroke->velocity_squared >>= 1; /* Erode velocity_squared. */ + } else { + stroke->components[X].movement = + (stroke->components[X].delta_mickeys * num) / denom; + stroke->components[Y].movement = + (stroke->components[Y].delta_mickeys * num) / denom; + + stroke->cum_movement += + abs(stroke->components[X].movement) + + abs(stroke->components[Y].movement); + } + + return ((stroke->components[X].movement != 0) || + (stroke->components[Y].movement != 0)); +} + +static __inline void +atp_setup_reap_time(struct atp_softc *sc, struct timeval *tvp) +{ + struct timeval reap_window = { + ATP_ZOMBIE_STROKE_REAP_WINDOW / 1000000, + ATP_ZOMBIE_STROKE_REAP_WINDOW % 1000000 + }; + + microtime(&sc->sc_reap_time); + timevaladd(&sc->sc_reap_time, &reap_window); + + sc->sc_reap_ctime = *tvp; /* ctime to reap */ +} + +static void +atp_reap_zombies(struct atp_softc *sc, u_int *n_reaped, u_int *reaped_xlocs) +{ + u_int i; + atp_stroke *stroke; + + *n_reaped = 0; + for (i = 0; i < sc->sc_n_strokes; i++) { + struct timeval tdiff; + + stroke = &sc->sc_strokes[i]; + + if ((stroke->flags & ATSF_ZOMBIE) == 0) + continue; + + /* Compare this stroke's ctime with the ctime being reaped. */ + if (timevalcmp(&stroke->ctime, &sc->sc_reap_ctime, >=)) { + tdiff = stroke->ctime; + timevalsub(&tdiff, &sc->sc_reap_ctime); + } else { + tdiff = sc->sc_reap_ctime; + timevalsub(&tdiff, &stroke->ctime); + } + + if ((tdiff.tv_sec > (ATP_COINCIDENCE_THRESHOLD / 1000000)) || + ((tdiff.tv_sec == (ATP_COINCIDENCE_THRESHOLD / 1000000)) && + (tdiff.tv_usec > (ATP_COINCIDENCE_THRESHOLD % 1000000)))) { + continue; /* Skip non-siblings. */ + } + + /* + * Reap this sibling zombie stroke. + */ + + if (reaped_xlocs != NULL) + reaped_xlocs[*n_reaped] = stroke->components[X].loc; + + /* Erase the stroke from the sc. */ + memcpy(&stroke[i], &stroke[i + 1], + (sc->sc_n_strokes - i - 1) * sizeof(atp_stroke)); + sc->sc_n_strokes--; + + *n_reaped += 1; + --i; /* Decr. i to keep it unchanged for the next iteration */ + } + + DPRINTFN(ATP_LLEVEL_INFO, "reaped %u zombies\n", *n_reaped); + + /* There could still be zombies remaining in the system. */ + for (i = 0; i < sc->sc_n_strokes; i++) { + stroke = &sc->sc_strokes[i]; + if (stroke->flags & ATSF_ZOMBIE) { + DPRINTFN(ATP_LLEVEL_INFO, "zombies remain!\n"); + atp_setup_reap_time(sc, &stroke->ctime); + return; + } + } + + /* If we reach here, then no more zombies remain. */ + sc->sc_state &= ~ATP_ZOMBIES_EXIST; +} + + +/* Device methods. */ +static device_probe_t atp_probe; +static device_attach_t atp_attach; +static device_detach_t atp_detach; +static usb_callback_t atp_intr; + +static const struct usb_config atp_config[ATP_N_TRANSFER] = { + [ATP_INTR_DT] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = { + .pipe_bof = 1, + .short_xfer_ok = 1, + }, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &atp_intr, + }, + [ATP_RESET] = { + .type = UE_CONTROL, + .endpoint = 0, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request) + MODE_LENGTH, + .callback = &atp_reset_callback, + .interval = 0, /* no pre-delay */ + }, +}; + +static int +atp_probe(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + + if ((uaa->info.bInterfaceClass != UICLASS_HID) || + (uaa->info.bInterfaceProtocol != UIPROTO_MOUSE)) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(atp_devs, sizeof(atp_devs), uaa)); +} + +static int +atp_attach(device_t dev) +{ + struct atp_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + usb_error_t err; + + DPRINTFN(ATP_LLEVEL_INFO, "sc=%p\n", sc); + + sc->sc_dev = dev; + sc->sc_usb_device = uaa->device; + + /* + * By default the touchpad behaves like an HID device, sending + * packets with reportID = 2. Such reports contain only + * limited information--they encode movement deltas and button + * events,--but do not include data from the pressure + * sensors. The device input mode can be switched from HID + * reports to raw sensor data using vendor-specific USB + * control commands; but first the mode must be read. + */ + err = atp_req_get_report(sc->sc_usb_device, sc->sc_mode_bytes); + if (err != USB_ERR_NORMAL_COMPLETION) { + DPRINTF("failed to read device mode (%d)\n", err); + return (ENXIO); + } + + if (atp_set_device_mode(dev, RAW_SENSOR_MODE) != 0) { + DPRINTF("failed to set mode to 'RAW_SENSOR' (%d)\n", err); + return (ENXIO); + } + + mtx_init(&sc->sc_mutex, "atpmtx", NULL, MTX_DEF | MTX_RECURSE); + + err = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, atp_config, + ATP_N_TRANSFER, sc, &sc->sc_mutex); + + if (err) { + DPRINTF("error=%s\n", usbd_errstr(err)); + goto detach; + } + + if (usb_fifo_attach(sc->sc_usb_device, sc, &sc->sc_mutex, + &atp_fifo_methods, &sc->sc_fifo, + device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644)) { + goto detach; + } + + device_set_usb_desc(dev); + + sc->sc_params = &atp_dev_params[uaa->driver_info]; + + sc->sc_hw.buttons = 3; + sc->sc_hw.iftype = MOUSE_IF_USB; + sc->sc_hw.type = MOUSE_PAD; + sc->sc_hw.model = MOUSE_MODEL_GENERIC; + sc->sc_hw.hwid = 0; + sc->sc_mode.protocol = MOUSE_PROTO_MSC; + sc->sc_mode.rate = -1; + sc->sc_mode.resolution = MOUSE_RES_UNKNOWN; + sc->sc_mode.accelfactor = 0; + sc->sc_mode.level = 0; + sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; + + sc->sc_state = 0; + + sc->sc_left_margin = atp_mickeys_scale_factor; + sc->sc_right_margin = (sc->sc_params->n_xsensors - 1) * + atp_mickeys_scale_factor; + + return (0); + +detach: + atp_detach(dev); + return (ENOMEM); +} + +static int +atp_detach(device_t dev) +{ + struct atp_softc *sc; + + sc = device_get_softc(dev); + if (sc->sc_state & ATP_ENABLED) { + mtx_lock(&sc->sc_mutex); + atp_disable(sc); + mtx_unlock(&sc->sc_mutex); + } + + usb_fifo_detach(&sc->sc_fifo); + + usbd_transfer_unsetup(sc->sc_xfer, ATP_N_TRANSFER); + + mtx_destroy(&sc->sc_mutex); + + return (0); +} + +static void +atp_intr(struct usb_xfer *xfer, usb_error_t error) +{ + struct atp_softc *sc = usbd_xfer_softc(xfer); + int len; + struct usb_page_cache *pc; + uint8_t status_bits; + atp_pspan pspans_x[ATP_MAX_PSPANS_PER_AXIS]; + atp_pspan pspans_y[ATP_MAX_PSPANS_PER_AXIS]; + u_int n_xpspans = 0, n_ypspans = 0; + u_int reaped_xlocs[ATP_MAX_STROKES]; + u_int tap_fingers = 0; + + usbd_xfer_status(xfer, &len, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (len > sc->sc_params->data_len) { + DPRINTFN(ATP_LLEVEL_ERROR, + "truncating large packet from %u to %u bytes\n", + len, sc->sc_params->data_len); + len = sc->sc_params->data_len; + } + if (len < sc->sc_params->data_len) + goto tr_setup; + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, sc->sensor_data, sc->sc_params->data_len); + + /* Interpret sensor data */ + atp_interpret_sensor_data(sc->sensor_data, + sc->sc_params->n_xsensors, X, sc->cur_x, + sc->sc_params->prot); + atp_interpret_sensor_data(sc->sensor_data, + sc->sc_params->n_ysensors, Y, sc->cur_y, + sc->sc_params->prot); + + /* + * If this is the initial update (from an untouched + * pad), we should set the base values for the sensor + * data; deltas with respect to these base values can + * be used as pressure readings subsequently. + */ + status_bits = sc->sensor_data[sc->sc_params->data_len - 1]; + if ((sc->sc_params->prot == ATP_PROT_GEYSER3 && + (status_bits & ATP_STATUS_BASE_UPDATE)) || + !(sc->sc_state & ATP_VALID)) { + memcpy(sc->base_x, sc->cur_x, + sc->sc_params->n_xsensors * sizeof(*(sc->base_x))); + memcpy(sc->base_y, sc->cur_y, + sc->sc_params->n_ysensors * sizeof(*(sc->base_y))); + sc->sc_state |= ATP_VALID; + goto tr_setup; + } + + /* Get pressure readings and detect p-spans for both axes. */ + atp_get_pressures(sc->pressure_x, sc->cur_x, sc->base_x, + sc->sc_params->n_xsensors); + atp_detect_pspans(sc->pressure_x, sc->sc_params->n_xsensors, + ATP_MAX_PSPANS_PER_AXIS, + pspans_x, &n_xpspans); + atp_get_pressures(sc->pressure_y, sc->cur_y, sc->base_y, + sc->sc_params->n_ysensors); + atp_detect_pspans(sc->pressure_y, sc->sc_params->n_ysensors, + ATP_MAX_PSPANS_PER_AXIS, + pspans_y, &n_ypspans); + + /* Update strokes with new pspans to detect movements. */ + sc->sc_status.flags &= ~MOUSE_POSCHANGED; + if (atp_update_strokes(sc, + pspans_x, n_xpspans, + pspans_y, n_ypspans)) + sc->sc_status.flags |= MOUSE_POSCHANGED; + + /* Reap zombies if it is time. */ + if (sc->sc_state & ATP_ZOMBIES_EXIST) { + struct timeval now; + + getmicrotime(&now); + if (timevalcmp(&now, &sc->sc_reap_time, >=)) + atp_reap_zombies(sc, &tap_fingers, + reaped_xlocs); + } + + sc->sc_status.flags &= ~MOUSE_STDBUTTONSCHANGED; + sc->sc_status.obutton = sc->sc_status.button; + + /* Get the state of the physical buttton. */ + sc->sc_status.button = (status_bits & ATP_STATUS_BUTTON) ? + MOUSE_BUTTON1DOWN : 0; + if (sc->sc_status.button != 0) { + /* Reset DOUBLE_TAP_N_DRAG if the button is pressed. */ + sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG; + } else if (sc->sc_state & ATP_DOUBLE_TAP_DRAG) { + /* Assume a button-press with DOUBLE_TAP_N_DRAG. */ + sc->sc_status.button = MOUSE_BUTTON1DOWN; + } + + sc->sc_status.flags |= + sc->sc_status.button ^ sc->sc_status.obutton; + if (sc->sc_status.flags & MOUSE_STDBUTTONSCHANGED) { + DPRINTFN(ATP_LLEVEL_INFO, "button %s\n", + ((sc->sc_status.button & MOUSE_BUTTON1DOWN) ? + "pressed" : "released")); + } else if ((sc->sc_status.obutton == 0) && + (sc->sc_status.button == 0) && + (tap_fingers != 0)) { + /* Ignore single-finger taps at the edges. */ + if ((tap_fingers == 1) && + ((reaped_xlocs[0] <= sc->sc_left_margin) || + (reaped_xlocs[0] > sc->sc_right_margin))) { + tap_fingers = 0; + } + DPRINTFN(ATP_LLEVEL_INFO, + "tap_fingers: %u\n", tap_fingers); + } + + if (sc->sc_status.flags & + (MOUSE_POSCHANGED | MOUSE_STDBUTTONSCHANGED)) { + int dx, dy; + u_int n_movements; + + dx = 0, dy = 0, n_movements = 0; + for (u_int i = 0; i < sc->sc_n_strokes; i++) { + atp_stroke *stroke = &sc->sc_strokes[i]; + + if ((stroke->components[X].movement) || + (stroke->components[Y].movement)) { + dx += stroke->components[X].movement; + dy += stroke->components[Y].movement; + n_movements++; + } + } + /* + * Disregard movement if multiple + * strokes record motion. + */ + if (n_movements != 1) + dx = 0, dy = 0; + + sc->sc_status.dx += dx; + sc->sc_status.dy += dy; + atp_add_to_queue(sc, dx, -dy, sc->sc_status.button); + } + + if (tap_fingers != 0) { + /* Add a pair of events (button-down and button-up). */ + switch (tap_fingers) { + case 1: atp_add_to_queue(sc, 0, 0, MOUSE_BUTTON1DOWN); + break; + case 2: atp_add_to_queue(sc, 0, 0, MOUSE_BUTTON2DOWN); + break; + case 3: atp_add_to_queue(sc, 0, 0, MOUSE_BUTTON3DOWN); + break; + default: break;/* handle taps of only up to 3 fingers */ + } + atp_add_to_queue(sc, 0, 0, 0); /* button release */ + } + + /* + * The device continues to trigger interrupts at a + * fast rate even after touchpad activity has + * stopped. Upon detecting that the device has + * remained idle beyond a threshold, we reinitialize + * it to silence the interrupts. + */ + if ((sc->sc_status.flags == 0) && + (sc->sc_n_strokes == 0) && + (sc->sc_status.button == 0)) { + sc->sc_idlecount++; + if (sc->sc_idlecount >= ATP_IDLENESS_THRESHOLD) { + DPRINTFN(ATP_LLEVEL_INFO, "idle\n"); + + /* + * Use the last frame before we go idle for + * calibration on pads which do not send + * calibration frames. + */ + if (sc->sc_params->prot < ATP_PROT_GEYSER3) { + memcpy(sc->base_x, sc->cur_x, + sc->sc_params->n_xsensors * + sizeof(*(sc->base_x))); + memcpy(sc->base_y, sc->cur_y, + sc->sc_params->n_ysensors * + sizeof(*(sc->base_y))); + } + + sc->sc_idlecount = 0; + usbd_transfer_start(sc->sc_xfer[ATP_RESET]); + } + } else { + sc->sc_idlecount = 0; + } + + case USB_ST_SETUP: + tr_setup: + /* check if we can put more data into the FIFO */ + if (usb_fifo_put_bytes_max( + sc->sc_fifo.fp[USB_FIFO_RX]) != 0) { + usbd_xfer_set_frame_len(xfer, 0, + sc->sc_params->data_len); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } + + return; +} + +static void +atp_add_to_queue(struct atp_softc *sc, int dx, int dy, uint32_t buttons_in) +{ + uint32_t buttons_out; + uint8_t buf[8]; + + dx = imin(dx, 254); dx = imax(dx, -256); + dy = imin(dy, 254); dy = imax(dy, -256); + + buttons_out = MOUSE_MSC_BUTTONS; + if (buttons_in & MOUSE_BUTTON1DOWN) + buttons_out &= ~MOUSE_MSC_BUTTON1UP; + else if (buttons_in & MOUSE_BUTTON2DOWN) + buttons_out &= ~MOUSE_MSC_BUTTON2UP; + else if (buttons_in & MOUSE_BUTTON3DOWN) + buttons_out &= ~MOUSE_MSC_BUTTON3UP; + + DPRINTFN(ATP_LLEVEL_INFO, "dx=%d, dy=%d, buttons=%x\n", + dx, dy, buttons_out); + + /* Encode the mouse data in standard format; refer to mouse(4) */ + buf[0] = sc->sc_mode.syncmask[1]; + buf[0] |= buttons_out; + buf[1] = dx >> 1; + buf[2] = dy >> 1; + buf[3] = dx - (dx >> 1); + buf[4] = dy - (dy >> 1); + /* Encode extra bytes for level 1 */ + if (sc->sc_mode.level == 1) { + buf[5] = 0; /* dz */ + buf[6] = 0; /* dz - (dz / 2) */ + buf[7] = MOUSE_SYS_EXTBUTTONS; /* Extra buttons all up. */ + } + + usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf, + sc->sc_mode.packetsize, 1); +} + +static void +atp_reset_buf(struct atp_softc *sc) +{ + /* reset read queue */ + usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]); +} + +static void +atp_start_read(struct usb_fifo *fifo) +{ + struct atp_softc *sc = usb_fifo_softc(fifo); + int rate; + + /* Check if we should override the default polling interval */ + rate = sc->sc_pollrate; + /* Range check rate */ + if (rate > 1000) + rate = 1000; + /* Check for set rate */ + if ((rate > 0) && (sc->sc_xfer[ATP_INTR_DT] != NULL)) { + /* Stop current transfer, if any */ + usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]); + /* Set new interval */ + usbd_xfer_set_interval(sc->sc_xfer[ATP_INTR_DT], 1000 / rate); + /* Only set pollrate once */ + sc->sc_pollrate = 0; + } + + usbd_transfer_start(sc->sc_xfer[ATP_INTR_DT]); +} + +static void +atp_stop_read(struct usb_fifo *fifo) +{ + struct atp_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]); +} + + +static int +atp_open(struct usb_fifo *fifo, int fflags) +{ + DPRINTFN(ATP_LLEVEL_INFO, "\n"); + + if (fflags & FREAD) { + struct atp_softc *sc = usb_fifo_softc(fifo); + int rc; + + if (sc->sc_state & ATP_ENABLED) + return (EBUSY); + + if (usb_fifo_alloc_buffer(fifo, + ATP_FIFO_BUF_SIZE, ATP_FIFO_QUEUE_MAXLEN)) { + return (ENOMEM); + } + + rc = atp_enable(sc); + if (rc != 0) { + usb_fifo_free_buffer(fifo); + return (rc); + } + } + + return (0); +} + +static void +atp_close(struct usb_fifo *fifo, int fflags) +{ + if (fflags & FREAD) { + struct atp_softc *sc = usb_fifo_softc(fifo); + + atp_disable(sc); + usb_fifo_free_buffer(fifo); + } +} + +int +atp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags) +{ + struct atp_softc *sc = usb_fifo_softc(fifo); + mousemode_t mode; + int error = 0; + + mtx_lock(&sc->sc_mutex); + + switch(cmd) { + case MOUSE_GETHWINFO: + *(mousehw_t *)addr = sc->sc_hw; + break; + case MOUSE_GETMODE: + *(mousemode_t *)addr = sc->sc_mode; + break; + case MOUSE_SETMODE: + mode = *(mousemode_t *)addr; + + if (mode.level == -1) + /* Don't change the current setting */ + ; + else if ((mode.level < 0) || (mode.level > 1)) { + error = EINVAL; + goto done; + } + sc->sc_mode.level = mode.level; + sc->sc_pollrate = mode.rate; + sc->sc_hw.buttons = 3; + + if (sc->sc_mode.level == 0) { + sc->sc_mode.protocol = MOUSE_PROTO_MSC; + sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; + } else if (sc->sc_mode.level == 1) { + sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE; + sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC; + } + atp_reset_buf(sc); + break; + case MOUSE_GETLEVEL: + *(int *)addr = sc->sc_mode.level; + break; + case MOUSE_SETLEVEL: + if (*(int *)addr < 0 || *(int *)addr > 1) { + error = EINVAL; + goto done; + } + sc->sc_mode.level = *(int *)addr; + sc->sc_hw.buttons = 3; + + if (sc->sc_mode.level == 0) { + sc->sc_mode.protocol = MOUSE_PROTO_MSC; + sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; + } else if (sc->sc_mode.level == 1) { + sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE; + sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC; + } + atp_reset_buf(sc); + break; + case MOUSE_GETSTATUS: { + mousestatus_t *status = (mousestatus_t *)addr; + + *status = sc->sc_status; + sc->sc_status.obutton = sc->sc_status.button; + sc->sc_status.button = 0; + sc->sc_status.dx = 0; + sc->sc_status.dy = 0; + sc->sc_status.dz = 0; + + if (status->dx || status->dy || status->dz) + status->flags |= MOUSE_POSCHANGED; + if (status->button != status->obutton) + status->flags |= MOUSE_BUTTONSCHANGED; + break; + } + default: + error = ENOTTY; + } + +done: + mtx_unlock(&sc->sc_mutex); + return (error); +} + +static int +atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS) +{ + int error; + u_int tmp; + u_int prev_mickeys_scale_factor; + + prev_mickeys_scale_factor = atp_mickeys_scale_factor; + + tmp = atp_mickeys_scale_factor; + error = sysctl_handle_int(oidp, &tmp, 0, req); + if (error != 0 || req->newptr == NULL) + return (error); + + if (tmp == prev_mickeys_scale_factor) + return (0); /* no change */ + + atp_mickeys_scale_factor = tmp; + DPRINTFN(ATP_LLEVEL_INFO, "%s: resetting mickeys_scale_factor to %u\n", + ATP_DRIVER_NAME, tmp); + + /* Update dependent thresholds. */ + if (atp_small_movement_threshold == (prev_mickeys_scale_factor >> 3)) + atp_small_movement_threshold = atp_mickeys_scale_factor >> 3; + if (atp_max_delta_mickeys == ((3 * prev_mickeys_scale_factor) >> 1)) + atp_max_delta_mickeys = ((3 * atp_mickeys_scale_factor) >>1); + if (atp_slide_min_movement == (prev_mickeys_scale_factor >> 3)) + atp_slide_min_movement = atp_mickeys_scale_factor >> 3; + + return (0); +} + +static device_method_t atp_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, atp_probe), + DEVMETHOD(device_attach, atp_attach), + DEVMETHOD(device_detach, atp_detach), + { 0, 0 } +}; + +static driver_t atp_driver = { + ATP_DRIVER_NAME, + atp_methods, + sizeof(struct atp_softc) +}; + +static devclass_t atp_devclass; + +DRIVER_MODULE(atp, uhub, atp_driver, atp_devclass, NULL, 0); +MODULE_DEPEND(atp, usb, 1, 1, 1); +MODULE_VERSION(atp, 1); diff --git a/sys/bus/u4b/input/uep.c b/sys/bus/u4b/input/uep.c new file mode 100644 index 0000000000..e534b27894 --- /dev/null +++ b/sys/bus/u4b/input/uep.c @@ -0,0 +1,443 @@ +/*- + * Copyright 2010, Gleb Smirnoff + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * http://home.eeti.com.tw/web20/drivers/Software%20Programming%20Guide_v2.0.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#include +#include +#include + +#define USB_DEBUG_VAR uep_debug +#include + +#ifdef USB_DEBUG +static int uep_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uep, CTLFLAG_RW, 0, "USB uep"); +SYSCTL_INT(_hw_usb_uep, OID_AUTO, debug, CTLFLAG_RW, + &uep_debug, 0, "Debug level"); +#endif + +#define UEP_MAX_X 2047 +#define UEP_MAX_Y 2047 + +#define UEP_DOWN 0x01 +#define UEP_PACKET_LEN_MAX 16 +#define UEP_PACKET_LEN_REPORT 5 +#define UEP_PACKET_LEN_REPORT2 6 +#define UEP_PACKET_DIAG 0x0a +#define UEP_PACKET_REPORT_MASK 0xe0 +#define UEP_PACKET_REPORT 0x80 +#define UEP_PACKET_REPORT_PRESSURE 0xc0 +#define UEP_PACKET_REPORT_PLAYER 0xa0 +#define UEP_PACKET_LEN_MASK + +#define UEP_FIFO_BUF_SIZE 8 /* bytes */ +#define UEP_FIFO_QUEUE_MAXLEN 50 /* units */ + +enum { + UEP_INTR_DT, + UEP_N_TRANSFER, +}; + +struct uep_softc { + struct mtx mtx; + + struct usb_xfer *xfer[UEP_N_TRANSFER]; + struct usb_fifo_sc fifo; + + u_int pollrate; + u_int state; +#define UEP_ENABLED 0x01 + + /* Reassembling buffer. */ + u_char buf[UEP_PACKET_LEN_MAX]; + uint8_t buf_len; +}; + +static usb_callback_t uep_intr_callback; + +static device_probe_t uep_probe; +static device_attach_t uep_attach; +static device_detach_t uep_detach; + +static usb_fifo_cmd_t uep_start_read; +static usb_fifo_cmd_t uep_stop_read; +static usb_fifo_open_t uep_open; +static usb_fifo_close_t uep_close; + +static void uep_put_queue(struct uep_softc *, u_char *); + +static struct usb_fifo_methods uep_fifo_methods = { + .f_open = &uep_open, + .f_close = &uep_close, + .f_start_read = &uep_start_read, + .f_stop_read = &uep_stop_read, + .basename[0] = "uep", +}; + +static int +get_pkt_len(u_char *buf) +{ + if (buf[0] == UEP_PACKET_DIAG) { + int len; + + len = buf[1] + 2; + if (len > UEP_PACKET_LEN_MAX) { + DPRINTF("bad packet len %u\n", len); + return (UEP_PACKET_LEN_MAX); + } + + return (len); + } + + switch (buf[0] & UEP_PACKET_REPORT_MASK) { + case UEP_PACKET_REPORT: + return (UEP_PACKET_LEN_REPORT); + case UEP_PACKET_REPORT_PRESSURE: + case UEP_PACKET_REPORT_PLAYER: + case UEP_PACKET_REPORT_PRESSURE | UEP_PACKET_REPORT_PLAYER: + return (UEP_PACKET_LEN_REPORT2); + default: + DPRINTF("bad packet len 0\n"); + return (0); + } +} + +static void +uep_process_pkt(struct uep_softc *sc, u_char *buf) +{ + int32_t x, y; + + if ((buf[0] & 0xFE) != 0x80) { + DPRINTF("bad input packet format 0x%.2x\n", buf[0]); + return; + } + + /* + * Packet format is 5 bytes: + * + * 1000000T + * 0000AAAA + * 0AAAAAAA + * 0000BBBB + * 0BBBBBBB + * + * T: 1=touched 0=not touched + * A: bits of axis A position, MSB to LSB + * B: bits of axis B position, MSB to LSB + * + * For the unit I have, which is CTF1020-S from CarTFT.com, + * A = X and B = Y. But in NetBSD uep(4) it is other way round :) + * + * The controller sends a stream of T=1 events while the + * panel is touched, followed by a single T=0 event. + * + */ + + x = (buf[1] << 7) | buf[2]; + y = (buf[3] << 7) | buf[4]; + + DPRINTFN(2, "x %u y %u\n", x, y); + + uep_put_queue(sc, buf); +} + +static void +uep_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uep_softc *sc = usbd_xfer_softc(xfer); + int len; + + usbd_xfer_status(xfer, &len, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + { + struct usb_page_cache *pc; + u_char buf[17], *p; + int pkt_len; + + if (len > sizeof(buf)) { + DPRINTF("bad input length %d\n", len); + goto tr_setup; + } + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, len); + + /* + * The below code mimics Linux a lot. I don't know + * why NetBSD reads complete packets, but we need + * to reassamble 'em like Linux does (tries?). + */ + if (sc->buf_len > 0) { + int res; + + if (sc->buf_len == 1) + sc->buf[1] = buf[0]; + + if ((pkt_len = get_pkt_len(sc->buf)) == 0) + goto tr_setup; + + res = pkt_len - sc->buf_len; + memcpy(sc->buf + sc->buf_len, buf, res); + uep_process_pkt(sc, sc->buf); + sc->buf_len = 0; + + p = buf + res; + len -= res; + } else + p = buf; + + if (len == 1) { + sc->buf[0] = buf[0]; + sc->buf_len = 1; + + goto tr_setup; + } + + while (len > 0) { + if ((pkt_len = get_pkt_len(p)) == 0) + goto tr_setup; + + /* full packet: process */ + if (pkt_len <= len) { + uep_process_pkt(sc, p); + } else { + /* incomplete packet: save in buffer */ + memcpy(sc->buf, p, len); + sc->buf_len = len; + } + p += pkt_len; + len -= pkt_len; + } + } + case USB_ST_SETUP: + tr_setup: + /* check if we can put more data into the FIFO */ + if (usb_fifo_put_bytes_max(sc->fifo.fp[USB_FIFO_RX]) != 0) { + usbd_xfer_set_frame_len(xfer, 0, + usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + } + break; + + default: + if (error != USB_ERR_CANCELLED) { + /* try clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static const struct usb_config uep_config[UEP_N_TRANSFER] = { + [UEP_INTR_DT] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &uep_intr_callback, + }, +}; + +static const STRUCT_USB_HOST_ID uep_devs[] = { + {USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL, 0)}, + {USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL2, 0)}, + {USB_VPI(USB_VENDOR_EGALAX2, USB_PRODUCT_EGALAX2_TPANEL, 0)}, +}; + +static int +uep_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != 0) + return (ENXIO); + if (uaa->info.bIfaceIndex != 0) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(uep_devs, sizeof(uep_devs), uaa)); +} + +static int +uep_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uep_softc *sc = device_get_softc(dev); + int error; + + device_set_usb_desc(dev); + + mtx_init(&sc->mtx, "uep lock", NULL, MTX_DEF); + + error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, + sc->xfer, uep_config, UEP_N_TRANSFER, sc, &sc->mtx); + + if (error) { + DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(error)); + goto detach; + } + + error = usb_fifo_attach(uaa->device, sc, &sc->mtx, &uep_fifo_methods, + &sc->fifo, device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + + if (error) { + DPRINTF("usb_fifo_attach error=%s\n", usbd_errstr(error)); + goto detach; + } + + sc->buf_len = 0; + + return (0); + +detach: + uep_detach(dev); + + return (ENOMEM); /* XXX */ +} + +static int +uep_detach(device_t dev) +{ + struct uep_softc *sc = device_get_softc(dev); + + usb_fifo_detach(&sc->fifo); + + usbd_transfer_unsetup(sc->xfer, UEP_N_TRANSFER); + + mtx_destroy(&sc->mtx); + + return (0); +} + +static void +uep_start_read(struct usb_fifo *fifo) +{ + struct uep_softc *sc = usb_fifo_softc(fifo); + u_int rate; + + if ((rate = sc->pollrate) > 1000) + rate = 1000; + + if (rate > 0 && sc->xfer[UEP_INTR_DT] != NULL) { + usbd_transfer_stop(sc->xfer[UEP_INTR_DT]); + usbd_xfer_set_interval(sc->xfer[UEP_INTR_DT], 1000 / rate); + sc->pollrate = 0; + } + + usbd_transfer_start(sc->xfer[UEP_INTR_DT]); +} + +static void +uep_stop_read(struct usb_fifo *fifo) +{ + struct uep_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_stop(sc->xfer[UEP_INTR_DT]); +} + +static void +uep_put_queue(struct uep_softc *sc, u_char *buf) +{ + usb_fifo_put_data_linear(sc->fifo.fp[USB_FIFO_RX], buf, + UEP_PACKET_LEN_REPORT, 1); +} + +static int +uep_open(struct usb_fifo *fifo, int fflags) +{ + if (fflags & FREAD) { + struct uep_softc *sc = usb_fifo_softc(fifo); + + if (sc->state & UEP_ENABLED) + return (EBUSY); + if (usb_fifo_alloc_buffer(fifo, UEP_FIFO_BUF_SIZE, + UEP_FIFO_QUEUE_MAXLEN)) + return (ENOMEM); + + sc->state |= UEP_ENABLED; + } + + return (0); +} + +static void +uep_close(struct usb_fifo *fifo, int fflags) +{ + if (fflags & FREAD) { + struct uep_softc *sc = usb_fifo_softc(fifo); + + sc->state &= ~(UEP_ENABLED); + usb_fifo_free_buffer(fifo); + } +} + +static devclass_t uep_devclass; + +static device_method_t uep_methods[] = { + DEVMETHOD(device_probe, uep_probe), + DEVMETHOD(device_attach, uep_attach), + DEVMETHOD(device_detach, uep_detach), + { 0, 0 }, +}; + +static driver_t uep_driver = { + .name = "uep", + .methods = uep_methods, + .size = sizeof(struct uep_softc), +}; + +DRIVER_MODULE(uep, uhub, uep_driver, uep_devclass, NULL, NULL); +MODULE_DEPEND(uep, usb, 1, 1, 1); +MODULE_VERSION(uep, 1); diff --git a/sys/bus/u4b/input/uhid.c b/sys/bus/u4b/input/uhid.c new file mode 100644 index 0000000000..929227b09c --- /dev/null +++ b/sys/bus/u4b/input/uhid.c @@ -0,0 +1,819 @@ +/* $NetBSD: uhid.c,v 1.46 2001/11/13 06:24:55 lukem Exp $ */ + +/* Also already merged from NetBSD: + * $NetBSD: uhid.c,v 1.54 2002/09/23 05:51:21 simonb Exp $ + */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbdevs.h" +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR uhid_debug +#include + +#include +#include + +#ifdef USB_DEBUG +static int uhid_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uhid, CTLFLAG_RW, 0, "USB uhid"); +SYSCTL_INT(_hw_usb_uhid, OID_AUTO, debug, CTLFLAG_RW, + &uhid_debug, 0, "Debug level"); +#endif + +#define UHID_BSIZE 1024 /* bytes, buffer size */ +#define UHID_FRAME_NUM 50 /* bytes, frame number */ + +enum { + UHID_INTR_DT_RD, + UHID_CTRL_DT_WR, + UHID_CTRL_DT_RD, + UHID_N_TRANSFER, +}; + +struct uhid_softc { + struct usb_fifo_sc sc_fifo; + struct mtx sc_mtx; + + struct usb_xfer *sc_xfer[UHID_N_TRANSFER]; + struct usb_device *sc_udev; + void *sc_repdesc_ptr; + + uint32_t sc_isize; + uint32_t sc_osize; + uint32_t sc_fsize; + + uint16_t sc_repdesc_size; + + uint8_t sc_iface_no; + uint8_t sc_iface_index; + uint8_t sc_iid; + uint8_t sc_oid; + uint8_t sc_fid; + uint8_t sc_flags; +#define UHID_FLAG_IMMED 0x01 /* set if read should be immediate */ +#define UHID_FLAG_STATIC_DESC 0x04 /* set if report descriptors are + * static */ +}; + +static const uint8_t uhid_xb360gp_report_descr[] = {UHID_XB360GP_REPORT_DESCR()}; +static const uint8_t uhid_graphire_report_descr[] = {UHID_GRAPHIRE_REPORT_DESCR()}; +static const uint8_t uhid_graphire3_4x5_report_descr[] = {UHID_GRAPHIRE3_4X5_REPORT_DESCR()}; + +/* prototypes */ + +static device_probe_t uhid_probe; +static device_attach_t uhid_attach; +static device_detach_t uhid_detach; + +static usb_callback_t uhid_intr_callback; +static usb_callback_t uhid_write_callback; +static usb_callback_t uhid_read_callback; + +static usb_fifo_cmd_t uhid_start_read; +static usb_fifo_cmd_t uhid_stop_read; +static usb_fifo_cmd_t uhid_start_write; +static usb_fifo_cmd_t uhid_stop_write; +static usb_fifo_open_t uhid_open; +static usb_fifo_close_t uhid_close; +static usb_fifo_ioctl_t uhid_ioctl; + +static struct usb_fifo_methods uhid_fifo_methods = { + .f_open = &uhid_open, + .f_close = &uhid_close, + .f_ioctl = &uhid_ioctl, + .f_start_read = &uhid_start_read, + .f_stop_read = &uhid_stop_read, + .f_start_write = &uhid_start_write, + .f_stop_write = &uhid_stop_write, + .basename[0] = "uhid", +}; + +static void +uhid_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uhid_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTF("transferred!\n"); + + pc = usbd_xfer_get_frame(xfer, 0); + + /* + * If the ID byte is non zero we allow descriptors + * having multiple sizes: + */ + if ((actlen >= sc->sc_isize) || + ((actlen > 0) && (sc->sc_iid != 0))) { + /* limit report length to the maximum */ + if (actlen > sc->sc_isize) + actlen = sc->sc_isize; + usb_fifo_put_data(sc->sc_fifo.fp[USB_FIFO_RX], pc, + 0, actlen, 1); + } else { + /* ignore it */ + DPRINTF("ignored transfer, %d bytes\n", actlen); + } + + case USB_ST_SETUP: +re_submit: + if (usb_fifo_put_bytes_max( + sc->sc_fifo.fp[USB_FIFO_RX]) != 0) { + usbd_xfer_set_frame_len(xfer, 0, sc->sc_isize); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto re_submit; + } + return; + } +} + +static void +uhid_fill_set_report(struct usb_device_request *req, uint8_t iface_no, + uint8_t type, uint8_t id, uint16_t size) +{ + req->bmRequestType = UT_WRITE_CLASS_INTERFACE; + req->bRequest = UR_SET_REPORT; + USETW2(req->wValue, type, id); + req->wIndex[0] = iface_no; + req->wIndex[1] = 0; + USETW(req->wLength, size); +} + +static void +uhid_fill_get_report(struct usb_device_request *req, uint8_t iface_no, + uint8_t type, uint8_t id, uint16_t size) +{ + req->bmRequestType = UT_READ_CLASS_INTERFACE; + req->bRequest = UR_GET_REPORT; + USETW2(req->wValue, type, id); + req->wIndex[0] = iface_no; + req->wIndex[1] = 0; + USETW(req->wLength, size); +} + +static void +uhid_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uhid_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc; + uint32_t size = sc->sc_osize; + uint32_t actlen; + uint8_t id; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: + /* try to extract the ID byte */ + if (sc->sc_oid) { + pc = usbd_xfer_get_frame(xfer, 0); + if (usb_fifo_get_data(sc->sc_fifo.fp[USB_FIFO_TX], pc, + 0, 1, &actlen, 0)) { + if (actlen != 1) { + goto tr_error; + } + usbd_copy_out(pc, 0, &id, 1); + + } else { + return; + } + if (size) { + size--; + } + } else { + id = 0; + } + + pc = usbd_xfer_get_frame(xfer, 1); + if (usb_fifo_get_data(sc->sc_fifo.fp[USB_FIFO_TX], pc, + 0, UHID_BSIZE, &actlen, 1)) { + if (actlen != size) { + goto tr_error; + } + uhid_fill_set_report + (&req, sc->sc_iface_no, + UHID_OUTPUT_REPORT, id, size); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, size); + usbd_xfer_set_frames(xfer, size ? 2 : 1); + usbd_transfer_submit(xfer); + } + return; + + default: +tr_error: + /* bomb out */ + usb_fifo_get_data_error(sc->sc_fifo.fp[USB_FIFO_TX]); + return; + } +} + +static void +uhid_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uhid_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc; + + pc = usbd_xfer_get_frame(xfer, 0); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + usb_fifo_put_data(sc->sc_fifo.fp[USB_FIFO_RX], pc, sizeof(req), + sc->sc_isize, 1); + return; + + case USB_ST_SETUP: + + if (usb_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) > 0) { + + uhid_fill_get_report + (&req, sc->sc_iface_no, UHID_INPUT_REPORT, + sc->sc_iid, sc->sc_isize); + + usbd_copy_in(pc, 0, &req, sizeof(req)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, sc->sc_isize); + usbd_xfer_set_frames(xfer, sc->sc_isize ? 2 : 1); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + /* bomb out */ + usb_fifo_put_data_error(sc->sc_fifo.fp[USB_FIFO_RX]); + return; + } +} + +static const struct usb_config uhid_config[UHID_N_TRANSFER] = { + + [UHID_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = UHID_BSIZE, + .callback = &uhid_intr_callback, + }, + + [UHID_CTRL_DT_WR] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request) + UHID_BSIZE, + .callback = &uhid_write_callback, + .timeout = 1000, /* 1 second */ + }, + + [UHID_CTRL_DT_RD] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request) + UHID_BSIZE, + .callback = &uhid_read_callback, + .timeout = 1000, /* 1 second */ + }, +}; + +static void +uhid_start_read(struct usb_fifo *fifo) +{ + struct uhid_softc *sc = usb_fifo_softc(fifo); + + if (sc->sc_flags & UHID_FLAG_IMMED) { + usbd_transfer_start(sc->sc_xfer[UHID_CTRL_DT_RD]); + } else { + usbd_transfer_start(sc->sc_xfer[UHID_INTR_DT_RD]); + } +} + +static void +uhid_stop_read(struct usb_fifo *fifo) +{ + struct uhid_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_stop(sc->sc_xfer[UHID_CTRL_DT_RD]); + usbd_transfer_stop(sc->sc_xfer[UHID_INTR_DT_RD]); +} + +static void +uhid_start_write(struct usb_fifo *fifo) +{ + struct uhid_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_start(sc->sc_xfer[UHID_CTRL_DT_WR]); +} + +static void +uhid_stop_write(struct usb_fifo *fifo) +{ + struct uhid_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_stop(sc->sc_xfer[UHID_CTRL_DT_WR]); +} + +static int +uhid_get_report(struct uhid_softc *sc, uint8_t type, + uint8_t id, void *kern_data, void *user_data, + uint16_t len) +{ + int err; + uint8_t free_data = 0; + + if (kern_data == NULL) { + kern_data = malloc(len, M_USBDEV, M_WAITOK); + if (kern_data == NULL) { + err = ENOMEM; + goto done; + } + free_data = 1; + } + err = usbd_req_get_report(sc->sc_udev, NULL, kern_data, + len, sc->sc_iface_index, type, id); + if (err) { + err = ENXIO; + goto done; + } + if (user_data) { + /* dummy buffer */ + err = copyout(kern_data, user_data, len); + if (err) { + goto done; + } + } +done: + if (free_data) { + free(kern_data, M_USBDEV); + } + return (err); +} + +static int +uhid_set_report(struct uhid_softc *sc, uint8_t type, + uint8_t id, void *kern_data, void *user_data, + uint16_t len) +{ + int err; + uint8_t free_data = 0; + + if (kern_data == NULL) { + kern_data = malloc(len, M_USBDEV, M_WAITOK); + if (kern_data == NULL) { + err = ENOMEM; + goto done; + } + free_data = 1; + err = copyin(user_data, kern_data, len); + if (err) { + goto done; + } + } + err = usbd_req_set_report(sc->sc_udev, NULL, kern_data, + len, sc->sc_iface_index, type, id); + if (err) { + err = ENXIO; + goto done; + } +done: + if (free_data) { + free(kern_data, M_USBDEV); + } + return (err); +} + +static int +uhid_open(struct usb_fifo *fifo, int fflags) +{ + struct uhid_softc *sc = usb_fifo_softc(fifo); + + /* + * The buffers are one byte larger than maximum so that one + * can detect too large read/writes and short transfers: + */ + if (fflags & FREAD) { + /* reset flags */ + sc->sc_flags &= ~UHID_FLAG_IMMED; + + if (usb_fifo_alloc_buffer(fifo, + sc->sc_isize + 1, UHID_FRAME_NUM)) { + return (ENOMEM); + } + } + if (fflags & FWRITE) { + if (usb_fifo_alloc_buffer(fifo, + sc->sc_osize + 1, UHID_FRAME_NUM)) { + return (ENOMEM); + } + } + return (0); +} + +static void +uhid_close(struct usb_fifo *fifo, int fflags) +{ + if (fflags & (FREAD | FWRITE)) { + usb_fifo_free_buffer(fifo); + } +} + +static int +uhid_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, + int fflags) +{ + struct uhid_softc *sc = usb_fifo_softc(fifo); + struct usb_gen_descriptor *ugd; + uint32_t size; + int error = 0; + uint8_t id; + + switch (cmd) { + case USB_GET_REPORT_DESC: + ugd = addr; + if (sc->sc_repdesc_size > ugd->ugd_maxlen) { + size = ugd->ugd_maxlen; + } else { + size = sc->sc_repdesc_size; + } + ugd->ugd_actlen = size; + if (ugd->ugd_data == NULL) + break; /* descriptor length only */ + error = copyout(sc->sc_repdesc_ptr, ugd->ugd_data, size); + break; + + case USB_SET_IMMED: + if (!(fflags & FREAD)) { + error = EPERM; + break; + } + if (*(int *)addr) { + + /* do a test read */ + + error = uhid_get_report(sc, UHID_INPUT_REPORT, + sc->sc_iid, NULL, NULL, sc->sc_isize); + if (error) { + break; + } + mtx_lock(&sc->sc_mtx); + sc->sc_flags |= UHID_FLAG_IMMED; + mtx_unlock(&sc->sc_mtx); + } else { + mtx_lock(&sc->sc_mtx); + sc->sc_flags &= ~UHID_FLAG_IMMED; + mtx_unlock(&sc->sc_mtx); + } + break; + + case USB_GET_REPORT: + if (!(fflags & FREAD)) { + error = EPERM; + break; + } + ugd = addr; + switch (ugd->ugd_report_type) { + case UHID_INPUT_REPORT: + size = sc->sc_isize; + id = sc->sc_iid; + break; + case UHID_OUTPUT_REPORT: + size = sc->sc_osize; + id = sc->sc_oid; + break; + case UHID_FEATURE_REPORT: + size = sc->sc_fsize; + id = sc->sc_fid; + break; + default: + return (EINVAL); + } + if (id != 0) + copyin(ugd->ugd_data, &id, 1); + error = uhid_get_report(sc, ugd->ugd_report_type, id, + NULL, ugd->ugd_data, imin(ugd->ugd_maxlen, size)); + break; + + case USB_SET_REPORT: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + ugd = addr; + switch (ugd->ugd_report_type) { + case UHID_INPUT_REPORT: + size = sc->sc_isize; + id = sc->sc_iid; + break; + case UHID_OUTPUT_REPORT: + size = sc->sc_osize; + id = sc->sc_oid; + break; + case UHID_FEATURE_REPORT: + size = sc->sc_fsize; + id = sc->sc_fid; + break; + default: + return (EINVAL); + } + if (id != 0) + copyin(ugd->ugd_data, &id, 1); + error = uhid_set_report(sc, ugd->ugd_report_type, id, + NULL, ugd->ugd_data, imin(ugd->ugd_maxlen, size)); + break; + + case USB_GET_REPORT_ID: + *(int *)addr = 0; /* XXX: we only support reportid 0? */ + break; + + default: + error = EINVAL; + break; + } + return (error); +} + +static const STRUCT_USB_HOST_ID uhid_devs[] = { + /* generic HID class */ + {USB_IFACE_CLASS(UICLASS_HID),}, + /* the Xbox 360 gamepad doesn't use the HID class */ + {USB_IFACE_CLASS(UICLASS_VENDOR), + USB_IFACE_SUBCLASS(UISUBCLASS_XBOX360_CONTROLLER), + USB_IFACE_PROTOCOL(UIPROTO_XBOX360_GAMEPAD),}, +}; + +static int +uhid_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + int error; + + DPRINTFN(11, "\n"); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + + error = usbd_lookup_id_by_uaa(uhid_devs, sizeof(uhid_devs), uaa); + if (error) + return (error); + + if (usb_test_quirk(uaa, UQ_HID_IGNORE)) + return (ENXIO); + + /* + * Don't attach to mouse and keyboard devices, hence then no + * "nomatch" event is generated and then ums and ukbd won't + * attach properly when loaded. + */ + if ((uaa->info.bInterfaceClass == UICLASS_HID) && + (uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) && + ((uaa->info.bInterfaceProtocol == UIPROTO_BOOT_KEYBOARD) || + (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE))) { + return (ENXIO); + } + + return (BUS_PROBE_GENERIC); +} + +static int +uhid_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uhid_softc *sc = device_get_softc(dev); + int unit = device_get_unit(dev); + int error = 0; + + DPRINTFN(10, "sc=%p\n", sc); + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, "uhid lock", NULL, MTX_DEF | MTX_RECURSE); + + sc->sc_udev = uaa->device; + + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = uaa->info.bIfaceIndex; + + error = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, uhid_config, + UHID_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + DPRINTF("error=%s\n", usbd_errstr(error)); + goto detach; + } + if (uaa->info.idVendor == USB_VENDOR_WACOM) { + + /* the report descriptor for the Wacom Graphire is broken */ + + if (uaa->info.idProduct == USB_PRODUCT_WACOM_GRAPHIRE) { + + sc->sc_repdesc_size = sizeof(uhid_graphire_report_descr); + sc->sc_repdesc_ptr = (void *)&uhid_graphire_report_descr; + sc->sc_flags |= UHID_FLAG_STATIC_DESC; + + } else if (uaa->info.idProduct == USB_PRODUCT_WACOM_GRAPHIRE3_4X5) { + + static uint8_t reportbuf[] = {2, 2, 2}; + + /* + * The Graphire3 needs 0x0202 to be written to + * feature report ID 2 before it'll start + * returning digitizer data. + */ + error = usbd_req_set_report(uaa->device, NULL, + reportbuf, sizeof(reportbuf), + uaa->info.bIfaceIndex, UHID_FEATURE_REPORT, 2); + + if (error) { + DPRINTF("set report failed, error=%s (ignored)\n", + usbd_errstr(error)); + } + sc->sc_repdesc_size = sizeof(uhid_graphire3_4x5_report_descr); + sc->sc_repdesc_ptr = (void *)&uhid_graphire3_4x5_report_descr; + sc->sc_flags |= UHID_FLAG_STATIC_DESC; + } + } else if ((uaa->info.bInterfaceClass == UICLASS_VENDOR) && + (uaa->info.bInterfaceSubClass == UISUBCLASS_XBOX360_CONTROLLER) && + (uaa->info.bInterfaceProtocol == UIPROTO_XBOX360_GAMEPAD)) { + + /* the Xbox 360 gamepad has no report descriptor */ + sc->sc_repdesc_size = sizeof(uhid_xb360gp_report_descr); + sc->sc_repdesc_ptr = (void *)&uhid_xb360gp_report_descr; + sc->sc_flags |= UHID_FLAG_STATIC_DESC; + } + if (sc->sc_repdesc_ptr == NULL) { + + error = usbd_req_get_hid_desc(uaa->device, NULL, + &sc->sc_repdesc_ptr, &sc->sc_repdesc_size, + M_USBDEV, uaa->info.bIfaceIndex); + + if (error) { + device_printf(dev, "no report descriptor\n"); + goto detach; + } + } + error = usbd_req_set_idle(uaa->device, NULL, + uaa->info.bIfaceIndex, 0, 0); + + if (error) { + DPRINTF("set idle failed, error=%s (ignored)\n", + usbd_errstr(error)); + } + sc->sc_isize = hid_report_size + (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_input, &sc->sc_iid); + + sc->sc_osize = hid_report_size + (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_output, &sc->sc_oid); + + sc->sc_fsize = hid_report_size + (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_feature, &sc->sc_fid); + + if (sc->sc_isize > UHID_BSIZE) { + DPRINTF("input size is too large, " + "%d bytes (truncating)\n", + sc->sc_isize); + sc->sc_isize = UHID_BSIZE; + } + if (sc->sc_osize > UHID_BSIZE) { + DPRINTF("output size is too large, " + "%d bytes (truncating)\n", + sc->sc_osize); + sc->sc_osize = UHID_BSIZE; + } + if (sc->sc_fsize > UHID_BSIZE) { + DPRINTF("feature size is too large, " + "%d bytes (truncating)\n", + sc->sc_fsize); + sc->sc_fsize = UHID_BSIZE; + } + + error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &uhid_fifo_methods, &sc->sc_fifo, + unit, 0 - 1, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + if (error) { + goto detach; + } + return (0); /* success */ + +detach: + uhid_detach(dev); + return (ENOMEM); +} + +static int +uhid_detach(device_t dev) +{ + struct uhid_softc *sc = device_get_softc(dev); + + usb_fifo_detach(&sc->sc_fifo); + + usbd_transfer_unsetup(sc->sc_xfer, UHID_N_TRANSFER); + + if (sc->sc_repdesc_ptr) { + if (!(sc->sc_flags & UHID_FLAG_STATIC_DESC)) { + free(sc->sc_repdesc_ptr, M_USBDEV); + } + } + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static devclass_t uhid_devclass; + +static device_method_t uhid_methods[] = { + DEVMETHOD(device_probe, uhid_probe), + DEVMETHOD(device_attach, uhid_attach), + DEVMETHOD(device_detach, uhid_detach), + {0, 0} +}; + +static driver_t uhid_driver = { + .name = "uhid", + .methods = uhid_methods, + .size = sizeof(struct uhid_softc), +}; + +DRIVER_MODULE(uhid, uhub, uhid_driver, uhid_devclass, NULL, 0); +MODULE_DEPEND(uhid, usb, 1, 1, 1); +MODULE_VERSION(uhid, 1); diff --git a/sys/bus/u4b/input/ukbd.c b/sys/bus/u4b/input/ukbd.c new file mode 100644 index 0000000000..eb7e1d9f7a --- /dev/null +++ b/sys/bus/u4b/input/ukbd.c @@ -0,0 +1,2139 @@ +#include +__FBSDID("$FreeBSD$"); + + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* + * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf + */ + +#include "opt_compat.h" +#include "opt_kbd.h" +#include "opt_ukbd.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define USB_DEBUG_VAR ukbd_debug +#include + +#include + +#include +#include +#include +#include + +#include + +/* the initial key map, accent map and fkey strings */ +#if defined(UKBD_DFLT_KEYMAP) && !defined(KLD_MODULE) +#define KBD_DFLT_KEYMAP +#include "ukbdmap.h" +#endif + +/* the following file must be included after "ukbdmap.h" */ +#include + +#ifdef USB_DEBUG +static int ukbd_debug = 0; +static int ukbd_no_leds = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ukbd, CTLFLAG_RW, 0, "USB ukbd"); +SYSCTL_INT(_hw_usb_ukbd, OID_AUTO, debug, CTLFLAG_RW, + &ukbd_debug, 0, "Debug level"); +SYSCTL_INT(_hw_usb_ukbd, OID_AUTO, no_leds, CTLFLAG_RW, + &ukbd_no_leds, 0, "Disables setting of keyboard leds"); + +TUNABLE_INT("hw.usb.ukbd.debug", &ukbd_debug); +TUNABLE_INT("hw.usb.ukbd.no_leds", &ukbd_no_leds); +#endif + +#define UKBD_EMULATE_ATSCANCODE 1 +#define UKBD_DRIVER_NAME "ukbd" +#define UKBD_NMOD 8 /* units */ +#define UKBD_NKEYCODE 6 /* units */ +#define UKBD_IN_BUF_SIZE (2*(UKBD_NMOD + (2*UKBD_NKEYCODE))) /* bytes */ +#define UKBD_IN_BUF_FULL (UKBD_IN_BUF_SIZE / 2) /* bytes */ +#define UKBD_NFKEY (sizeof(fkey_tab)/sizeof(fkey_tab[0])) /* units */ +#define UKBD_BUFFER_SIZE 64 /* bytes */ + +struct ukbd_data { + uint16_t modifiers; +#define MOD_CONTROL_L 0x01 +#define MOD_CONTROL_R 0x10 +#define MOD_SHIFT_L 0x02 +#define MOD_SHIFT_R 0x20 +#define MOD_ALT_L 0x04 +#define MOD_ALT_R 0x40 +#define MOD_WIN_L 0x08 +#define MOD_WIN_R 0x80 +/* internal */ +#define MOD_EJECT 0x0100 +#define MOD_FN 0x0200 + uint8_t keycode[UKBD_NKEYCODE]; +}; + +enum { + UKBD_INTR_DT, + UKBD_CTRL_LED, + UKBD_N_TRANSFER, +}; + +struct ukbd_softc { + keyboard_t sc_kbd; + keymap_t sc_keymap; + accentmap_t sc_accmap; + fkeytab_t sc_fkeymap[UKBD_NFKEY]; + struct hid_location sc_loc_apple_eject; + struct hid_location sc_loc_apple_fn; + struct hid_location sc_loc_ctrl_l; + struct hid_location sc_loc_ctrl_r; + struct hid_location sc_loc_shift_l; + struct hid_location sc_loc_shift_r; + struct hid_location sc_loc_alt_l; + struct hid_location sc_loc_alt_r; + struct hid_location sc_loc_win_l; + struct hid_location sc_loc_win_r; + struct hid_location sc_loc_events; + struct hid_location sc_loc_numlock; + struct hid_location sc_loc_capslock; + struct hid_location sc_loc_scrolllock; + struct usb_callout sc_callout; + struct ukbd_data sc_ndata; + struct ukbd_data sc_odata; + + struct thread *sc_poll_thread; + struct usb_device *sc_udev; + struct usb_interface *sc_iface; + struct usb_xfer *sc_xfer[UKBD_N_TRANSFER]; + + uint32_t sc_ntime[UKBD_NKEYCODE]; + uint32_t sc_otime[UKBD_NKEYCODE]; + uint32_t sc_input[UKBD_IN_BUF_SIZE]; /* input buffer */ + uint32_t sc_time_ms; + uint32_t sc_composed_char; /* composed char code, if non-zero */ +#ifdef UKBD_EMULATE_ATSCANCODE + uint32_t sc_buffered_char[2]; +#endif + uint32_t sc_flags; /* flags */ +#define UKBD_FLAG_COMPOSE 0x00000001 +#define UKBD_FLAG_POLLING 0x00000002 +#define UKBD_FLAG_SET_LEDS 0x00000004 +#define UKBD_FLAG_ATTACHED 0x00000010 +#define UKBD_FLAG_GONE 0x00000020 + +#define UKBD_FLAG_HID_MASK 0x003fffc0 +#define UKBD_FLAG_APPLE_EJECT 0x00000040 +#define UKBD_FLAG_APPLE_FN 0x00000080 +#define UKBD_FLAG_APPLE_SWAP 0x00000100 +#define UKBD_FLAG_TIMER_RUNNING 0x00000200 +#define UKBD_FLAG_CTRL_L 0x00000400 +#define UKBD_FLAG_CTRL_R 0x00000800 +#define UKBD_FLAG_SHIFT_L 0x00001000 +#define UKBD_FLAG_SHIFT_R 0x00002000 +#define UKBD_FLAG_ALT_L 0x00004000 +#define UKBD_FLAG_ALT_R 0x00008000 +#define UKBD_FLAG_WIN_L 0x00010000 +#define UKBD_FLAG_WIN_R 0x00020000 +#define UKBD_FLAG_EVENTS 0x00040000 +#define UKBD_FLAG_NUMLOCK 0x00080000 +#define UKBD_FLAG_CAPSLOCK 0x00100000 +#define UKBD_FLAG_SCROLLLOCK 0x00200000 + + int sc_mode; /* input mode (K_XLATE,K_RAW,K_CODE) */ + int sc_state; /* shift/lock key state */ + int sc_accents; /* accent key index (> 0) */ + int sc_led_size; + int sc_kbd_size; + + uint16_t sc_inputs; + uint16_t sc_inputhead; + uint16_t sc_inputtail; + uint16_t sc_modifiers; + + uint8_t sc_leds; /* store for async led requests */ + uint8_t sc_iface_index; + uint8_t sc_iface_no; + uint8_t sc_id_apple_eject; + uint8_t sc_id_apple_fn; + uint8_t sc_id_ctrl_l; + uint8_t sc_id_ctrl_r; + uint8_t sc_id_shift_l; + uint8_t sc_id_shift_r; + uint8_t sc_id_alt_l; + uint8_t sc_id_alt_r; + uint8_t sc_id_win_l; + uint8_t sc_id_win_r; + uint8_t sc_id_event; + uint8_t sc_id_numlock; + uint8_t sc_id_capslock; + uint8_t sc_id_scrolllock; + uint8_t sc_id_events; + uint8_t sc_kbd_id; + + uint8_t sc_buffer[UKBD_BUFFER_SIZE]; +}; + +#define KEY_ERROR 0x01 + +#define KEY_PRESS 0 +#define KEY_RELEASE 0x400 +#define KEY_INDEX(c) ((c) & 0xFF) + +#define SCAN_PRESS 0 +#define SCAN_RELEASE 0x80 +#define SCAN_PREFIX_E0 0x100 +#define SCAN_PREFIX_E1 0x200 +#define SCAN_PREFIX_CTL 0x400 +#define SCAN_PREFIX_SHIFT 0x800 +#define SCAN_PREFIX (SCAN_PREFIX_E0 | SCAN_PREFIX_E1 | \ + SCAN_PREFIX_CTL | SCAN_PREFIX_SHIFT) +#define SCAN_CHAR(c) ((c) & 0x7f) + +#define UKBD_LOCK() mtx_lock(&Giant) +#define UKBD_UNLOCK() mtx_unlock(&Giant) + +#ifdef INVARIANTS + +/* + * Assert that the lock is held in all contexts + * where the code can be executed. + */ +#define UKBD_LOCK_ASSERT() mtx_assert(&Giant, MA_OWNED) + +/* + * Assert that the lock is held in the contexts + * where it really has to be so. + */ +#define UKBD_CTX_LOCK_ASSERT() \ + do { \ + if (!kdb_active && panicstr == NULL) \ + mtx_assert(&Giant, MA_OWNED); \ + } while (0) +#else + +#define UKBD_LOCK_ASSERT() (void)0 +#define UKBD_CTX_LOCK_ASSERT() (void)0 + +#endif + +struct ukbd_mods { + uint32_t mask, key; +}; + +static const struct ukbd_mods ukbd_mods[UKBD_NMOD] = { + {MOD_CONTROL_L, 0xe0}, + {MOD_CONTROL_R, 0xe4}, + {MOD_SHIFT_L, 0xe1}, + {MOD_SHIFT_R, 0xe5}, + {MOD_ALT_L, 0xe2}, + {MOD_ALT_R, 0xe6}, + {MOD_WIN_L, 0xe3}, + {MOD_WIN_R, 0xe7}, +}; + +#define NN 0 /* no translation */ +/* + * Translate USB keycodes to AT keyboard scancodes. + */ +/* + * FIXME: Mac USB keyboard generates: + * 0x53: keypad NumLock/Clear + * 0x66: Power + * 0x67: keypad = + * 0x68: F13 + * 0x69: F14 + * 0x6a: F15 + */ +static const uint8_t ukbd_trtab[256] = { + 0, 0, 0, 0, 30, 48, 46, 32, /* 00 - 07 */ + 18, 33, 34, 35, 23, 36, 37, 38, /* 08 - 0F */ + 50, 49, 24, 25, 16, 19, 31, 20, /* 10 - 17 */ + 22, 47, 17, 45, 21, 44, 2, 3, /* 18 - 1F */ + 4, 5, 6, 7, 8, 9, 10, 11, /* 20 - 27 */ + 28, 1, 14, 15, 57, 12, 13, 26, /* 28 - 2F */ + 27, 43, 43, 39, 40, 41, 51, 52, /* 30 - 37 */ + 53, 58, 59, 60, 61, 62, 63, 64, /* 38 - 3F */ + 65, 66, 67, 68, 87, 88, 92, 70, /* 40 - 47 */ + 104, 102, 94, 96, 103, 99, 101, 98, /* 48 - 4F */ + 97, 100, 95, 69, 91, 55, 74, 78,/* 50 - 57 */ + 89, 79, 80, 81, 75, 76, 77, 71, /* 58 - 5F */ + 72, 73, 82, 83, 86, 107, 122, NN, /* 60 - 67 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* 68 - 6F */ + NN, NN, NN, NN, 115, 108, 111, 113, /* 70 - 77 */ + 109, 110, 112, 118, 114, 116, 117, 119, /* 78 - 7F */ + 121, 120, NN, NN, NN, NN, NN, 123, /* 80 - 87 */ + 124, 125, 126, 127, 128, NN, NN, NN, /* 88 - 8F */ + NN, NN, NN, NN, NN, NN, NN, NN, /* 90 - 97 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* 98 - 9F */ + NN, NN, NN, NN, NN, NN, NN, NN, /* A0 - A7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* A8 - AF */ + NN, NN, NN, NN, NN, NN, NN, NN, /* B0 - B7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* B8 - BF */ + NN, NN, NN, NN, NN, NN, NN, NN, /* C0 - C7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* C8 - CF */ + NN, NN, NN, NN, NN, NN, NN, NN, /* D0 - D7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* D8 - DF */ + 29, 42, 56, 105, 90, 54, 93, 106, /* E0 - E7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* E8 - EF */ + NN, NN, NN, NN, NN, NN, NN, NN, /* F0 - F7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* F8 - FF */ +}; + +static const uint8_t ukbd_boot_desc[] = { + 0x05, 0x01, 0x09, 0x06, 0xa1, + 0x01, 0x05, 0x07, 0x19, 0xe0, + 0x29, 0xe7, 0x15, 0x00, 0x25, + 0x01, 0x75, 0x01, 0x95, 0x08, + 0x81, 0x02, 0x95, 0x01, 0x75, + 0x08, 0x81, 0x01, 0x95, 0x03, + 0x75, 0x01, 0x05, 0x08, 0x19, + 0x01, 0x29, 0x03, 0x91, 0x02, + 0x95, 0x05, 0x75, 0x01, 0x91, + 0x01, 0x95, 0x06, 0x75, 0x08, + 0x15, 0x00, 0x26, 0xff, 0x00, + 0x05, 0x07, 0x19, 0x00, 0x2a, + 0xff, 0x00, 0x81, 0x00, 0xc0 +}; + +/* prototypes */ +static void ukbd_timeout(void *); +static void ukbd_set_leds(struct ukbd_softc *, uint8_t); +static int ukbd_set_typematic(keyboard_t *, int); +#ifdef UKBD_EMULATE_ATSCANCODE +static int ukbd_key2scan(struct ukbd_softc *, int, int, int); +#endif +static uint32_t ukbd_read_char(keyboard_t *, int); +static void ukbd_clear_state(keyboard_t *); +static int ukbd_ioctl(keyboard_t *, u_long, caddr_t); +static int ukbd_enable(keyboard_t *); +static int ukbd_disable(keyboard_t *); +static void ukbd_interrupt(struct ukbd_softc *); +static void ukbd_event_keyinput(struct ukbd_softc *); + +static device_probe_t ukbd_probe; +static device_attach_t ukbd_attach; +static device_detach_t ukbd_detach; +static device_resume_t ukbd_resume; + +static uint8_t +ukbd_any_key_pressed(struct ukbd_softc *sc) +{ + uint8_t i; + uint8_t j; + + for (j = i = 0; i < UKBD_NKEYCODE; i++) + j |= sc->sc_odata.keycode[i]; + + return (j ? 1 : 0); +} + +static void +ukbd_start_timer(struct ukbd_softc *sc) +{ + sc->sc_flags |= UKBD_FLAG_TIMER_RUNNING; + usb_callout_reset(&sc->sc_callout, hz / 40, &ukbd_timeout, sc); +} + +static void +ukbd_put_key(struct ukbd_softc *sc, uint32_t key) +{ + + UKBD_CTX_LOCK_ASSERT(); + + DPRINTF("0x%02x (%d) %s\n", key, key, + (key & KEY_RELEASE) ? "released" : "pressed"); + + if (sc->sc_inputs < UKBD_IN_BUF_SIZE) { + sc->sc_input[sc->sc_inputtail] = key; + ++(sc->sc_inputs); + ++(sc->sc_inputtail); + if (sc->sc_inputtail >= UKBD_IN_BUF_SIZE) { + sc->sc_inputtail = 0; + } + } else { + DPRINTF("input buffer is full\n"); + } +} + +static void +ukbd_do_poll(struct ukbd_softc *sc, uint8_t wait) +{ + + UKBD_CTX_LOCK_ASSERT(); + KASSERT((sc->sc_flags & UKBD_FLAG_POLLING) != 0, + ("ukbd_do_poll called when not polling\n")); + DPRINTFN(2, "polling\n"); + + if (!kdb_active && !SCHEDULER_STOPPED()) { + /* + * In this context the kernel is polling for input, + * but the USB subsystem works in normal interrupt-driven + * mode, so we just wait on the USB threads to do the job. + * Note that we currently hold the Giant, but it's also used + * as the transfer mtx, so we must release it while waiting. + */ + while (sc->sc_inputs == 0) { + /* + * Give USB threads a chance to run. Note that + * kern_yield performs DROP_GIANT + PICKUP_GIANT. + */ + kern_yield(PRI_UNCHANGED); + if (!wait) + break; + } + return; + } + + while (sc->sc_inputs == 0) { + + usbd_transfer_poll(sc->sc_xfer, UKBD_N_TRANSFER); + + /* Delay-optimised support for repetition of keys */ + if (ukbd_any_key_pressed(sc)) { + /* a key is pressed - need timekeeping */ + DELAY(1000); + + /* 1 millisecond has passed */ + sc->sc_time_ms += 1; + } + + ukbd_interrupt(sc); + + if (!wait) + break; + } +} + +static int32_t +ukbd_get_key(struct ukbd_softc *sc, uint8_t wait) +{ + int32_t c; + + UKBD_CTX_LOCK_ASSERT(); + KASSERT((!kdb_active && !SCHEDULER_STOPPED()) + || (sc->sc_flags & UKBD_FLAG_POLLING) != 0, + ("not polling in kdb or panic\n")); + + if (sc->sc_inputs == 0) { + /* start transfer, if not already started */ + usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT]); + } + + if (sc->sc_flags & UKBD_FLAG_POLLING) + ukbd_do_poll(sc, wait); + + if (sc->sc_inputs == 0) { + c = -1; + } else { + c = sc->sc_input[sc->sc_inputhead]; + --(sc->sc_inputs); + ++(sc->sc_inputhead); + if (sc->sc_inputhead >= UKBD_IN_BUF_SIZE) { + sc->sc_inputhead = 0; + } + } + return (c); +} + +static void +ukbd_interrupt(struct ukbd_softc *sc) +{ + uint32_t n_mod; + uint32_t o_mod; + uint32_t now = sc->sc_time_ms; + uint32_t dtime; + uint8_t key; + uint8_t i; + uint8_t j; + + UKBD_CTX_LOCK_ASSERT(); + + if (sc->sc_ndata.keycode[0] == KEY_ERROR) + return; + + n_mod = sc->sc_ndata.modifiers; + o_mod = sc->sc_odata.modifiers; + if (n_mod != o_mod) { + for (i = 0; i < UKBD_NMOD; i++) { + if ((n_mod & ukbd_mods[i].mask) != + (o_mod & ukbd_mods[i].mask)) { + ukbd_put_key(sc, ukbd_mods[i].key | + ((n_mod & ukbd_mods[i].mask) ? + KEY_PRESS : KEY_RELEASE)); + } + } + } + /* Check for released keys. */ + for (i = 0; i < UKBD_NKEYCODE; i++) { + key = sc->sc_odata.keycode[i]; + if (key == 0) { + continue; + } + for (j = 0; j < UKBD_NKEYCODE; j++) { + if (sc->sc_ndata.keycode[j] == 0) { + continue; + } + if (key == sc->sc_ndata.keycode[j]) { + goto rfound; + } + } + ukbd_put_key(sc, key | KEY_RELEASE); +rfound: ; + } + + /* Check for pressed keys. */ + for (i = 0; i < UKBD_NKEYCODE; i++) { + key = sc->sc_ndata.keycode[i]; + if (key == 0) { + continue; + } + sc->sc_ntime[i] = now + sc->sc_kbd.kb_delay1; + for (j = 0; j < UKBD_NKEYCODE; j++) { + if (sc->sc_odata.keycode[j] == 0) { + continue; + } + if (key == sc->sc_odata.keycode[j]) { + + /* key is still pressed */ + + sc->sc_ntime[i] = sc->sc_otime[j]; + dtime = (sc->sc_otime[j] - now); + + if (!(dtime & 0x80000000)) { + /* time has not elapsed */ + goto pfound; + } + sc->sc_ntime[i] = now + sc->sc_kbd.kb_delay2; + break; + } + } + ukbd_put_key(sc, key | KEY_PRESS); + + /* + * If any other key is presently down, force its repeat to be + * well in the future (100s). This makes the last key to be + * pressed do the autorepeat. + */ + for (j = 0; j != UKBD_NKEYCODE; j++) { + if (j != i) + sc->sc_ntime[j] = now + (100 * 1000); + } +pfound: ; + } + + sc->sc_odata = sc->sc_ndata; + + memcpy(sc->sc_otime, sc->sc_ntime, sizeof(sc->sc_otime)); + + ukbd_event_keyinput(sc); +} + +static void +ukbd_event_keyinput(struct ukbd_softc *sc) +{ + int c; + + UKBD_CTX_LOCK_ASSERT(); + + if ((sc->sc_flags & UKBD_FLAG_POLLING) != 0) + return; + + if (sc->sc_inputs == 0) + return; + + if (KBD_IS_ACTIVE(&sc->sc_kbd) && + KBD_IS_BUSY(&sc->sc_kbd)) { + /* let the callback function process the input */ + (sc->sc_kbd.kb_callback.kc_func) (&sc->sc_kbd, KBDIO_KEYINPUT, + sc->sc_kbd.kb_callback.kc_arg); + } else { + /* read and discard the input, no one is waiting for it */ + do { + c = ukbd_read_char(&sc->sc_kbd, 0); + } while (c != NOKEY); + } +} + +static void +ukbd_timeout(void *arg) +{ + struct ukbd_softc *sc = arg; + + UKBD_LOCK_ASSERT(); + + sc->sc_time_ms += 25; /* milliseconds */ + + ukbd_interrupt(sc); + + /* Make sure any leftover key events gets read out */ + ukbd_event_keyinput(sc); + + if (ukbd_any_key_pressed(sc) || (sc->sc_inputs != 0)) { + ukbd_start_timer(sc); + } else { + sc->sc_flags &= ~UKBD_FLAG_TIMER_RUNNING; + } +} + +static uint8_t +ukbd_apple_fn(uint8_t keycode) { + switch (keycode) { + case 0x28: return 0x49; /* RETURN -> INSERT */ + case 0x2a: return 0x4c; /* BACKSPACE -> DEL */ + case 0x50: return 0x4a; /* LEFT ARROW -> HOME */ + case 0x4f: return 0x4d; /* RIGHT ARROW -> END */ + case 0x52: return 0x4b; /* UP ARROW -> PGUP */ + case 0x51: return 0x4e; /* DOWN ARROW -> PGDN */ + default: return keycode; + } +} + +static uint8_t +ukbd_apple_swap(uint8_t keycode) { + switch (keycode) { + case 0x35: return 0x64; + case 0x64: return 0x35; + default: return keycode; + } +} + +static void +ukbd_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ukbd_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t i; + uint8_t offset; + uint8_t id; + int len; + + UKBD_LOCK_ASSERT(); + + usbd_xfer_status(xfer, &len, NULL, NULL, NULL); + pc = usbd_xfer_get_frame(xfer, 0); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTF("actlen=%d bytes\n", len); + + if (len == 0) { + DPRINTF("zero length data\n"); + goto tr_setup; + } + + if (sc->sc_kbd_id != 0) { + /* check and remove HID ID byte */ + usbd_copy_out(pc, 0, &id, 1); + offset = 1; + len--; + if (len == 0) { + DPRINTF("zero length data\n"); + goto tr_setup; + } + } else { + offset = 0; + id = 0; + } + + if (len > UKBD_BUFFER_SIZE) + len = UKBD_BUFFER_SIZE; + + /* get data */ + usbd_copy_out(pc, offset, sc->sc_buffer, len); + + /* clear temporary storage */ + memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata)); + + /* scan through HID data */ + if ((sc->sc_flags & UKBD_FLAG_APPLE_EJECT) && + (id == sc->sc_id_apple_eject)) { + if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_apple_eject)) + sc->sc_modifiers |= MOD_EJECT; + else + sc->sc_modifiers &= ~MOD_EJECT; + } + if ((sc->sc_flags & UKBD_FLAG_APPLE_FN) && + (id == sc->sc_id_apple_fn)) { + if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_apple_fn)) + sc->sc_modifiers |= MOD_FN; + else + sc->sc_modifiers &= ~MOD_FN; + } + if ((sc->sc_flags & UKBD_FLAG_CTRL_L) && + (id == sc->sc_id_ctrl_l)) { + if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_ctrl_l)) + sc-> sc_modifiers |= MOD_CONTROL_L; + else + sc-> sc_modifiers &= ~MOD_CONTROL_L; + } + if ((sc->sc_flags & UKBD_FLAG_CTRL_R) && + (id == sc->sc_id_ctrl_r)) { + if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_ctrl_r)) + sc->sc_modifiers |= MOD_CONTROL_R; + else + sc->sc_modifiers &= ~MOD_CONTROL_R; + } + if ((sc->sc_flags & UKBD_FLAG_SHIFT_L) && + (id == sc->sc_id_shift_l)) { + if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_shift_l)) + sc->sc_modifiers |= MOD_SHIFT_L; + else + sc->sc_modifiers &= ~MOD_SHIFT_L; + } + if ((sc->sc_flags & UKBD_FLAG_SHIFT_R) && + (id == sc->sc_id_shift_r)) { + if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_shift_r)) + sc->sc_modifiers |= MOD_SHIFT_R; + else + sc->sc_modifiers &= ~MOD_SHIFT_R; + } + if ((sc->sc_flags & UKBD_FLAG_ALT_L) && + (id == sc->sc_id_alt_l)) { + if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_alt_l)) + sc->sc_modifiers |= MOD_ALT_L; + else + sc->sc_modifiers &= ~MOD_ALT_L; + } + if ((sc->sc_flags & UKBD_FLAG_ALT_R) && + (id == sc->sc_id_alt_r)) { + if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_alt_r)) + sc->sc_modifiers |= MOD_ALT_R; + else + sc->sc_modifiers &= ~MOD_ALT_R; + } + if ((sc->sc_flags & UKBD_FLAG_WIN_L) && + (id == sc->sc_id_win_l)) { + if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_win_l)) + sc->sc_modifiers |= MOD_WIN_L; + else + sc->sc_modifiers &= ~MOD_WIN_L; + } + if ((sc->sc_flags & UKBD_FLAG_WIN_R) && + (id == sc->sc_id_win_r)) { + if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_win_r)) + sc->sc_modifiers |= MOD_WIN_R; + else + sc->sc_modifiers &= ~MOD_WIN_R; + } + + sc->sc_ndata.modifiers = sc->sc_modifiers; + + if ((sc->sc_flags & UKBD_FLAG_EVENTS) && + (id == sc->sc_id_events)) { + i = sc->sc_loc_events.count; + if (i > UKBD_NKEYCODE) + i = UKBD_NKEYCODE; + if (i > len) + i = len; + while (i--) { + sc->sc_ndata.keycode[i] = + hid_get_data(sc->sc_buffer + i, len - i, + &sc->sc_loc_events); + } + } + +#ifdef USB_DEBUG + DPRINTF("modifiers = 0x%04x\n", (int)sc->sc_modifiers); + for (i = 0; i < UKBD_NKEYCODE; i++) { + if (sc->sc_ndata.keycode[i]) { + DPRINTF("[%d] = 0x%02x\n", + (int)i, (int)sc->sc_ndata.keycode[i]); + } + } +#endif + if (sc->sc_modifiers & MOD_FN) { + for (i = 0; i < UKBD_NKEYCODE; i++) { + sc->sc_ndata.keycode[i] = + ukbd_apple_fn(sc->sc_ndata.keycode[i]); + } + } + + if (sc->sc_flags & UKBD_FLAG_APPLE_SWAP) { + for (i = 0; i < UKBD_NKEYCODE; i++) { + sc->sc_ndata.keycode[i] = + ukbd_apple_swap(sc->sc_ndata.keycode[i]); + } + } + + ukbd_interrupt(sc); + + if (!(sc->sc_flags & UKBD_FLAG_TIMER_RUNNING)) { + if (ukbd_any_key_pressed(sc)) { + ukbd_start_timer(sc); + } + } + + case USB_ST_SETUP: +tr_setup: + if (sc->sc_inputs < UKBD_IN_BUF_FULL) { + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + } else { + DPRINTF("input queue is full!\n"); + } + break; + + default: /* Error */ + DPRINTF("error=%s\n", usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +ukbd_set_leds_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ukbd_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc; + uint8_t id; + uint8_t any; + int len; + + UKBD_LOCK_ASSERT(); + +#ifdef USB_DEBUG + if (ukbd_no_leds) + return; +#endif + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: + if (!(sc->sc_flags & UKBD_FLAG_SET_LEDS)) + break; + sc->sc_flags &= ~UKBD_FLAG_SET_LEDS; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, UHID_OUTPUT_REPORT, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + req.wLength[1] = 0; + + memset(sc->sc_buffer, 0, UKBD_BUFFER_SIZE); + + id = 0; + any = 0; + + /* Assumption: All led bits must be in the same ID. */ + + if (sc->sc_flags & UKBD_FLAG_NUMLOCK) { + if (sc->sc_leds & NLKED) { + hid_put_data_unsigned(sc->sc_buffer + 1, UKBD_BUFFER_SIZE - 1, + &sc->sc_loc_numlock, 1); + } + id = sc->sc_id_numlock; + any = 1; + } + + if (sc->sc_flags & UKBD_FLAG_SCROLLLOCK) { + if (sc->sc_leds & SLKED) { + hid_put_data_unsigned(sc->sc_buffer + 1, UKBD_BUFFER_SIZE - 1, + &sc->sc_loc_scrolllock, 1); + } + id = sc->sc_id_scrolllock; + any = 1; + } + + if (sc->sc_flags & UKBD_FLAG_CAPSLOCK) { + if (sc->sc_leds & CLKED) { + hid_put_data_unsigned(sc->sc_buffer + 1, UKBD_BUFFER_SIZE - 1, + &sc->sc_loc_capslock, 1); + } + id = sc->sc_id_capslock; + any = 1; + } + + /* if no leds, nothing to do */ + if (!any) + break; + + /* range check output report length */ + len = sc->sc_led_size; + if (len > (UKBD_BUFFER_SIZE - 1)) + len = (UKBD_BUFFER_SIZE - 1); + + /* check if we need to prefix an ID byte */ + sc->sc_buffer[0] = id; + + pc = usbd_xfer_get_frame(xfer, 1); + if (id != 0) { + len++; + usbd_copy_in(pc, 0, sc->sc_buffer, len); + } else { + usbd_copy_in(pc, 0, sc->sc_buffer + 1, len); + } + req.wLength[0] = len; + usbd_xfer_set_frame_len(xfer, 1, len); + + DPRINTF("len=%d, id=%d\n", len, id); + + /* setup control request last */ + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + + /* start data transfer */ + usbd_xfer_set_frames(xfer, 2); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTFN(1, "error=%s\n", usbd_errstr(error)); + break; + } +} + +static const struct usb_config ukbd_config[UKBD_N_TRANSFER] = { + + [UKBD_INTR_DT] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &ukbd_intr_callback, + }, + + [UKBD_CTRL_LED] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request) + UKBD_BUFFER_SIZE, + .callback = &ukbd_set_leds_callback, + .timeout = 1000, /* 1 second */ + }, +}; + +/* A match on these entries will load ukbd */ +static const STRUCT_USB_HOST_ID __used ukbd_devs[] = { + {USB_IFACE_CLASS(UICLASS_HID), + USB_IFACE_SUBCLASS(UISUBCLASS_BOOT), + USB_IFACE_PROTOCOL(UIPROTO_BOOT_KEYBOARD),}, +}; + +static int +ukbd_probe(device_t dev) +{ + keyboard_switch_t *sw = kbd_get_switch(UKBD_DRIVER_NAME); + struct usb_attach_arg *uaa = device_get_ivars(dev); + void *d_ptr; + int error; + uint16_t d_len; + + UKBD_LOCK_ASSERT(); + DPRINTFN(11, "\n"); + + if (sw == NULL) { + return (ENXIO); + } + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + + if (uaa->info.bInterfaceClass != UICLASS_HID) + return (ENXIO); + + if ((uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) && + (uaa->info.bInterfaceProtocol == UIPROTO_BOOT_KEYBOARD)) { + if (usb_test_quirk(uaa, UQ_KBD_IGNORE)) + return (ENXIO); + else + return (BUS_PROBE_DEFAULT); + } + + error = usbd_req_get_hid_desc(uaa->device, NULL, + &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex); + + if (error) + return (ENXIO); + + /* + * NOTE: we currently don't support USB mouse and USB keyboard + * on the same USB endpoint. + */ + if (hid_is_collection(d_ptr, d_len, + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE))) { + /* most likely a mouse */ + error = ENXIO; + } else if (hid_is_collection(d_ptr, d_len, + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_KEYBOARD))) { + if (usb_test_quirk(uaa, UQ_KBD_IGNORE)) + error = ENXIO; + else + error = BUS_PROBE_DEFAULT; + } else + error = ENXIO; + + free(d_ptr, M_TEMP); + return (error); +} + +static void +ukbd_parse_hid(struct ukbd_softc *sc, const uint8_t *ptr, uint32_t len) +{ + uint32_t flags; + + /* reset detected bits */ + sc->sc_flags &= ~UKBD_FLAG_HID_MASK; + + /* check if there is an ID byte */ + sc->sc_kbd_size = hid_report_size(ptr, len, + hid_input, &sc->sc_kbd_id); + + /* investigate if this is an Apple Keyboard */ + if (hid_locate(ptr, len, + HID_USAGE2(HUP_CONSUMER, HUG_APPLE_EJECT), + hid_input, 0, &sc->sc_loc_apple_eject, &flags, + &sc->sc_id_apple_eject)) { + if (flags & HIO_VARIABLE) + sc->sc_flags |= UKBD_FLAG_APPLE_EJECT | + UKBD_FLAG_APPLE_SWAP; + DPRINTFN(1, "Found Apple eject-key\n"); + } + if (hid_locate(ptr, len, + HID_USAGE2(0xFFFF, 0x0003), + hid_input, 0, &sc->sc_loc_apple_fn, &flags, + &sc->sc_id_apple_fn)) { + if (flags & HIO_VARIABLE) + sc->sc_flags |= UKBD_FLAG_APPLE_FN; + DPRINTFN(1, "Found Apple FN-key\n"); + } + /* figure out some keys */ + if (hid_locate(ptr, len, + HID_USAGE2(HUP_KEYBOARD, 0xE0), + hid_input, 0, &sc->sc_loc_ctrl_l, &flags, + &sc->sc_id_ctrl_l)) { + if (flags & HIO_VARIABLE) + sc->sc_flags |= UKBD_FLAG_CTRL_L; + DPRINTFN(1, "Found left control\n"); + } + if (hid_locate(ptr, len, + HID_USAGE2(HUP_KEYBOARD, 0xE4), + hid_input, 0, &sc->sc_loc_ctrl_r, &flags, + &sc->sc_id_ctrl_r)) { + if (flags & HIO_VARIABLE) + sc->sc_flags |= UKBD_FLAG_CTRL_R; + DPRINTFN(1, "Found right control\n"); + } + if (hid_locate(ptr, len, + HID_USAGE2(HUP_KEYBOARD, 0xE1), + hid_input, 0, &sc->sc_loc_shift_l, &flags, + &sc->sc_id_shift_l)) { + if (flags & HIO_VARIABLE) + sc->sc_flags |= UKBD_FLAG_SHIFT_L; + DPRINTFN(1, "Found left shift\n"); + } + if (hid_locate(ptr, len, + HID_USAGE2(HUP_KEYBOARD, 0xE5), + hid_input, 0, &sc->sc_loc_shift_r, &flags, + &sc->sc_id_shift_r)) { + if (flags & HIO_VARIABLE) + sc->sc_flags |= UKBD_FLAG_SHIFT_R; + DPRINTFN(1, "Found right shift\n"); + } + if (hid_locate(ptr, len, + HID_USAGE2(HUP_KEYBOARD, 0xE2), + hid_input, 0, &sc->sc_loc_alt_l, &flags, + &sc->sc_id_alt_l)) { + if (flags & HIO_VARIABLE) + sc->sc_flags |= UKBD_FLAG_ALT_L; + DPRINTFN(1, "Found left alt\n"); + } + if (hid_locate(ptr, len, + HID_USAGE2(HUP_KEYBOARD, 0xE6), + hid_input, 0, &sc->sc_loc_alt_r, &flags, + &sc->sc_id_alt_r)) { + if (flags & HIO_VARIABLE) + sc->sc_flags |= UKBD_FLAG_ALT_R; + DPRINTFN(1, "Found right alt\n"); + } + if (hid_locate(ptr, len, + HID_USAGE2(HUP_KEYBOARD, 0xE3), + hid_input, 0, &sc->sc_loc_win_l, &flags, + &sc->sc_id_win_l)) { + if (flags & HIO_VARIABLE) + sc->sc_flags |= UKBD_FLAG_WIN_L; + DPRINTFN(1, "Found left GUI\n"); + } + if (hid_locate(ptr, len, + HID_USAGE2(HUP_KEYBOARD, 0xE7), + hid_input, 0, &sc->sc_loc_win_r, &flags, + &sc->sc_id_win_r)) { + if (flags & HIO_VARIABLE) + sc->sc_flags |= UKBD_FLAG_WIN_R; + DPRINTFN(1, "Found right GUI\n"); + } + /* figure out event buffer */ + if (hid_locate(ptr, len, + HID_USAGE2(HUP_KEYBOARD, 0x00), + hid_input, 0, &sc->sc_loc_events, &flags, + &sc->sc_id_events)) { + sc->sc_flags |= UKBD_FLAG_EVENTS; + DPRINTFN(1, "Found keyboard events\n"); + } + + /* figure out leds on keyboard */ + sc->sc_led_size = hid_report_size(ptr, len, + hid_output, NULL); + + if (hid_locate(ptr, len, + HID_USAGE2(HUP_LEDS, 0x01), + hid_output, 0, &sc->sc_loc_numlock, &flags, + &sc->sc_id_numlock)) { + if (flags & HIO_VARIABLE) + sc->sc_flags |= UKBD_FLAG_NUMLOCK; + DPRINTFN(1, "Found keyboard numlock\n"); + } + if (hid_locate(ptr, len, + HID_USAGE2(HUP_LEDS, 0x02), + hid_output, 0, &sc->sc_loc_capslock, &flags, + &sc->sc_id_capslock)) { + if (flags & HIO_VARIABLE) + sc->sc_flags |= UKBD_FLAG_CAPSLOCK; + DPRINTFN(1, "Found keyboard capslock\n"); + } + if (hid_locate(ptr, len, + HID_USAGE2(HUP_LEDS, 0x03), + hid_output, 0, &sc->sc_loc_scrolllock, &flags, + &sc->sc_id_scrolllock)) { + if (flags & HIO_VARIABLE) + sc->sc_flags |= UKBD_FLAG_SCROLLLOCK; + DPRINTFN(1, "Found keyboard scrolllock\n"); + } +} + +static int +ukbd_attach(device_t dev) +{ + struct ukbd_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + int32_t unit = device_get_unit(dev); + keyboard_t *kbd = &sc->sc_kbd; + void *hid_ptr = NULL; + usb_error_t err; + uint16_t n; + uint16_t hid_len; + + UKBD_LOCK_ASSERT(); + + kbd_init_struct(kbd, UKBD_DRIVER_NAME, KB_OTHER, unit, 0, 0, 0); + + kbd->kb_data = (void *)sc; + + device_set_usb_desc(dev); + + sc->sc_udev = uaa->device; + sc->sc_iface = uaa->iface; + sc->sc_iface_index = uaa->info.bIfaceIndex; + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_mode = K_XLATE; + + usb_callout_init_mtx(&sc->sc_callout, &Giant, 0); + + err = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, ukbd_config, + UKBD_N_TRANSFER, sc, &Giant); + + if (err) { + DPRINTF("error=%s\n", usbd_errstr(err)); + goto detach; + } + /* setup default keyboard maps */ + + sc->sc_keymap = key_map; + sc->sc_accmap = accent_map; + for (n = 0; n < UKBD_NFKEY; n++) { + sc->sc_fkeymap[n] = fkey_tab[n]; + } + + kbd_set_maps(kbd, &sc->sc_keymap, &sc->sc_accmap, + sc->sc_fkeymap, UKBD_NFKEY); + + KBD_FOUND_DEVICE(kbd); + + ukbd_clear_state(kbd); + + /* + * FIXME: set the initial value for lock keys in "sc_state" + * according to the BIOS data? + */ + KBD_PROBE_DONE(kbd); + + /* get HID descriptor */ + err = usbd_req_get_hid_desc(uaa->device, NULL, &hid_ptr, + &hid_len, M_TEMP, uaa->info.bIfaceIndex); + + if (err == 0) { + DPRINTF("Parsing HID descriptor of %d bytes\n", + (int)hid_len); + + ukbd_parse_hid(sc, hid_ptr, hid_len); + + free(hid_ptr, M_TEMP); + } + + /* check if we should use the boot protocol */ + if (usb_test_quirk(uaa, UQ_KBD_BOOTPROTO) || + (err != 0) || (!(sc->sc_flags & UKBD_FLAG_EVENTS))) { + + DPRINTF("Forcing boot protocol\n"); + + err = usbd_req_set_protocol(sc->sc_udev, NULL, + sc->sc_iface_index, 0); + + if (err != 0) { + DPRINTF("Set protocol error=%s (ignored)\n", + usbd_errstr(err)); + } + + ukbd_parse_hid(sc, ukbd_boot_desc, sizeof(ukbd_boot_desc)); + } + + /* ignore if SETIDLE fails, hence it is not crucial */ + usbd_req_set_idle(sc->sc_udev, NULL, sc->sc_iface_index, 0, 0); + + ukbd_ioctl(kbd, KDSETLED, (caddr_t)&sc->sc_state); + + KBD_INIT_DONE(kbd); + + if (kbd_register(kbd) < 0) { + goto detach; + } + KBD_CONFIG_DONE(kbd); + + ukbd_enable(kbd); + +#ifdef KBD_INSTALL_CDEV + if (kbd_attach(kbd)) { + goto detach; + } +#endif + sc->sc_flags |= UKBD_FLAG_ATTACHED; + + if (bootverbose) { + genkbd_diag(kbd, bootverbose); + } + + /* start the keyboard */ + usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT]); + + return (0); /* success */ + +detach: + ukbd_detach(dev); + return (ENXIO); /* error */ +} + +static int +ukbd_detach(device_t dev) +{ + struct ukbd_softc *sc = device_get_softc(dev); + int error; + + UKBD_LOCK_ASSERT(); + + DPRINTF("\n"); + + sc->sc_flags |= UKBD_FLAG_GONE; + + usb_callout_stop(&sc->sc_callout); + + ukbd_disable(&sc->sc_kbd); + +#ifdef KBD_INSTALL_CDEV + if (sc->sc_flags & UKBD_FLAG_ATTACHED) { + error = kbd_detach(&sc->sc_kbd); + if (error) { + /* usb attach cannot return an error */ + device_printf(dev, "WARNING: kbd_detach() " + "returned non-zero! (ignored)\n"); + } + } +#endif + if (KBD_IS_CONFIGURED(&sc->sc_kbd)) { + error = kbd_unregister(&sc->sc_kbd); + if (error) { + /* usb attach cannot return an error */ + device_printf(dev, "WARNING: kbd_unregister() " + "returned non-zero! (ignored)\n"); + } + } + sc->sc_kbd.kb_flags = 0; + + usbd_transfer_unsetup(sc->sc_xfer, UKBD_N_TRANSFER); + + usb_callout_drain(&sc->sc_callout); + + DPRINTF("%s: disconnected\n", + device_get_nameunit(dev)); + + return (0); +} + +static int +ukbd_resume(device_t dev) +{ + struct ukbd_softc *sc = device_get_softc(dev); + + UKBD_LOCK_ASSERT(); + + ukbd_clear_state(&sc->sc_kbd); + + return (0); +} + +/* early keyboard probe, not supported */ +static int +ukbd_configure(int flags) +{ + return (0); +} + +/* detect a keyboard, not used */ +static int +ukbd__probe(int unit, void *arg, int flags) +{ + return (ENXIO); +} + +/* reset and initialize the device, not used */ +static int +ukbd_init(int unit, keyboard_t **kbdp, void *arg, int flags) +{ + return (ENXIO); +} + +/* test the interface to the device, not used */ +static int +ukbd_test_if(keyboard_t *kbd) +{ + return (0); +} + +/* finish using this keyboard, not used */ +static int +ukbd_term(keyboard_t *kbd) +{ + return (ENXIO); +} + +/* keyboard interrupt routine, not used */ +static int +ukbd_intr(keyboard_t *kbd, void *arg) +{ + return (0); +} + +/* lock the access to the keyboard, not used */ +static int +ukbd_lock(keyboard_t *kbd, int lock) +{ + return (1); +} + +/* + * Enable the access to the device; until this function is called, + * the client cannot read from the keyboard. + */ +static int +ukbd_enable(keyboard_t *kbd) +{ + + UKBD_LOCK(); + KBD_ACTIVATE(kbd); + UKBD_UNLOCK(); + + return (0); +} + +/* disallow the access to the device */ +static int +ukbd_disable(keyboard_t *kbd) +{ + + UKBD_LOCK(); + KBD_DEACTIVATE(kbd); + UKBD_UNLOCK(); + + return (0); +} + +/* check if data is waiting */ +/* Currently unused. */ +static int +ukbd_check(keyboard_t *kbd) +{ + struct ukbd_softc *sc = kbd->kb_data; + + UKBD_CTX_LOCK_ASSERT(); + + if (!KBD_IS_ACTIVE(kbd)) + return (0); + + if (sc->sc_flags & UKBD_FLAG_POLLING) + ukbd_do_poll(sc, 0); + +#ifdef UKBD_EMULATE_ATSCANCODE + if (sc->sc_buffered_char[0]) { + return (1); + } +#endif + if (sc->sc_inputs > 0) { + return (1); + } + return (0); +} + +/* check if char is waiting */ +static int +ukbd_check_char_locked(keyboard_t *kbd) +{ + struct ukbd_softc *sc = kbd->kb_data; + + UKBD_CTX_LOCK_ASSERT(); + + if (!KBD_IS_ACTIVE(kbd)) + return (0); + + if ((sc->sc_composed_char > 0) && + (!(sc->sc_flags & UKBD_FLAG_COMPOSE))) { + return (1); + } + return (ukbd_check(kbd)); +} + +static int +ukbd_check_char(keyboard_t *kbd) +{ + int result; + + UKBD_LOCK(); + result = ukbd_check_char_locked(kbd); + UKBD_UNLOCK(); + + return (result); +} + +/* read one byte from the keyboard if it's allowed */ +/* Currently unused. */ +static int +ukbd_read(keyboard_t *kbd, int wait) +{ + struct ukbd_softc *sc = kbd->kb_data; + int32_t usbcode; +#ifdef UKBD_EMULATE_ATSCANCODE + uint32_t keycode; + uint32_t scancode; + +#endif + + UKBD_CTX_LOCK_ASSERT(); + + if (!KBD_IS_ACTIVE(kbd)) + return (-1); + +#ifdef UKBD_EMULATE_ATSCANCODE + if (sc->sc_buffered_char[0]) { + scancode = sc->sc_buffered_char[0]; + if (scancode & SCAN_PREFIX) { + sc->sc_buffered_char[0] &= ~SCAN_PREFIX; + return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); + } + sc->sc_buffered_char[0] = sc->sc_buffered_char[1]; + sc->sc_buffered_char[1] = 0; + return (scancode); + } +#endif /* UKBD_EMULATE_ATSCANCODE */ + + /* XXX */ + usbcode = ukbd_get_key(sc, (wait == FALSE) ? 0 : 1); + if (!KBD_IS_ACTIVE(kbd) || (usbcode == -1)) + return (-1); + + ++(kbd->kb_count); + +#ifdef UKBD_EMULATE_ATSCANCODE + keycode = ukbd_trtab[KEY_INDEX(usbcode)]; + if (keycode == NN) { + return -1; + } + return (ukbd_key2scan(sc, keycode, sc->sc_ndata.modifiers, + (usbcode & KEY_RELEASE))); +#else /* !UKBD_EMULATE_ATSCANCODE */ + return (usbcode); +#endif /* UKBD_EMULATE_ATSCANCODE */ +} + +/* read char from the keyboard */ +static uint32_t +ukbd_read_char_locked(keyboard_t *kbd, int wait) +{ + struct ukbd_softc *sc = kbd->kb_data; + uint32_t action; + uint32_t keycode; + int32_t usbcode; +#ifdef UKBD_EMULATE_ATSCANCODE + uint32_t scancode; +#endif + + UKBD_CTX_LOCK_ASSERT(); + + if (!KBD_IS_ACTIVE(kbd)) + return (NOKEY); + +next_code: + + /* do we have a composed char to return ? */ + + if ((sc->sc_composed_char > 0) && + (!(sc->sc_flags & UKBD_FLAG_COMPOSE))) { + + action = sc->sc_composed_char; + sc->sc_composed_char = 0; + + if (action > 0xFF) { + goto errkey; + } + goto done; + } +#ifdef UKBD_EMULATE_ATSCANCODE + + /* do we have a pending raw scan code? */ + + if (sc->sc_mode == K_RAW) { + scancode = sc->sc_buffered_char[0]; + if (scancode) { + if (scancode & SCAN_PREFIX) { + sc->sc_buffered_char[0] = (scancode & ~SCAN_PREFIX); + return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); + } + sc->sc_buffered_char[0] = sc->sc_buffered_char[1]; + sc->sc_buffered_char[1] = 0; + return (scancode); + } + } +#endif /* UKBD_EMULATE_ATSCANCODE */ + + /* see if there is something in the keyboard port */ + /* XXX */ + usbcode = ukbd_get_key(sc, (wait == FALSE) ? 0 : 1); + if (usbcode == -1) { + return (NOKEY); + } + ++kbd->kb_count; + +#ifdef UKBD_EMULATE_ATSCANCODE + /* USB key index -> key code -> AT scan code */ + keycode = ukbd_trtab[KEY_INDEX(usbcode)]; + if (keycode == NN) { + return (NOKEY); + } + /* return an AT scan code for the K_RAW mode */ + if (sc->sc_mode == K_RAW) { + return (ukbd_key2scan(sc, keycode, sc->sc_ndata.modifiers, + (usbcode & KEY_RELEASE))); + } +#else /* !UKBD_EMULATE_ATSCANCODE */ + + /* return the byte as is for the K_RAW mode */ + if (sc->sc_mode == K_RAW) { + return (usbcode); + } + /* USB key index -> key code */ + keycode = ukbd_trtab[KEY_INDEX(usbcode)]; + if (keycode == NN) { + return (NOKEY); + } +#endif /* UKBD_EMULATE_ATSCANCODE */ + + switch (keycode) { + case 0x38: /* left alt (compose key) */ + if (usbcode & KEY_RELEASE) { + if (sc->sc_flags & UKBD_FLAG_COMPOSE) { + sc->sc_flags &= ~UKBD_FLAG_COMPOSE; + + if (sc->sc_composed_char > 0xFF) { + sc->sc_composed_char = 0; + } + } + } else { + if (!(sc->sc_flags & UKBD_FLAG_COMPOSE)) { + sc->sc_flags |= UKBD_FLAG_COMPOSE; + sc->sc_composed_char = 0; + } + } + break; + /* XXX: I don't like these... */ + case 0x5c: /* print screen */ + if (sc->sc_flags & ALTS) { + keycode = 0x54; /* sysrq */ + } + break; + case 0x68: /* pause/break */ + if (sc->sc_flags & CTLS) { + keycode = 0x6c; /* break */ + } + break; + } + + /* return the key code in the K_CODE mode */ + if (usbcode & KEY_RELEASE) { + keycode |= SCAN_RELEASE; + } + if (sc->sc_mode == K_CODE) { + return (keycode); + } + /* compose a character code */ + if (sc->sc_flags & UKBD_FLAG_COMPOSE) { + switch (keycode) { + /* key pressed, process it */ + case 0x47: + case 0x48: + case 0x49: /* keypad 7,8,9 */ + sc->sc_composed_char *= 10; + sc->sc_composed_char += keycode - 0x40; + goto check_composed; + + case 0x4B: + case 0x4C: + case 0x4D: /* keypad 4,5,6 */ + sc->sc_composed_char *= 10; + sc->sc_composed_char += keycode - 0x47; + goto check_composed; + + case 0x4F: + case 0x50: + case 0x51: /* keypad 1,2,3 */ + sc->sc_composed_char *= 10; + sc->sc_composed_char += keycode - 0x4E; + goto check_composed; + + case 0x52: /* keypad 0 */ + sc->sc_composed_char *= 10; + goto check_composed; + + /* key released, no interest here */ + case SCAN_RELEASE | 0x47: + case SCAN_RELEASE | 0x48: + case SCAN_RELEASE | 0x49: /* keypad 7,8,9 */ + case SCAN_RELEASE | 0x4B: + case SCAN_RELEASE | 0x4C: + case SCAN_RELEASE | 0x4D: /* keypad 4,5,6 */ + case SCAN_RELEASE | 0x4F: + case SCAN_RELEASE | 0x50: + case SCAN_RELEASE | 0x51: /* keypad 1,2,3 */ + case SCAN_RELEASE | 0x52: /* keypad 0 */ + goto next_code; + + case 0x38: /* left alt key */ + break; + + default: + if (sc->sc_composed_char > 0) { + sc->sc_flags &= ~UKBD_FLAG_COMPOSE; + sc->sc_composed_char = 0; + goto errkey; + } + break; + } + } + /* keycode to key action */ + action = genkbd_keyaction(kbd, SCAN_CHAR(keycode), + (keycode & SCAN_RELEASE), + &sc->sc_state, &sc->sc_accents); + if (action == NOKEY) { + goto next_code; + } +done: + return (action); + +check_composed: + if (sc->sc_composed_char <= 0xFF) { + goto next_code; + } +errkey: + return (ERRKEY); +} + +/* Currently wait is always false. */ +static uint32_t +ukbd_read_char(keyboard_t *kbd, int wait) +{ + uint32_t keycode; + + UKBD_LOCK(); + keycode = ukbd_read_char_locked(kbd, wait); + UKBD_UNLOCK(); + + return (keycode); +} + +/* some useful control functions */ +static int +ukbd_ioctl_locked(keyboard_t *kbd, u_long cmd, caddr_t arg) +{ + struct ukbd_softc *sc = kbd->kb_data; + int i; +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + int ival; + +#endif + + UKBD_LOCK_ASSERT(); + + switch (cmd) { + case KDGKBMODE: /* get keyboard mode */ + *(int *)arg = sc->sc_mode; + break; +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + case _IO('K', 7): + ival = IOCPARM_IVAL(arg); + arg = (caddr_t)&ival; + /* FALLTHROUGH */ +#endif + case KDSKBMODE: /* set keyboard mode */ + switch (*(int *)arg) { + case K_XLATE: + if (sc->sc_mode != K_XLATE) { + /* make lock key state and LED state match */ + sc->sc_state &= ~LOCK_MASK; + sc->sc_state |= KBD_LED_VAL(kbd); + } + /* FALLTHROUGH */ + case K_RAW: + case K_CODE: + if (sc->sc_mode != *(int *)arg) { + if ((sc->sc_flags & UKBD_FLAG_POLLING) == 0) + ukbd_clear_state(kbd); + sc->sc_mode = *(int *)arg; + } + break; + default: + return (EINVAL); + } + break; + + case KDGETLED: /* get keyboard LED */ + *(int *)arg = KBD_LED_VAL(kbd); + break; +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + case _IO('K', 66): + ival = IOCPARM_IVAL(arg); + arg = (caddr_t)&ival; + /* FALLTHROUGH */ +#endif + case KDSETLED: /* set keyboard LED */ + /* NOTE: lock key state in "sc_state" won't be changed */ + if (*(int *)arg & ~LOCK_MASK) + return (EINVAL); + + i = *(int *)arg; + + /* replace CAPS LED with ALTGR LED for ALTGR keyboards */ + if (sc->sc_mode == K_XLATE && + kbd->kb_keymap->n_keys > ALTGR_OFFSET) { + if (i & ALKED) + i |= CLKED; + else + i &= ~CLKED; + } + if (KBD_HAS_DEVICE(kbd)) + ukbd_set_leds(sc, i); + + KBD_LED_VAL(kbd) = *(int *)arg; + break; + case KDGKBSTATE: /* get lock key state */ + *(int *)arg = sc->sc_state & LOCK_MASK; + break; +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + case _IO('K', 20): + ival = IOCPARM_IVAL(arg); + arg = (caddr_t)&ival; + /* FALLTHROUGH */ +#endif + case KDSKBSTATE: /* set lock key state */ + if (*(int *)arg & ~LOCK_MASK) { + return (EINVAL); + } + sc->sc_state &= ~LOCK_MASK; + sc->sc_state |= *(int *)arg; + + /* set LEDs and quit */ + return (ukbd_ioctl(kbd, KDSETLED, arg)); + + case KDSETREPEAT: /* set keyboard repeat rate (new + * interface) */ + if (!KBD_HAS_DEVICE(kbd)) { + return (0); + } + if (((int *)arg)[1] < 0) { + return (EINVAL); + } + if (((int *)arg)[0] < 0) { + return (EINVAL); + } + if (((int *)arg)[0] < 200) /* fastest possible value */ + kbd->kb_delay1 = 200; + else + kbd->kb_delay1 = ((int *)arg)[0]; + kbd->kb_delay2 = ((int *)arg)[1]; + return (0); + +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + case _IO('K', 67): + ival = IOCPARM_IVAL(arg); + arg = (caddr_t)&ival; + /* FALLTHROUGH */ +#endif + case KDSETRAD: /* set keyboard repeat rate (old + * interface) */ + return (ukbd_set_typematic(kbd, *(int *)arg)); + + case PIO_KEYMAP: /* set keyboard translation table */ + case OPIO_KEYMAP: /* set keyboard translation table + * (compat) */ + case PIO_KEYMAPENT: /* set keyboard translation table + * entry */ + case PIO_DEADKEYMAP: /* set accent key translation table */ + sc->sc_accents = 0; + /* FALLTHROUGH */ + default: + return (genkbd_commonioctl(kbd, cmd, arg)); + } + + return (0); +} + +static int +ukbd_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg) +{ + int result; + + /* + * XXX KDGKBSTATE, KDSKBSTATE and KDSETLED can be called from any + * context where printf(9) can be called, which among other things + * includes interrupt filters and threads with any kinds of locks + * already held. For this reason it would be dangerous to acquire + * the Giant here unconditionally. On the other hand we have to + * have it to handle the ioctl. + * So we make our best effort to auto-detect whether we can grab + * the Giant or not. Blame syscons(4) for this. + */ + switch (cmd) { + case KDGKBSTATE: + case KDSKBSTATE: + case KDSETLED: + if (!mtx_owned(&Giant) && !SCHEDULER_STOPPED()) + return (EDEADLK); /* best I could come up with */ + /* FALLTHROUGH */ + default: + UKBD_LOCK(); + result = ukbd_ioctl_locked(kbd, cmd, arg); + UKBD_UNLOCK(); + return (result); + } +} + + +/* clear the internal state of the keyboard */ +static void +ukbd_clear_state(keyboard_t *kbd) +{ + struct ukbd_softc *sc = kbd->kb_data; + + UKBD_CTX_LOCK_ASSERT(); + + sc->sc_flags &= ~(UKBD_FLAG_COMPOSE | UKBD_FLAG_POLLING); + sc->sc_state &= LOCK_MASK; /* preserve locking key state */ + sc->sc_accents = 0; + sc->sc_composed_char = 0; +#ifdef UKBD_EMULATE_ATSCANCODE + sc->sc_buffered_char[0] = 0; + sc->sc_buffered_char[1] = 0; +#endif + memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata)); + memset(&sc->sc_odata, 0, sizeof(sc->sc_odata)); + memset(&sc->sc_ntime, 0, sizeof(sc->sc_ntime)); + memset(&sc->sc_otime, 0, sizeof(sc->sc_otime)); +} + +/* save the internal state, not used */ +static int +ukbd_get_state(keyboard_t *kbd, void *buf, size_t len) +{ + return (len == 0) ? 1 : -1; +} + +/* set the internal state, not used */ +static int +ukbd_set_state(keyboard_t *kbd, void *buf, size_t len) +{ + return (EINVAL); +} + +static int +ukbd_poll(keyboard_t *kbd, int on) +{ + struct ukbd_softc *sc = kbd->kb_data; + + UKBD_LOCK(); + if (on) { + sc->sc_flags |= UKBD_FLAG_POLLING; + sc->sc_poll_thread = curthread; + } else { + sc->sc_flags &= ~UKBD_FLAG_POLLING; + ukbd_start_timer(sc); /* start timer */ + } + UKBD_UNLOCK(); + + return (0); +} + +/* local functions */ + +static void +ukbd_set_leds(struct ukbd_softc *sc, uint8_t leds) +{ + + UKBD_LOCK_ASSERT(); + DPRINTF("leds=0x%02x\n", leds); + + sc->sc_leds = leds; + sc->sc_flags |= UKBD_FLAG_SET_LEDS; + + /* start transfer, if not already started */ + + usbd_transfer_start(sc->sc_xfer[UKBD_CTRL_LED]); +} + +static int +ukbd_set_typematic(keyboard_t *kbd, int code) +{ + static const int delays[] = {250, 500, 750, 1000}; + static const int rates[] = {34, 38, 42, 46, 50, 55, 59, 63, + 68, 76, 84, 92, 100, 110, 118, 126, + 136, 152, 168, 184, 200, 220, 236, 252, + 272, 304, 336, 368, 400, 440, 472, 504}; + + if (code & ~0x7f) { + return (EINVAL); + } + kbd->kb_delay1 = delays[(code >> 5) & 3]; + kbd->kb_delay2 = rates[code & 0x1f]; + return (0); +} + +#ifdef UKBD_EMULATE_ATSCANCODE +static int +ukbd_key2scan(struct ukbd_softc *sc, int code, int shift, int up) +{ + static const int scan[] = { + /* 89 */ + 0x11c, /* Enter */ + /* 90-99 */ + 0x11d, /* Ctrl-R */ + 0x135, /* Divide */ + 0x137 | SCAN_PREFIX_SHIFT, /* PrintScreen */ + 0x138, /* Alt-R */ + 0x147, /* Home */ + 0x148, /* Up */ + 0x149, /* PageUp */ + 0x14b, /* Left */ + 0x14d, /* Right */ + 0x14f, /* End */ + /* 100-109 */ + 0x150, /* Down */ + 0x151, /* PageDown */ + 0x152, /* Insert */ + 0x153, /* Delete */ + 0x146, /* XXX Pause/Break */ + 0x15b, /* Win_L(Super_L) */ + 0x15c, /* Win_R(Super_R) */ + 0x15d, /* Application(Menu) */ + + /* SUN TYPE 6 USB KEYBOARD */ + 0x168, /* Sun Type 6 Help */ + 0x15e, /* Sun Type 6 Stop */ + /* 110 - 119 */ + 0x15f, /* Sun Type 6 Again */ + 0x160, /* Sun Type 6 Props */ + 0x161, /* Sun Type 6 Undo */ + 0x162, /* Sun Type 6 Front */ + 0x163, /* Sun Type 6 Copy */ + 0x164, /* Sun Type 6 Open */ + 0x165, /* Sun Type 6 Paste */ + 0x166, /* Sun Type 6 Find */ + 0x167, /* Sun Type 6 Cut */ + 0x125, /* Sun Type 6 Mute */ + /* 120 - 128 */ + 0x11f, /* Sun Type 6 VolumeDown */ + 0x11e, /* Sun Type 6 VolumeUp */ + 0x120, /* Sun Type 6 PowerDown */ + + /* Japanese 106/109 keyboard */ + 0x73, /* Keyboard Intl' 1 (backslash / underscore) */ + 0x70, /* Keyboard Intl' 2 (Katakana / Hiragana) */ + 0x7d, /* Keyboard Intl' 3 (Yen sign) (Not using in jp106/109) */ + 0x79, /* Keyboard Intl' 4 (Henkan) */ + 0x7b, /* Keyboard Intl' 5 (Muhenkan) */ + 0x5c, /* Keyboard Intl' 6 (Keypad ,) (For PC-9821 layout) */ + }; + + if ((code >= 89) && (code < (89 + (sizeof(scan) / sizeof(scan[0]))))) { + code = scan[code - 89]; + } + /* Pause/Break */ + if ((code == 104) && (!(shift & (MOD_CONTROL_L | MOD_CONTROL_R)))) { + code = (0x45 | SCAN_PREFIX_E1 | SCAN_PREFIX_CTL); + } + if (shift & (MOD_SHIFT_L | MOD_SHIFT_R)) { + code &= ~SCAN_PREFIX_SHIFT; + } + code |= (up ? SCAN_RELEASE : SCAN_PRESS); + + if (code & SCAN_PREFIX) { + if (code & SCAN_PREFIX_CTL) { + /* Ctrl */ + sc->sc_buffered_char[0] = (0x1d | (code & SCAN_RELEASE)); + sc->sc_buffered_char[1] = (code & ~SCAN_PREFIX); + } else if (code & SCAN_PREFIX_SHIFT) { + /* Shift */ + sc->sc_buffered_char[0] = (0x2a | (code & SCAN_RELEASE)); + sc->sc_buffered_char[1] = (code & ~SCAN_PREFIX_SHIFT); + } else { + sc->sc_buffered_char[0] = (code & ~SCAN_PREFIX); + sc->sc_buffered_char[1] = 0; + } + return ((code & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); + } + return (code); + +} + +#endif /* UKBD_EMULATE_ATSCANCODE */ + +static keyboard_switch_t ukbdsw = { + .probe = &ukbd__probe, + .init = &ukbd_init, + .term = &ukbd_term, + .intr = &ukbd_intr, + .test_if = &ukbd_test_if, + .enable = &ukbd_enable, + .disable = &ukbd_disable, + .read = &ukbd_read, + .check = &ukbd_check, + .read_char = &ukbd_read_char, + .check_char = &ukbd_check_char, + .ioctl = &ukbd_ioctl, + .lock = &ukbd_lock, + .clear_state = &ukbd_clear_state, + .get_state = &ukbd_get_state, + .set_state = &ukbd_set_state, + .get_fkeystr = &genkbd_get_fkeystr, + .poll = &ukbd_poll, + .diag = &genkbd_diag, +}; + +KEYBOARD_DRIVER(ukbd, ukbdsw, ukbd_configure); + +static int +ukbd_driver_load(module_t mod, int what, void *arg) +{ + switch (what) { + case MOD_LOAD: + kbd_add_driver(&ukbd_kbd_driver); + break; + case MOD_UNLOAD: + kbd_delete_driver(&ukbd_kbd_driver); + break; + } + return (0); +} + +static devclass_t ukbd_devclass; + +static device_method_t ukbd_methods[] = { + DEVMETHOD(device_probe, ukbd_probe), + DEVMETHOD(device_attach, ukbd_attach), + DEVMETHOD(device_detach, ukbd_detach), + DEVMETHOD(device_resume, ukbd_resume), + {0, 0} +}; + +static driver_t ukbd_driver = { + .name = "ukbd", + .methods = ukbd_methods, + .size = sizeof(struct ukbd_softc), +}; + +DRIVER_MODULE(ukbd, uhub, ukbd_driver, ukbd_devclass, ukbd_driver_load, 0); +MODULE_DEPEND(ukbd, usb, 1, 1, 1); +MODULE_VERSION(ukbd, 1); diff --git a/sys/bus/u4b/input/ums.c b/sys/bus/u4b/input/ums.c new file mode 100644 index 0000000000..a910df4e8a --- /dev/null +++ b/sys/bus/u4b/input/ums.c @@ -0,0 +1,1065 @@ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR ums_debug +#include + +#include + +#include +#include +#include +#include + +#ifdef USB_DEBUG +static int ums_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ums, CTLFLAG_RW, 0, "USB ums"); +SYSCTL_INT(_hw_usb_ums, OID_AUTO, debug, CTLFLAG_RW, + &ums_debug, 0, "Debug level"); +#endif + +#define MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE) +#define MOUSE_FLAGS (HIO_RELATIVE) + +#define UMS_BUF_SIZE 8 /* bytes */ +#define UMS_IFQ_MAXLEN 50 /* units */ +#define UMS_BUTTON_MAX 31 /* exclusive, must be less than 32 */ +#define UMS_BUT(i) ((i) < 3 ? (((i) + 2) % 3) : (i)) +#define UMS_INFO_MAX 2 /* maximum number of HID sets */ + +enum { + UMS_INTR_DT, + UMS_N_TRANSFER, +}; + +struct ums_info { + struct hid_location sc_loc_w; + struct hid_location sc_loc_x; + struct hid_location sc_loc_y; + struct hid_location sc_loc_z; + struct hid_location sc_loc_t; + struct hid_location sc_loc_btn[UMS_BUTTON_MAX]; + + uint32_t sc_flags; +#define UMS_FLAG_X_AXIS 0x0001 +#define UMS_FLAG_Y_AXIS 0x0002 +#define UMS_FLAG_Z_AXIS 0x0004 +#define UMS_FLAG_T_AXIS 0x0008 +#define UMS_FLAG_SBU 0x0010 /* spurious button up events */ +#define UMS_FLAG_REVZ 0x0020 /* Z-axis is reversed */ +#define UMS_FLAG_W_AXIS 0x0040 + + uint8_t sc_iid_w; + uint8_t sc_iid_x; + uint8_t sc_iid_y; + uint8_t sc_iid_z; + uint8_t sc_iid_t; + uint8_t sc_iid_btn[UMS_BUTTON_MAX]; + uint8_t sc_buttons; +}; + +struct ums_softc { + struct usb_fifo_sc sc_fifo; + struct mtx sc_mtx; + struct usb_callout sc_callout; + struct ums_info sc_info[UMS_INFO_MAX]; + + mousehw_t sc_hw; + mousemode_t sc_mode; + mousestatus_t sc_status; + + struct usb_xfer *sc_xfer[UMS_N_TRANSFER]; + + int sc_pollrate; + + uint8_t sc_buttons; + uint8_t sc_iid; + uint8_t sc_temp[64]; +}; + +static void ums_put_queue_timeout(void *__sc); + +static usb_callback_t ums_intr_callback; + +static device_probe_t ums_probe; +static device_attach_t ums_attach; +static device_detach_t ums_detach; + +static usb_fifo_cmd_t ums_start_read; +static usb_fifo_cmd_t ums_stop_read; +static usb_fifo_open_t ums_open; +static usb_fifo_close_t ums_close; +static usb_fifo_ioctl_t ums_ioctl; + +static void ums_put_queue(struct ums_softc *, int32_t, int32_t, + int32_t, int32_t, int32_t); +static int ums_sysctl_handler_parseinfo(SYSCTL_HANDLER_ARGS); + +static struct usb_fifo_methods ums_fifo_methods = { + .f_open = &ums_open, + .f_close = &ums_close, + .f_ioctl = &ums_ioctl, + .f_start_read = &ums_start_read, + .f_stop_read = &ums_stop_read, + .basename[0] = "ums", +}; + +static void +ums_put_queue_timeout(void *__sc) +{ + struct ums_softc *sc = __sc; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + ums_put_queue(sc, 0, 0, 0, 0, 0); +} + +static void +ums_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ums_softc *sc = usbd_xfer_softc(xfer); + struct ums_info *info = &sc->sc_info[0]; + struct usb_page_cache *pc; + uint8_t *buf = sc->sc_temp; + int32_t buttons = 0; + int32_t buttons_found = 0; + int32_t dw = 0; + int32_t dx = 0; + int32_t dy = 0; + int32_t dz = 0; + int32_t dt = 0; + uint8_t i; + uint8_t id; + int len; + + usbd_xfer_status(xfer, &len, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(6, "sc=%p actlen=%d\n", sc, len); + + if (len > sizeof(sc->sc_temp)) { + DPRINTFN(6, "truncating large packet to %zu bytes\n", + sizeof(sc->sc_temp)); + len = sizeof(sc->sc_temp); + } + if (len == 0) + goto tr_setup; + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, len); + + DPRINTFN(6, "data = %02x %02x %02x %02x " + "%02x %02x %02x %02x\n", + (len > 0) ? buf[0] : 0, (len > 1) ? buf[1] : 0, + (len > 2) ? buf[2] : 0, (len > 3) ? buf[3] : 0, + (len > 4) ? buf[4] : 0, (len > 5) ? buf[5] : 0, + (len > 6) ? buf[6] : 0, (len > 7) ? buf[7] : 0); + + if (sc->sc_iid) { + id = *buf; + + len--; + buf++; + + } else { + id = 0; + if (sc->sc_info[0].sc_flags & UMS_FLAG_SBU) { + if ((*buf == 0x14) || (*buf == 0x15)) { + goto tr_setup; + } + } + } + + repeat: + if ((info->sc_flags & UMS_FLAG_W_AXIS) && + (id == info->sc_iid_w)) + dw += hid_get_data(buf, len, &info->sc_loc_w); + + if ((info->sc_flags & UMS_FLAG_X_AXIS) && + (id == info->sc_iid_x)) + dx += hid_get_data(buf, len, &info->sc_loc_x); + + if ((info->sc_flags & UMS_FLAG_Y_AXIS) && + (id == info->sc_iid_y)) + dy = -hid_get_data(buf, len, &info->sc_loc_y); + + if ((info->sc_flags & UMS_FLAG_Z_AXIS) && + (id == info->sc_iid_z)) { + int32_t temp; + temp = hid_get_data(buf, len, &info->sc_loc_z); + if (info->sc_flags & UMS_FLAG_REVZ) + temp = -temp; + dz -= temp; + } + + if ((info->sc_flags & UMS_FLAG_T_AXIS) && + (id == info->sc_iid_t)) + dt -= hid_get_data(buf, len, &info->sc_loc_t); + + for (i = 0; i < info->sc_buttons; i++) { + uint32_t mask; + mask = 1UL << UMS_BUT(i); + /* check for correct button ID */ + if (id != info->sc_iid_btn[i]) + continue; + /* check for button pressed */ + if (hid_get_data(buf, len, &info->sc_loc_btn[i])) + buttons |= mask; + /* register button mask */ + buttons_found |= mask; + } + + if (++info != &sc->sc_info[UMS_INFO_MAX]) + goto repeat; + + /* keep old button value(s) for non-detected buttons */ + buttons |= sc->sc_status.button & ~buttons_found; + + if (dx || dy || dz || dt || dw || + (buttons != sc->sc_status.button)) { + + DPRINTFN(6, "x:%d y:%d z:%d t:%d w:%d buttons:0x%08x\n", + dx, dy, dz, dt, dw, buttons); + + /* translate T-axis into button presses until further */ + if (dt > 0) + buttons |= 1UL << 3; + else if (dt < 0) + buttons |= 1UL << 4; + + sc->sc_status.button = buttons; + sc->sc_status.dx += dx; + sc->sc_status.dy += dy; + sc->sc_status.dz += dz; + /* + * sc->sc_status.dt += dt; + * no way to export this yet + */ + + /* + * The Qtronix keyboard has a built in PS/2 + * port for a mouse. The firmware once in a + * while posts a spurious button up + * event. This event we ignore by doing a + * timeout for 50 msecs. If we receive + * dx=dy=dz=buttons=0 before we add the event + * to the queue. In any other case we delete + * the timeout event. + */ + if ((sc->sc_info[0].sc_flags & UMS_FLAG_SBU) && + (dx == 0) && (dy == 0) && (dz == 0) && (dt == 0) && + (dw == 0) && (buttons == 0)) { + + usb_callout_reset(&sc->sc_callout, hz / 20, + &ums_put_queue_timeout, sc); + } else { + + usb_callout_stop(&sc->sc_callout); + + ums_put_queue(sc, dx, dy, dz, dt, buttons); + } + } + case USB_ST_SETUP: +tr_setup: + /* check if we can put more data into the FIFO */ + if (usb_fifo_put_bytes_max( + sc->sc_fifo.fp[USB_FIFO_RX]) != 0) { + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static const struct usb_config ums_config[UMS_N_TRANSFER] = { + + [UMS_INTR_DT] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &ums_intr_callback, + }, +}; + +/* A match on these entries will load ums */ +static const STRUCT_USB_HOST_ID __used ums_devs[] = { + {USB_IFACE_CLASS(UICLASS_HID), + USB_IFACE_SUBCLASS(UISUBCLASS_BOOT), + USB_IFACE_PROTOCOL(UIPROTO_MOUSE),}, +}; + +static int +ums_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + void *d_ptr; + struct hid_data *hd; + struct hid_item hi; + int error, mdepth, found; + uint16_t d_len; + + DPRINTFN(11, "\n"); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + + if (uaa->info.bInterfaceClass != UICLASS_HID) + return (ENXIO); + + if ((uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) && + (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE)) + return (BUS_PROBE_DEFAULT); + + error = usbd_req_get_hid_desc(uaa->device, NULL, + &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex); + + if (error) + return (ENXIO); + + hd = hid_start_parse(d_ptr, d_len, 1 << hid_input); + if (hd == NULL) + return (0); + mdepth = 0; + found = 0; + while (hid_get_item(hd, &hi)) { + switch (hi.kind) { + case hid_collection: + if (mdepth != 0) + mdepth++; + else if (hi.collection == 1 && + hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)) + mdepth++; + break; + case hid_endcollection: + if (mdepth != 0) + mdepth--; + break; + case hid_input: + if (mdepth == 0) + break; + if (hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X) && + (hi.flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) + found++; + if (hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y) && + (hi.flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) + found++; + break; + default: + break; + } + } + hid_end_parse(hd); + free(d_ptr, M_TEMP); + return (found ? BUS_PROBE_DEFAULT : ENXIO); +} + +static void +ums_hid_parse(struct ums_softc *sc, device_t dev, const uint8_t *buf, + uint16_t len, uint8_t index) +{ + struct ums_info *info = &sc->sc_info[index]; + uint32_t flags; + uint8_t i; + uint8_t j; + + if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), + hid_input, index, &info->sc_loc_x, &flags, &info->sc_iid_x)) { + + if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { + info->sc_flags |= UMS_FLAG_X_AXIS; + } + } + if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), + hid_input, index, &info->sc_loc_y, &flags, &info->sc_iid_y)) { + + if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { + info->sc_flags |= UMS_FLAG_Y_AXIS; + } + } + /* Try the wheel first as the Z activator since it's tradition. */ + if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, + HUG_WHEEL), hid_input, index, &info->sc_loc_z, &flags, + &info->sc_iid_z) || + hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, + HUG_TWHEEL), hid_input, index, &info->sc_loc_z, &flags, + &info->sc_iid_z)) { + if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { + info->sc_flags |= UMS_FLAG_Z_AXIS; + } + /* + * We might have both a wheel and Z direction, if so put + * put the Z on the W coordinate. + */ + if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, + HUG_Z), hid_input, index, &info->sc_loc_w, &flags, + &info->sc_iid_w)) { + + if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { + info->sc_flags |= UMS_FLAG_W_AXIS; + } + } + } else if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, + HUG_Z), hid_input, index, &info->sc_loc_z, &flags, + &info->sc_iid_z)) { + + if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { + info->sc_flags |= UMS_FLAG_Z_AXIS; + } + } + /* + * The Microsoft Wireless Intellimouse 2.0 reports it's wheel + * using 0x0048, which is HUG_TWHEEL, and seems to expect you + * to know that the byte after the wheel is the tilt axis. + * There are no other HID axis descriptors other than X,Y and + * TWHEEL + */ + if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, + HUG_TWHEEL), hid_input, index, &info->sc_loc_t, + &flags, &info->sc_iid_t)) { + + info->sc_loc_t.pos += 8; + + if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) { + info->sc_flags |= UMS_FLAG_T_AXIS; + } + } else if (hid_locate(buf, len, HID_USAGE2(HUP_CONSUMER, + HUC_AC_PAN), hid_input, index, &info->sc_loc_t, + &flags, &info->sc_iid_t)) { + + if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) + info->sc_flags |= UMS_FLAG_T_AXIS; + } + /* figure out the number of buttons */ + + for (i = 0; i < UMS_BUTTON_MAX; i++) { + if (!hid_locate(buf, len, HID_USAGE2(HUP_BUTTON, (i + 1)), + hid_input, index, &info->sc_loc_btn[i], NULL, + &info->sc_iid_btn[i])) { + break; + } + } + + /* detect other buttons */ + + for (j = 0; (i < UMS_BUTTON_MAX) && (j < 2); i++, j++) { + if (!hid_locate(buf, len, HID_USAGE2(HUP_MICROSOFT, (j + 1)), + hid_input, index, &info->sc_loc_btn[i], NULL, + &info->sc_iid_btn[i])) { + break; + } + } + + info->sc_buttons = i; + + if (i > sc->sc_buttons) + sc->sc_buttons = i; + + if (info->sc_flags == 0) + return; + + /* announce information about the mouse */ + device_printf(dev, "%d buttons and [%s%s%s%s%s] coordinates ID=%u\n", + (info->sc_buttons), + (info->sc_flags & UMS_FLAG_X_AXIS) ? "X" : "", + (info->sc_flags & UMS_FLAG_Y_AXIS) ? "Y" : "", + (info->sc_flags & UMS_FLAG_Z_AXIS) ? "Z" : "", + (info->sc_flags & UMS_FLAG_T_AXIS) ? "T" : "", + (info->sc_flags & UMS_FLAG_W_AXIS) ? "W" : "", + info->sc_iid_x); +} + +static int +ums_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ums_softc *sc = device_get_softc(dev); + struct ums_info *info; + void *d_ptr = NULL; + int isize; + int err; + uint16_t d_len; + uint8_t i; +#ifdef USB_DEBUG + uint8_t j; +#endif + + DPRINTFN(11, "sc=%p\n", sc); + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, "ums lock", NULL, MTX_DEF | MTX_RECURSE); + + usb_callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0); + + /* + * Force the report (non-boot) protocol. + * + * Mice without boot protocol support may choose not to implement + * Set_Protocol at all; Ignore any error. + */ + err = usbd_req_set_protocol(uaa->device, NULL, + uaa->info.bIfaceIndex, 1); + + err = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, ums_config, + UMS_N_TRANSFER, sc, &sc->sc_mtx); + + if (err) { + DPRINTF("error=%s\n", usbd_errstr(err)); + goto detach; + } + + /* Get HID descriptor */ + err = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr, + &d_len, M_TEMP, uaa->info.bIfaceIndex); + + if (err) { + device_printf(dev, "error reading report description\n"); + goto detach; + } + + isize = hid_report_size(d_ptr, d_len, hid_input, &sc->sc_iid); + + /* + * The Microsoft Wireless Notebook Optical Mouse seems to be in worse + * shape than the Wireless Intellimouse 2.0, as its X, Y, wheel, and + * all of its other button positions are all off. It also reports that + * it has two addional buttons and a tilt wheel. + */ + if (usb_test_quirk(uaa, UQ_MS_BAD_CLASS)) { + + sc->sc_iid = 0; + + info = &sc->sc_info[0]; + info->sc_flags = (UMS_FLAG_X_AXIS | + UMS_FLAG_Y_AXIS | + UMS_FLAG_Z_AXIS | + UMS_FLAG_SBU); + info->sc_buttons = 3; + isize = 5; + /* 1st byte of descriptor report contains garbage */ + info->sc_loc_x.pos = 16; + info->sc_loc_x.size = 8; + info->sc_loc_y.pos = 24; + info->sc_loc_y.size = 8; + info->sc_loc_z.pos = 32; + info->sc_loc_z.size = 8; + info->sc_loc_btn[0].pos = 8; + info->sc_loc_btn[0].size = 1; + info->sc_loc_btn[1].pos = 9; + info->sc_loc_btn[1].size = 1; + info->sc_loc_btn[2].pos = 10; + info->sc_loc_btn[2].size = 1; + + /* Announce device */ + device_printf(dev, "3 buttons and [XYZ] " + "coordinates ID=0\n"); + + } else { + /* Search the HID descriptor and announce device */ + for (i = 0; i < UMS_INFO_MAX; i++) { + ums_hid_parse(sc, dev, d_ptr, d_len, i); + } + } + + if (usb_test_quirk(uaa, UQ_MS_REVZ)) { + info = &sc->sc_info[0]; + /* Some wheels need the Z axis reversed. */ + info->sc_flags |= UMS_FLAG_REVZ; + } + if (isize > usbd_xfer_max_framelen(sc->sc_xfer[UMS_INTR_DT])) { + DPRINTF("WARNING: report size, %d bytes, is larger " + "than interrupt size, %d bytes!\n", isize, + usbd_xfer_max_framelen(sc->sc_xfer[UMS_INTR_DT])); + } + free(d_ptr, M_TEMP); + d_ptr = NULL; + +#ifdef USB_DEBUG + for (j = 0; j < UMS_INFO_MAX; j++) { + info = &sc->sc_info[j]; + + DPRINTF("sc=%p, index=%d\n", sc, j); + DPRINTF("X\t%d/%d id=%d\n", info->sc_loc_x.pos, + info->sc_loc_x.size, info->sc_iid_x); + DPRINTF("Y\t%d/%d id=%d\n", info->sc_loc_y.pos, + info->sc_loc_y.size, info->sc_iid_y); + DPRINTF("Z\t%d/%d id=%d\n", info->sc_loc_z.pos, + info->sc_loc_z.size, info->sc_iid_z); + DPRINTF("T\t%d/%d id=%d\n", info->sc_loc_t.pos, + info->sc_loc_t.size, info->sc_iid_t); + DPRINTF("W\t%d/%d id=%d\n", info->sc_loc_w.pos, + info->sc_loc_w.size, info->sc_iid_w); + + for (i = 0; i < info->sc_buttons; i++) { + DPRINTF("B%d\t%d/%d id=%d\n", + i + 1, info->sc_loc_btn[i].pos, + info->sc_loc_btn[i].size, info->sc_iid_btn[i]); + } + } + DPRINTF("size=%d, id=%d\n", isize, sc->sc_iid); +#endif + + if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON) + sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON; + else + sc->sc_hw.buttons = sc->sc_buttons; + + sc->sc_hw.iftype = MOUSE_IF_USB; + sc->sc_hw.type = MOUSE_MOUSE; + sc->sc_hw.model = MOUSE_MODEL_GENERIC; + sc->sc_hw.hwid = 0; + + sc->sc_mode.protocol = MOUSE_PROTO_MSC; + sc->sc_mode.rate = -1; + sc->sc_mode.resolution = MOUSE_RES_UNKNOWN; + sc->sc_mode.accelfactor = 0; + sc->sc_mode.level = 0; + sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; + + err = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &ums_fifo_methods, &sc->sc_fifo, + device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + if (err) { + goto detach; + } + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "parseinfo", CTLTYPE_STRING|CTLFLAG_RD, + sc, 0, ums_sysctl_handler_parseinfo, + "", "Dump of parsed HID report descriptor"); + + return (0); + +detach: + if (d_ptr) { + free(d_ptr, M_TEMP); + } + ums_detach(dev); + return (ENOMEM); +} + +static int +ums_detach(device_t self) +{ + struct ums_softc *sc = device_get_softc(self); + + DPRINTF("sc=%p\n", sc); + + usb_fifo_detach(&sc->sc_fifo); + + usbd_transfer_unsetup(sc->sc_xfer, UMS_N_TRANSFER); + + usb_callout_drain(&sc->sc_callout); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +ums_start_read(struct usb_fifo *fifo) +{ + struct ums_softc *sc = usb_fifo_softc(fifo); + int rate; + + /* Check if we should override the default polling interval */ + rate = sc->sc_pollrate; + /* Range check rate */ + if (rate > 1000) + rate = 1000; + /* Check for set rate */ + if ((rate > 0) && (sc->sc_xfer[UMS_INTR_DT] != NULL)) { + DPRINTF("Setting pollrate = %d\n", rate); + /* Stop current transfer, if any */ + usbd_transfer_stop(sc->sc_xfer[UMS_INTR_DT]); + /* Set new interval */ + usbd_xfer_set_interval(sc->sc_xfer[UMS_INTR_DT], 1000 / rate); + /* Only set pollrate once */ + sc->sc_pollrate = 0; + } + + usbd_transfer_start(sc->sc_xfer[UMS_INTR_DT]); +} + +static void +ums_stop_read(struct usb_fifo *fifo) +{ + struct ums_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_stop(sc->sc_xfer[UMS_INTR_DT]); + usb_callout_stop(&sc->sc_callout); +} + + +#if ((MOUSE_SYS_PACKETSIZE != 8) || \ + (MOUSE_MSC_PACKETSIZE != 5)) +#error "Software assumptions are not met. Please update code." +#endif + +static void +ums_put_queue(struct ums_softc *sc, int32_t dx, int32_t dy, + int32_t dz, int32_t dt, int32_t buttons) +{ + uint8_t buf[8]; + + if (1) { + + if (dx > 254) + dx = 254; + if (dx < -256) + dx = -256; + if (dy > 254) + dy = 254; + if (dy < -256) + dy = -256; + if (dz > 126) + dz = 126; + if (dz < -128) + dz = -128; + if (dt > 126) + dt = 126; + if (dt < -128) + dt = -128; + + buf[0] = sc->sc_mode.syncmask[1]; + buf[0] |= (~buttons) & MOUSE_MSC_BUTTONS; + buf[1] = dx >> 1; + buf[2] = dy >> 1; + buf[3] = dx - (dx >> 1); + buf[4] = dy - (dy >> 1); + + if (sc->sc_mode.level == 1) { + buf[5] = dz >> 1; + buf[6] = dz - (dz >> 1); + buf[7] = (((~buttons) >> 3) & MOUSE_SYS_EXTBUTTONS); + } + usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf, + sc->sc_mode.packetsize, 1); + + } else { + DPRINTF("Buffer full, discarded packet\n"); + } +} + +static void +ums_reset_buf(struct ums_softc *sc) +{ + /* reset read queue */ + usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]); +} + +static int +ums_open(struct usb_fifo *fifo, int fflags) +{ + struct ums_softc *sc = usb_fifo_softc(fifo); + + DPRINTFN(2, "\n"); + + if (fflags & FREAD) { + + /* reset status */ + + sc->sc_status.flags = 0; + sc->sc_status.button = 0; + sc->sc_status.obutton = 0; + sc->sc_status.dx = 0; + sc->sc_status.dy = 0; + sc->sc_status.dz = 0; + /* sc->sc_status.dt = 0; */ + + if (usb_fifo_alloc_buffer(fifo, + UMS_BUF_SIZE, UMS_IFQ_MAXLEN)) { + return (ENOMEM); + } + } + return (0); +} + +static void +ums_close(struct usb_fifo *fifo, int fflags) +{ + if (fflags & FREAD) { + usb_fifo_free_buffer(fifo); + } +} + +static int +ums_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags) +{ + struct ums_softc *sc = usb_fifo_softc(fifo); + mousemode_t mode; + int error = 0; + + DPRINTFN(2, "\n"); + + mtx_lock(&sc->sc_mtx); + + switch (cmd) { + case MOUSE_GETHWINFO: + *(mousehw_t *)addr = sc->sc_hw; + break; + + case MOUSE_GETMODE: + *(mousemode_t *)addr = sc->sc_mode; + break; + + case MOUSE_SETMODE: + mode = *(mousemode_t *)addr; + + if (mode.level == -1) { + /* don't change the current setting */ + } else if ((mode.level < 0) || (mode.level > 1)) { + error = EINVAL; + goto done; + } else { + sc->sc_mode.level = mode.level; + } + + /* store polling rate */ + sc->sc_pollrate = mode.rate; + + if (sc->sc_mode.level == 0) { + if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON) + sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON; + else + sc->sc_hw.buttons = sc->sc_buttons; + sc->sc_mode.protocol = MOUSE_PROTO_MSC; + sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; + } else if (sc->sc_mode.level == 1) { + if (sc->sc_buttons > MOUSE_SYS_MAXBUTTON) + sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON; + else + sc->sc_hw.buttons = sc->sc_buttons; + sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE; + sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC; + } + ums_reset_buf(sc); + break; + + case MOUSE_GETLEVEL: + *(int *)addr = sc->sc_mode.level; + break; + + case MOUSE_SETLEVEL: + if (*(int *)addr < 0 || *(int *)addr > 1) { + error = EINVAL; + goto done; + } + sc->sc_mode.level = *(int *)addr; + + if (sc->sc_mode.level == 0) { + if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON) + sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON; + else + sc->sc_hw.buttons = sc->sc_buttons; + sc->sc_mode.protocol = MOUSE_PROTO_MSC; + sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; + } else if (sc->sc_mode.level == 1) { + if (sc->sc_buttons > MOUSE_SYS_MAXBUTTON) + sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON; + else + sc->sc_hw.buttons = sc->sc_buttons; + sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE; + sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE; + sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK; + sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC; + } + ums_reset_buf(sc); + break; + + case MOUSE_GETSTATUS:{ + mousestatus_t *status = (mousestatus_t *)addr; + + *status = sc->sc_status; + sc->sc_status.obutton = sc->sc_status.button; + sc->sc_status.button = 0; + sc->sc_status.dx = 0; + sc->sc_status.dy = 0; + sc->sc_status.dz = 0; + /* sc->sc_status.dt = 0; */ + + if (status->dx || status->dy || status->dz /* || status->dt */ ) { + status->flags |= MOUSE_POSCHANGED; + } + if (status->button != status->obutton) { + status->flags |= MOUSE_BUTTONSCHANGED; + } + break; + } + default: + error = ENOTTY; + } + +done: + mtx_unlock(&sc->sc_mtx); + return (error); +} + +static int +ums_sysctl_handler_parseinfo(SYSCTL_HANDLER_ARGS) +{ + struct ums_softc *sc = arg1; + struct ums_info *info; + struct sbuf *sb; + int i, j, err, had_output; + + sb = sbuf_new_auto(); + for (i = 0, had_output = 0; i < UMS_INFO_MAX; i++) { + info = &sc->sc_info[i]; + + /* Don't emit empty info */ + if ((info->sc_flags & + (UMS_FLAG_X_AXIS | UMS_FLAG_Y_AXIS | UMS_FLAG_Z_AXIS | + UMS_FLAG_T_AXIS | UMS_FLAG_W_AXIS)) == 0 && + info->sc_buttons == 0) + continue; + + if (had_output) + sbuf_printf(sb, "\n"); + had_output = 1; + sbuf_printf(sb, "i%d:", i + 1); + if (info->sc_flags & UMS_FLAG_X_AXIS) + sbuf_printf(sb, " X:r%d, p%d, s%d;", + (int)info->sc_iid_x, + (int)info->sc_loc_x.pos, + (int)info->sc_loc_x.size); + if (info->sc_flags & UMS_FLAG_Y_AXIS) + sbuf_printf(sb, " Y:r%d, p%d, s%d;", + (int)info->sc_iid_y, + (int)info->sc_loc_y.pos, + (int)info->sc_loc_y.size); + if (info->sc_flags & UMS_FLAG_Z_AXIS) + sbuf_printf(sb, " Z:r%d, p%d, s%d;", + (int)info->sc_iid_z, + (int)info->sc_loc_z.pos, + (int)info->sc_loc_z.size); + if (info->sc_flags & UMS_FLAG_T_AXIS) + sbuf_printf(sb, " T:r%d, p%d, s%d;", + (int)info->sc_iid_t, + (int)info->sc_loc_t.pos, + (int)info->sc_loc_t.size); + if (info->sc_flags & UMS_FLAG_W_AXIS) + sbuf_printf(sb, " W:r%d, p%d, s%d;", + (int)info->sc_iid_w, + (int)info->sc_loc_w.pos, + (int)info->sc_loc_w.size); + + for (j = 0; j < info->sc_buttons; j++) { + sbuf_printf(sb, " B%d:r%d, p%d, s%d;", j + 1, + (int)info->sc_iid_btn[j], + (int)info->sc_loc_btn[j].pos, + (int)info->sc_loc_btn[j].size); + } + } + sbuf_finish(sb); + err = SYSCTL_OUT(req, sbuf_data(sb), sbuf_len(sb) + 1); + sbuf_delete(sb); + + return (err); +} + +static devclass_t ums_devclass; + +static device_method_t ums_methods[] = { + DEVMETHOD(device_probe, ums_probe), + DEVMETHOD(device_attach, ums_attach), + DEVMETHOD(device_detach, ums_detach), + {0, 0} +}; + +static driver_t ums_driver = { + .name = "ums", + .methods = ums_methods, + .size = sizeof(struct ums_softc), +}; + +DRIVER_MODULE(ums, uhub, ums_driver, ums_devclass, NULL, 0); +MODULE_DEPEND(ums, usb, 1, 1, 1); +MODULE_VERSION(ums, 1); diff --git a/sys/bus/u4b/input/usb_rdesc.h b/sys/bus/u4b/input/usb_rdesc.h new file mode 100644 index 0000000000..23ea620b73 --- /dev/null +++ b/sys/bus/u4b/input/usb_rdesc.h @@ -0,0 +1,276 @@ +/*- + * Copyright (c) 2000 Nick Hibma + * All rights reserved. + * + * Copyright (c) 2005 Ed Schouten + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + * This file contains replacements for broken HID report descriptors. + */ + +#define UHID_GRAPHIRE_REPORT_DESCR(...) \ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x01, /* USAGE (Digitizer) */\ + 0xa1, 0x01, /* COLLECTION (Application) */\ + 0x85, 0x02, /* REPORT_ID (2) */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x01, /* USAGE (Digitizer) */\ + 0xa1, 0x00, /* COLLECTION (Physical) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */\ + 0x09, 0x33, /* USAGE (Touch) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x44, /* USAGE (Barrel Switch) */\ + 0x95, 0x02, /* REPORT_COUNT (2) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x95, 0x02, /* REPORT_COUNT (2) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x03, /* INPUT (Cnst,Var,Abs) */\ + 0x09, 0x3c, /* USAGE (Invert) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x38, /* USAGE (Transducer Index) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x32, /* USAGE (In Range) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ + 0x09, 0x30, /* USAGE (X) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x26, 0xde, 0x27, /* LOGICAL_MAXIMUM (10206) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x31, /* USAGE (Y) */\ + 0x26, 0xfe, 0x1c, /* LOGICAL_MAXIMUM (7422) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x30, /* USAGE (Tip Pressure) */\ + 0x26, 0xff, 0x01, /* LOGICAL_MAXIMUM (511) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0xc0, /* END_COLLECTION */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x85, 0x02, /* REPORT_ID (2) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x85, 0x03, /* REPORT_ID (3) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ + 0xc0, /* END_COLLECTION */\ + +#define UHID_GRAPHIRE3_4X5_REPORT_DESCR(...) \ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ + 0x09, 0x02, /* USAGE (Mouse) */\ + 0xa1, 0x01, /* COLLECTION (Application) */\ + 0x85, 0x01, /* REPORT_ID (1) */\ + 0x09, 0x01, /* USAGE (Pointer) */\ + 0xa1, 0x00, /* COLLECTION (Physical) */\ + 0x05, 0x09, /* USAGE_PAGE (Button) */\ + 0x19, 0x01, /* USAGE_MINIMUM (Button 1) */\ + 0x29, 0x03, /* USAGE_MAXIMUM (Button 3) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */\ + 0x95, 0x03, /* REPORT_COUNT (3) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x05, /* REPORT_SIZE (5) */\ + 0x81, 0x01, /* INPUT (Cnst,Ary,Abs) */\ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ + 0x09, 0x30, /* USAGE (X) */\ + 0x09, 0x31, /* USAGE (Y) */\ + 0x09, 0x38, /* USAGE (Wheel) */\ + 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */\ + 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */\ + 0x75, 0x08, /* REPORT_SIZE (8) */\ + 0x95, 0x03, /* REPORT_COUNT (3) */\ + 0x81, 0x06, /* INPUT (Data,Var,Rel) */\ + 0xc0, /* END_COLLECTION */\ + 0xc0, /* END_COLLECTION */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x01, /* USAGE (Pointer) */\ + 0xa1, 0x01, /* COLLECTION (Applicaption) */\ + 0x85, 0x02, /* REPORT_ID (2) */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x01, /* USAGE (Digitizer) */\ + 0xa1, 0x00, /* COLLECTION (Physical) */\ + 0x09, 0x33, /* USAGE (Touch) */\ + 0x09, 0x44, /* USAGE (Barrel Switch) */\ + 0x09, 0x44, /* USAGE (Barrel Switch) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x95, 0x03, /* REPORT_COUNT (3) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x95, 0x02, /* REPORT_COUNT (2) */\ + 0x81, 0x01, /* INPUT (Cnst,Ary,Abs) */\ + 0x09, 0x3c, /* USAGE (Invert) */\ + 0x09, 0x38, /* USAGE (Transducer Index) */\ + 0x09, 0x32, /* USAGE (In Range) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x95, 0x03, /* REPORT_COUNT (3) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ + 0x09, 0x30, /* USAGE (X) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x26, 0xde, 0x27, /* LOGICAL_MAXIMUM (10206) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x31, /* USAGE (Y) */\ + 0x26, 0xfe, 0x1c, /* LOGICAL_MAXIMUM (7422) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x30, /* USAGE (Tip Pressure) */\ + 0x26, 0xff, 0x01, /* LOGICAL_MAXIMUM (511) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0xc0, /* END_COLLECTION */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x85, 0x02, /* REPORT_ID (2) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x85, 0x03, /* REPORT_ID (3) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ + 0xc0 /* END_COLLECTION */\ + +/* + * The descriptor has no output report format, thus preventing you from + * controlling the LEDs and the built-in rumblers. + */ +#define UHID_XB360GP_REPORT_DESCR(...) \ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x05, /* USAGE (Gamepad) */\ + 0xa1, 0x01, /* COLLECTION (Application) */\ + /* Unused */\ + 0x75, 0x08, /* REPORT SIZE (8) */\ + 0x95, 0x01, /* REPORT COUNT (1) */\ + 0x81, 0x01, /* INPUT (Constant) */\ + /* Byte count */\ + 0x75, 0x08, /* REPORT SIZE (8) */\ + 0x95, 0x01, /* REPORT COUNT (1) */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x3b, /* USAGE (Byte Count) */\ + 0x81, 0x01, /* INPUT (Constant) */\ + /* D-Pad */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x01, /* USAGE (Pointer) */\ + 0xa1, 0x00, /* COLLECTION (Physical) */\ + 0x75, 0x01, /* REPORT SIZE (1) */\ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL MAXIMUM (1) */\ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ + 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */\ + 0x95, 0x04, /* REPORT COUNT (4) */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x90, /* USAGE (D-Pad Up) */\ + 0x09, 0x91, /* USAGE (D-Pad Down) */\ + 0x09, 0x93, /* USAGE (D-Pad Left) */\ + 0x09, 0x92, /* USAGE (D-Pad Right) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + 0xc0, /* END COLLECTION */\ + /* Buttons 5-11 */\ + 0x75, 0x01, /* REPORT SIZE (1) */\ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL MAXIMUM (1) */\ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ + 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */\ + 0x95, 0x07, /* REPORT COUNT (7) */\ + 0x05, 0x09, /* USAGE PAGE (Button) */\ + 0x09, 0x08, /* USAGE (Button 8) */\ + 0x09, 0x07, /* USAGE (Button 7) */\ + 0x09, 0x09, /* USAGE (Button 9) */\ + 0x09, 0x0a, /* USAGE (Button 10) */\ + 0x09, 0x05, /* USAGE (Button 5) */\ + 0x09, 0x06, /* USAGE (Button 6) */\ + 0x09, 0x0b, /* USAGE (Button 11) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + /* Unused */\ + 0x75, 0x01, /* REPORT SIZE (1) */\ + 0x95, 0x01, /* REPORT COUNT (1) */\ + 0x81, 0x01, /* INPUT (Constant) */\ + /* Buttons 1-4 */\ + 0x75, 0x01, /* REPORT SIZE (1) */\ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL MAXIMUM (1) */\ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ + 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */\ + 0x95, 0x04, /* REPORT COUNT (4) */\ + 0x05, 0x09, /* USAGE PAGE (Button) */\ + 0x19, 0x01, /* USAGE MINIMUM (Button 1) */\ + 0x29, 0x04, /* USAGE MAXIMUM (Button 4) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + /* Triggers */\ + 0x75, 0x08, /* REPORT SIZE (8) */\ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ + 0x26, 0xff, 0x00, /* LOGICAL MAXIMUM (255) */\ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ + 0x46, 0xff, 0x00, /* PHYSICAL MAXIMUM (255) */\ + 0x95, 0x02, /* REPORT SIZE (2) */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x32, /* USAGE (Z) */\ + 0x09, 0x35, /* USAGE (Rz) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + /* Sticks */\ + 0x75, 0x10, /* REPORT SIZE (16) */\ + 0x16, 0x00, 0x80, /* LOGICAL MINIMUM (-32768) */\ + 0x26, 0xff, 0x7f, /* LOGICAL MAXIMUM (32767) */\ + 0x36, 0x00, 0x80, /* PHYSICAL MINIMUM (-32768) */\ + 0x46, 0xff, 0x7f, /* PHYSICAL MAXIMUM (32767) */\ + 0x95, 0x04, /* REPORT COUNT (4) */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x30, /* USAGE (X) */\ + 0x09, 0x31, /* USAGE (Y) */\ + 0x09, 0x33, /* USAGE (Rx) */\ + 0x09, 0x34, /* USAGE (Ry) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + /* Unused */\ + 0x75, 0x30, /* REPORT SIZE (48) */\ + 0x95, 0x01, /* REPORT COUNT (1) */\ + 0x81, 0x01, /* INPUT (Constant) */\ + 0xc0 /* END COLLECTION */\ + diff --git a/sys/bus/u4b/misc/udbp.c b/sys/bus/u4b/misc/udbp.c new file mode 100644 index 0000000000..150985e8e7 --- /dev/null +++ b/sys/bus/u4b/misc/udbp.c @@ -0,0 +1,857 @@ +/*- + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of author nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY NICK HIBMA AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +/* Driver for arbitrary double bulk pipe devices. + * The driver assumes that there will be the same driver on the other side. + * + * XXX Some more information on what the framing of the IP packets looks like. + * + * To take full advantage of bulk transmission, packets should be chosen + * between 1k and 5k in size (1k to make sure the sending side starts + * streaming, and <5k to avoid overflowing the system with small TDs). + */ + + +/* probe/attach/detach: + * Connect the driver to the hardware and netgraph + * + * The reason we submit a bulk in transfer is that USB does not know about + * interrupts. The bulk transfer continuously polls the device for data. + * While the device has no data available, the device NAKs the TDs. As soon + * as there is data, the transfer happens and the data comes flowing in. + * + * In case you were wondering, interrupt transfers happen exactly that way. + * It therefore doesn't make sense to use the interrupt pipe to signal + * 'data ready' and then schedule a bulk transfer to fetch it. That would + * incur a 2ms delay at least, without reducing bandwidth requirements. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR udbp_debug +#include + +#include + +#include +#include +#include +#include + +#include + +#ifdef USB_DEBUG +static int udbp_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, udbp, CTLFLAG_RW, 0, "USB udbp"); +SYSCTL_INT(_hw_usb_udbp, OID_AUTO, debug, CTLFLAG_RW, + &udbp_debug, 0, "udbp debug level"); +#endif + +#define UDBP_TIMEOUT 2000 /* timeout on outbound transfers, in + * msecs */ +#define UDBP_BUFFERSIZE MCLBYTES /* maximum number of bytes in one + * transfer */ +#define UDBP_T_WR 0 +#define UDBP_T_RD 1 +#define UDBP_T_WR_CS 2 +#define UDBP_T_RD_CS 3 +#define UDBP_T_MAX 4 +#define UDBP_Q_MAXLEN 50 + +struct udbp_softc { + + struct mtx sc_mtx; + struct ng_bt_mbufq sc_xmitq_hipri; /* hi-priority transmit queue */ + struct ng_bt_mbufq sc_xmitq; /* low-priority transmit queue */ + + struct usb_xfer *sc_xfer[UDBP_T_MAX]; + node_p sc_node; /* back pointer to node */ + hook_p sc_hook; /* pointer to the hook */ + struct mbuf *sc_bulk_in_buffer; + + uint32_t sc_packets_in; /* packets in from downstream */ + uint32_t sc_packets_out; /* packets out towards downstream */ + + uint8_t sc_flags; +#define UDBP_FLAG_READ_STALL 0x01 /* read transfer stalled */ +#define UDBP_FLAG_WRITE_STALL 0x02 /* write transfer stalled */ + + uint8_t sc_name[16]; +}; + +/* prototypes */ + +static int udbp_modload(module_t mod, int event, void *data); + +static device_probe_t udbp_probe; +static device_attach_t udbp_attach; +static device_detach_t udbp_detach; + +static usb_callback_t udbp_bulk_read_callback; +static usb_callback_t udbp_bulk_read_clear_stall_callback; +static usb_callback_t udbp_bulk_write_callback; +static usb_callback_t udbp_bulk_write_clear_stall_callback; + +static void udbp_bulk_read_complete(node_p, hook_p, void *, int); + +static ng_constructor_t ng_udbp_constructor; +static ng_rcvmsg_t ng_udbp_rcvmsg; +static ng_shutdown_t ng_udbp_rmnode; +static ng_newhook_t ng_udbp_newhook; +static ng_connect_t ng_udbp_connect; +static ng_rcvdata_t ng_udbp_rcvdata; +static ng_disconnect_t ng_udbp_disconnect; + +/* Parse type for struct ngudbpstat */ +static const struct ng_parse_struct_field + ng_udbp_stat_type_fields[] = NG_UDBP_STATS_TYPE_INFO; + +static const struct ng_parse_type ng_udbp_stat_type = { + &ng_parse_struct_type, + &ng_udbp_stat_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_udbp_cmdlist[] = { + { + NGM_UDBP_COOKIE, + NGM_UDBP_GET_STATUS, + "getstatus", + NULL, + &ng_udbp_stat_type, + }, + { + NGM_UDBP_COOKIE, + NGM_UDBP_SET_FLAG, + "setflag", + &ng_parse_int32_type, + NULL + }, + {0} +}; + +/* Netgraph node type descriptor */ +static struct ng_type ng_udbp_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_UDBP_NODE_TYPE, + .constructor = ng_udbp_constructor, + .rcvmsg = ng_udbp_rcvmsg, + .shutdown = ng_udbp_rmnode, + .newhook = ng_udbp_newhook, + .connect = ng_udbp_connect, + .rcvdata = ng_udbp_rcvdata, + .disconnect = ng_udbp_disconnect, + .cmdlist = ng_udbp_cmdlist, +}; + +/* USB config */ +static const struct usb_config udbp_config[UDBP_T_MAX] = { + + [UDBP_T_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UDBP_BUFFERSIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &udbp_bulk_write_callback, + .timeout = UDBP_TIMEOUT, + }, + + [UDBP_T_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UDBP_BUFFERSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &udbp_bulk_read_callback, + }, + + [UDBP_T_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &udbp_bulk_write_clear_stall_callback, + .timeout = 1000, /* 1 second */ + .interval = 50, /* 50ms */ + }, + + [UDBP_T_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &udbp_bulk_read_clear_stall_callback, + .timeout = 1000, /* 1 second */ + .interval = 50, /* 50ms */ + }, +}; + +static devclass_t udbp_devclass; + +static device_method_t udbp_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, udbp_probe), + DEVMETHOD(device_attach, udbp_attach), + DEVMETHOD(device_detach, udbp_detach), + {0, 0} +}; + +static driver_t udbp_driver = { + .name = "udbp", + .methods = udbp_methods, + .size = sizeof(struct udbp_softc), +}; + +DRIVER_MODULE(udbp, uhub, udbp_driver, udbp_devclass, udbp_modload, 0); +MODULE_DEPEND(udbp, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION); +MODULE_DEPEND(udbp, usb, 1, 1, 1); +MODULE_VERSION(udbp, 1); + +static int +udbp_modload(module_t mod, int event, void *data) +{ + int error; + + switch (event) { + case MOD_LOAD: + error = ng_newtype(&ng_udbp_typestruct); + if (error != 0) { + printf("%s: Could not register " + "Netgraph node type, error=%d\n", + NG_UDBP_NODE_TYPE, error); + } + break; + + case MOD_UNLOAD: + error = ng_rmtype(&ng_udbp_typestruct); + break; + + default: + error = EOPNOTSUPP; + break; + } + return (error); +} + +static const STRUCT_USB_HOST_ID udbp_devs[] = { + {USB_VPI(USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_TURBOCONNECT, 0)}, + {USB_VPI(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2301, 0)}, + {USB_VPI(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2302, 0)}, + {USB_VPI(USB_VENDOR_ANCHOR, USB_PRODUCT_ANCHOR_EZLINK, 0)}, + {USB_VPI(USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL620USB, 0)}, +}; + +static int +udbp_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != 0) + return (ENXIO); + if (uaa->info.bIfaceIndex != 0) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(udbp_devs, sizeof(udbp_devs), uaa)); +} + +static int +udbp_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct udbp_softc *sc = device_get_softc(dev); + int error; + + device_set_usb_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + mtx_init(&sc->sc_mtx, "udbp lock", NULL, MTX_DEF | MTX_RECURSE); + + error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, + sc->sc_xfer, udbp_config, UDBP_T_MAX, sc, &sc->sc_mtx); + if (error) { + DPRINTF("error=%s\n", usbd_errstr(error)); + goto detach; + } + NG_BT_MBUFQ_INIT(&sc->sc_xmitq, UDBP_Q_MAXLEN); + + NG_BT_MBUFQ_INIT(&sc->sc_xmitq_hipri, UDBP_Q_MAXLEN); + + /* create Netgraph node */ + + if (ng_make_node_common(&ng_udbp_typestruct, &sc->sc_node) != 0) { + printf("%s: Could not create Netgraph node\n", + sc->sc_name); + sc->sc_node = NULL; + goto detach; + } + /* name node */ + + if (ng_name_node(sc->sc_node, sc->sc_name) != 0) { + printf("%s: Could not name node\n", + sc->sc_name); + NG_NODE_UNREF(sc->sc_node); + sc->sc_node = NULL; + goto detach; + } + NG_NODE_SET_PRIVATE(sc->sc_node, sc); + + /* the device is now operational */ + + return (0); /* success */ + +detach: + udbp_detach(dev); + return (ENOMEM); /* failure */ +} + +static int +udbp_detach(device_t dev) +{ + struct udbp_softc *sc = device_get_softc(dev); + + /* destroy Netgraph node */ + + if (sc->sc_node != NULL) { + NG_NODE_SET_PRIVATE(sc->sc_node, NULL); + ng_rmnode_self(sc->sc_node); + sc->sc_node = NULL; + } + /* free USB transfers, if any */ + + usbd_transfer_unsetup(sc->sc_xfer, UDBP_T_MAX); + + mtx_destroy(&sc->sc_mtx); + + /* destroy queues */ + + NG_BT_MBUFQ_DESTROY(&sc->sc_xmitq); + NG_BT_MBUFQ_DESTROY(&sc->sc_xmitq_hipri); + + /* extra check */ + + if (sc->sc_bulk_in_buffer) { + m_freem(sc->sc_bulk_in_buffer); + sc->sc_bulk_in_buffer = NULL; + } + return (0); /* success */ +} + +static void +udbp_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct udbp_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + struct mbuf *m; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + /* allocate new mbuf */ + + MGETHDR(m, M_DONTWAIT, MT_DATA); + + if (m == NULL) { + goto tr_setup; + } + MCLGET(m, M_DONTWAIT); + + if (!(m->m_flags & M_EXT)) { + m_freem(m); + goto tr_setup; + } + m->m_pkthdr.len = m->m_len = actlen; + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, m->m_data, actlen); + + sc->sc_bulk_in_buffer = m; + + DPRINTF("received package %d bytes\n", actlen); + + case USB_ST_SETUP: +tr_setup: + if (sc->sc_bulk_in_buffer) { + ng_send_fn(sc->sc_node, NULL, &udbp_bulk_read_complete, NULL, 0); + return; + } + if (sc->sc_flags & UDBP_FLAG_READ_STALL) { + usbd_transfer_start(sc->sc_xfer[UDBP_T_RD_CS]); + return; + } + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= UDBP_FLAG_READ_STALL; + usbd_transfer_start(sc->sc_xfer[UDBP_T_RD_CS]); + } + return; + + } +} + +static void +udbp_bulk_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct udbp_softc *sc = usbd_xfer_softc(xfer); + struct usb_xfer *xfer_other = sc->sc_xfer[UDBP_T_RD]; + + if (usbd_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UDBP_FLAG_READ_STALL; + usbd_transfer_start(xfer_other); + } +} + +static void +udbp_bulk_read_complete(node_p node, hook_p hook, void *arg1, int arg2) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(node); + struct mbuf *m; + int error; + + if (sc == NULL) { + return; + } + mtx_lock(&sc->sc_mtx); + + m = sc->sc_bulk_in_buffer; + + if (m) { + + sc->sc_bulk_in_buffer = NULL; + + if ((sc->sc_hook == NULL) || + NG_HOOK_NOT_VALID(sc->sc_hook)) { + DPRINTF("No upstream hook\n"); + goto done; + } + sc->sc_packets_in++; + + NG_SEND_DATA_ONLY(error, sc->sc_hook, m); + + m = NULL; + } +done: + if (m) { + m_freem(m); + } + /* start USB bulk-in transfer, if not already started */ + + usbd_transfer_start(sc->sc_xfer[UDBP_T_RD]); + + mtx_unlock(&sc->sc_mtx); +} + +static void +udbp_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct udbp_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + struct mbuf *m; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + sc->sc_packets_out++; + + case USB_ST_SETUP: + if (sc->sc_flags & UDBP_FLAG_WRITE_STALL) { + usbd_transfer_start(sc->sc_xfer[UDBP_T_WR_CS]); + return; + } + /* get next mbuf, if any */ + + NG_BT_MBUFQ_DEQUEUE(&sc->sc_xmitq_hipri, m); + if (m == NULL) { + NG_BT_MBUFQ_DEQUEUE(&sc->sc_xmitq, m); + if (m == NULL) { + DPRINTF("Data queue is empty\n"); + return; + } + } + if (m->m_pkthdr.len > MCLBYTES) { + DPRINTF("truncating large packet " + "from %d to %d bytes\n", m->m_pkthdr.len, + MCLBYTES); + m->m_pkthdr.len = MCLBYTES; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); + + usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len); + + DPRINTF("packet out: %d bytes\n", m->m_pkthdr.len); + + m_freem(m); + + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= UDBP_FLAG_WRITE_STALL; + usbd_transfer_start(sc->sc_xfer[UDBP_T_WR_CS]); + } + return; + + } +} + +static void +udbp_bulk_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct udbp_softc *sc = usbd_xfer_softc(xfer); + struct usb_xfer *xfer_other = sc->sc_xfer[UDBP_T_WR]; + + if (usbd_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~UDBP_FLAG_WRITE_STALL; + usbd_transfer_start(xfer_other); + } +} + +/*********************************************************************** + * Start of Netgraph methods + **********************************************************************/ + +/* + * If this is a device node so this work is done in the attach() + * routine and the constructor will return EINVAL as you should not be able + * to create nodes that depend on hardware (unless you can add the hardware :) + */ +static int +ng_udbp_constructor(node_p node) +{ + return (EINVAL); +} + +/* + * Give our ok for a hook to be added... + * If we are not running this might kick a device into life. + * Possibly decode information out of the hook name. + * Add the hook's private info to the hook structure. + * (if we had some). In this example, we assume that there is a + * an array of structs, called 'channel' in the private info, + * one for each active channel. The private + * pointer of each hook points to the appropriate UDBP_hookinfo struct + * so that the source of an input packet is easily identified. + */ +static int +ng_udbp_newhook(node_p node, hook_p hook, const char *name) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(node); + int32_t error = 0; + + if (strcmp(name, NG_UDBP_HOOK_NAME)) { + return (EINVAL); + } + mtx_lock(&sc->sc_mtx); + + if (sc->sc_hook != NULL) { + error = EISCONN; + } else { + sc->sc_hook = hook; + NG_HOOK_SET_PRIVATE(hook, NULL); + } + + mtx_unlock(&sc->sc_mtx); + + return (error); +} + +/* + * Get a netgraph control message. + * Check it is one we understand. If needed, send a response. + * We could save the address for an async action later, but don't here. + * Always free the message. + * The response should be in a malloc'd region that the caller can 'free'. + * A response is not required. + * Theoretically you could respond defferently to old message types if + * the cookie in the header didn't match what we consider to be current + * (so that old userland programs could continue to work). + */ +static int +ng_udbp_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + /* Deal with message according to cookie and command */ + switch (msg->header.typecookie) { + case NGM_UDBP_COOKIE: + switch (msg->header.cmd) { + case NGM_UDBP_GET_STATUS: + { + struct ngudbpstat *stats; + + NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); + if (!resp) { + error = ENOMEM; + break; + } + stats = (struct ngudbpstat *)resp->data; + mtx_lock(&sc->sc_mtx); + stats->packets_in = sc->sc_packets_in; + stats->packets_out = sc->sc_packets_out; + mtx_unlock(&sc->sc_mtx); + break; + } + case NGM_UDBP_SET_FLAG: + if (msg->header.arglen != sizeof(uint32_t)) { + error = EINVAL; + break; + } + DPRINTF("flags = 0x%08x\n", + *((uint32_t *)msg->data)); + break; + default: + error = EINVAL; /* unknown command */ + break; + } + break; + default: + error = EINVAL; /* unknown cookie type */ + break; + } + + /* Take care of synchronous response, if any */ + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +/* + * Accept data from the hook and queue it for output. + */ +static int +ng_udbp_rcvdata(hook_p hook, item_p item) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + struct ng_bt_mbufq *queue_ptr; + struct mbuf *m; + struct ng_tag_prio *ptag; + int error; + + if (sc == NULL) { + NG_FREE_ITEM(item); + return (EHOSTDOWN); + } + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + /* + * Now queue the data for when it can be sent + */ + ptag = (void *)m_tag_locate(m, NGM_GENERIC_COOKIE, + NG_TAG_PRIO, NULL); + + if (ptag && (ptag->priority > NG_PRIO_CUTOFF)) + queue_ptr = &sc->sc_xmitq_hipri; + else + queue_ptr = &sc->sc_xmitq; + + mtx_lock(&sc->sc_mtx); + + if (NG_BT_MBUFQ_FULL(queue_ptr)) { + NG_BT_MBUFQ_DROP(queue_ptr); + NG_FREE_M(m); + error = ENOBUFS; + } else { + NG_BT_MBUFQ_ENQUEUE(queue_ptr, m); + /* + * start bulk-out transfer, if not already started: + */ + usbd_transfer_start(sc->sc_xfer[UDBP_T_WR]); + error = 0; + } + + mtx_unlock(&sc->sc_mtx); + + return (error); +} + +/* + * Do local shutdown processing.. + * We are a persistant device, we refuse to go away, and + * only remove our links and reset ourself. + */ +static int +ng_udbp_rmnode(node_p node) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(node); + + /* Let old node go */ + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); /* forget it ever existed */ + + if (sc == NULL) { + goto done; + } + /* Create Netgraph node */ + if (ng_make_node_common(&ng_udbp_typestruct, &sc->sc_node) != 0) { + printf("%s: Could not create Netgraph node\n", + sc->sc_name); + sc->sc_node = NULL; + goto done; + } + /* Name node */ + if (ng_name_node(sc->sc_node, sc->sc_name) != 0) { + printf("%s: Could not name Netgraph node\n", + sc->sc_name); + NG_NODE_UNREF(sc->sc_node); + sc->sc_node = NULL; + goto done; + } + NG_NODE_SET_PRIVATE(sc->sc_node, sc); + +done: + if (sc) { + mtx_unlock(&sc->sc_mtx); + } + return (0); +} + +/* + * This is called once we've already connected a new hook to the other node. + * It gives us a chance to balk at the last minute. + */ +static int +ng_udbp_connect(hook_p hook) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + + /* probably not at splnet, force outward queueing */ + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); + + mtx_lock(&sc->sc_mtx); + + sc->sc_flags |= (UDBP_FLAG_READ_STALL | + UDBP_FLAG_WRITE_STALL); + + /* start bulk-in transfer */ + usbd_transfer_start(sc->sc_xfer[UDBP_T_RD]); + + /* start bulk-out transfer */ + usbd_transfer_start(sc->sc_xfer[UDBP_T_WR]); + + mtx_unlock(&sc->sc_mtx); + + return (0); +} + +/* + * Dook disconnection + * + * For this type, removal of the last link destroys the node + */ +static int +ng_udbp_disconnect(hook_p hook) +{ + struct udbp_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + int error = 0; + + if (sc != NULL) { + + mtx_lock(&sc->sc_mtx); + + if (hook != sc->sc_hook) { + error = EINVAL; + } else { + + /* stop bulk-in transfer */ + usbd_transfer_stop(sc->sc_xfer[UDBP_T_RD_CS]); + usbd_transfer_stop(sc->sc_xfer[UDBP_T_RD]); + + /* stop bulk-out transfer */ + usbd_transfer_stop(sc->sc_xfer[UDBP_T_WR_CS]); + usbd_transfer_stop(sc->sc_xfer[UDBP_T_WR]); + + /* cleanup queues */ + NG_BT_MBUFQ_DRAIN(&sc->sc_xmitq); + NG_BT_MBUFQ_DRAIN(&sc->sc_xmitq_hipri); + + if (sc->sc_bulk_in_buffer) { + m_freem(sc->sc_bulk_in_buffer); + sc->sc_bulk_in_buffer = NULL; + } + sc->sc_hook = NULL; + } + + mtx_unlock(&sc->sc_mtx); + } + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + + return (error); +} diff --git a/sys/bus/u4b/misc/udbp.h b/sys/bus/u4b/misc/udbp.h new file mode 100644 index 0000000000..e6fd853261 --- /dev/null +++ b/sys/bus/u4b/misc/udbp.h @@ -0,0 +1,80 @@ +/*- + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file was derived from src/sys/netgraph/ng_sample.h, revision 1.1 + * written by Julian Elischer, Whistle Communications. + * + * $FreeBSD$ + */ + +#ifndef _NETGRAPH_UDBP_H_ +#define _NETGRAPH_UDBP_H_ + +/* Node type name. This should be unique among all netgraph node types */ +#define NG_UDBP_NODE_TYPE "udbp" + +/* Node type cookie. Should also be unique. This value MUST change whenever + an incompatible change is made to this header file, to insure consistency. + The de facto method for generating cookies is to take the output of the + date command: date -u +'%s' */ +#define NGM_UDBP_COOKIE 944609300 + + +#define NG_UDBP_HOOK_NAME "data" + +/* Netgraph commands understood by this node type */ +enum { + NGM_UDBP_SET_FLAG = 1, + NGM_UDBP_GET_STATUS, +}; + +/* This structure is returned by the NGM_UDBP_GET_STATUS command */ +struct ngudbpstat { + uint32_t packets_in; /* packets in from downstream */ + uint32_t packets_out; /* packets out towards downstream */ +}; + +/* + * This is used to define the 'parse type' for a struct ngudbpstat, which + * is bascially a description of how to convert a binary struct ngudbpstat + * to an ASCII string and back. See ng_parse.h for more info. + * + * This needs to be kept in sync with the above structure definition + */ +#define NG_UDBP_STATS_TYPE_INFO { \ + { "packets_in", &ng_parse_int32_type }, \ + { "packets_out", &ng_parse_int32_type }, \ + { NULL }, \ +} + +#endif /* _NETGRAPH_UDBP_H_ */ diff --git a/sys/bus/u4b/misc/ufm.c b/sys/bus/u4b/misc/ufm.c new file mode 100644 index 0000000000..11bea65ae5 --- /dev/null +++ b/sys/bus/u4b/misc/ufm.c @@ -0,0 +1,336 @@ +/*- + * Copyright (c) 2001 M. Warner Losh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This code is based on ugen.c and ulpt.c developed by Lennart Augustsson. + * This code includes software developed by the NetBSD Foundation, Inc. and + * its contributors. + */ + +#include +__FBSDID("$FreeBSD$"); + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR usb_debug +#include + +#include + +#define UFM_CMD0 0x00 +#define UFM_CMD_SET_FREQ 0x01 +#define UFM_CMD2 0x02 + +struct ufm_softc { + struct usb_fifo_sc sc_fifo; + struct mtx sc_mtx; + + struct usb_device *sc_udev; + + uint32_t sc_unit; + uint32_t sc_freq; + + uint8_t sc_name[16]; +}; + +/* prototypes */ + +static device_probe_t ufm_probe; +static device_attach_t ufm_attach; +static device_detach_t ufm_detach; + +static usb_fifo_ioctl_t ufm_ioctl; + +static struct usb_fifo_methods ufm_fifo_methods = { + .f_ioctl = &ufm_ioctl, + .basename[0] = "ufm", +}; + +static int ufm_do_req(struct ufm_softc *, uint8_t, uint16_t, uint16_t, + uint8_t *); +static int ufm_set_freq(struct ufm_softc *, void *); +static int ufm_get_freq(struct ufm_softc *, void *); +static int ufm_start(struct ufm_softc *, void *); +static int ufm_stop(struct ufm_softc *, void *); +static int ufm_get_stat(struct ufm_softc *, void *); + +static devclass_t ufm_devclass; + +static device_method_t ufm_methods[] = { + DEVMETHOD(device_probe, ufm_probe), + DEVMETHOD(device_attach, ufm_attach), + DEVMETHOD(device_detach, ufm_detach), + {0, 0} +}; + +static driver_t ufm_driver = { + .name = "ufm", + .methods = ufm_methods, + .size = sizeof(struct ufm_softc), +}; + +DRIVER_MODULE(ufm, uhub, ufm_driver, ufm_devclass, NULL, 0); +MODULE_DEPEND(ufm, usb, 1, 1, 1); +MODULE_VERSION(ufm, 1); + +static const STRUCT_USB_HOST_ID ufm_devs[] = { + {USB_VPI(USB_VENDOR_CYPRESS, USB_PRODUCT_CYPRESS_FMRADIO, 0)}, +}; + +static int +ufm_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != 0) + return (ENXIO); + if (uaa->info.bIfaceIndex != 0) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(ufm_devs, sizeof(ufm_devs), uaa)); +} + +static int +ufm_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ufm_softc *sc = device_get_softc(dev); + int error; + + sc->sc_udev = uaa->device; + sc->sc_unit = device_get_unit(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + mtx_init(&sc->sc_mtx, "ufm lock", NULL, MTX_DEF | MTX_RECURSE); + + device_set_usb_desc(dev); + + error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &ufm_fifo_methods, &sc->sc_fifo, + device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + if (error) { + goto detach; + } + return (0); /* success */ + +detach: + ufm_detach(dev); + return (ENXIO); +} + +static int +ufm_detach(device_t dev) +{ + struct ufm_softc *sc = device_get_softc(dev); + + usb_fifo_detach(&sc->sc_fifo); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static int +ufm_do_req(struct ufm_softc *sc, uint8_t request, + uint16_t value, uint16_t index, uint8_t *retbuf) +{ + int error; + + struct usb_device_request req; + uint8_t buf[1]; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = request; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, 1); + + error = usbd_do_request(sc->sc_udev, NULL, &req, buf); + + if (retbuf) { + *retbuf = buf[0]; + } + if (error) { + return (ENXIO); + } + return (0); +} + +static int +ufm_set_freq(struct ufm_softc *sc, void *addr) +{ + int freq = *(int *)addr; + + /* + * Freq now is in Hz. We need to convert it to the frequency + * that the radio wants. This frequency is 10.7MHz above + * the actual frequency. We then need to convert to + * units of 12.5kHz. We add one to the IFM to make rounding + * easier. + */ + mtx_lock(&sc->sc_mtx); + sc->sc_freq = freq; + mtx_unlock(&sc->sc_mtx); + + freq = (freq + 10700001) / 12500; + + /* This appears to set the frequency */ + if (ufm_do_req(sc, UFM_CMD_SET_FREQ, + freq >> 8, freq, NULL) != 0) { + return (EIO); + } + /* Not sure what this does */ + if (ufm_do_req(sc, UFM_CMD0, + 0x96, 0xb7, NULL) != 0) { + return (EIO); + } + return (0); +} + +static int +ufm_get_freq(struct ufm_softc *sc, void *addr) +{ + int *valp = (int *)addr; + + mtx_lock(&sc->sc_mtx); + *valp = sc->sc_freq; + mtx_unlock(&sc->sc_mtx); + return (0); +} + +static int +ufm_start(struct ufm_softc *sc, void *addr) +{ + uint8_t ret; + + if (ufm_do_req(sc, UFM_CMD0, + 0x00, 0xc7, &ret)) { + return (EIO); + } + if (ufm_do_req(sc, UFM_CMD2, + 0x01, 0x00, &ret)) { + return (EIO); + } + if (ret & 0x1) { + return (EIO); + } + return (0); +} + +static int +ufm_stop(struct ufm_softc *sc, void *addr) +{ + if (ufm_do_req(sc, UFM_CMD0, + 0x16, 0x1C, NULL)) { + return (EIO); + } + if (ufm_do_req(sc, UFM_CMD2, + 0x00, 0x00, NULL)) { + return (EIO); + } + return (0); +} + +static int +ufm_get_stat(struct ufm_softc *sc, void *addr) +{ + uint8_t ret; + + /* + * Note, there's a 240ms settle time before the status + * will be valid, so sleep that amount. + */ + usb_pause_mtx(NULL, hz / 4); + + if (ufm_do_req(sc, UFM_CMD0, + 0x00, 0x24, &ret)) { + return (EIO); + } + *(int *)addr = ret; + + return (0); +} + +static int +ufm_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, + int fflags) +{ + struct ufm_softc *sc = usb_fifo_softc(fifo); + int error = 0; + + if ((fflags & (FWRITE | FREAD)) != (FWRITE | FREAD)) { + return (EACCES); + } + + switch (cmd) { + case FM_SET_FREQ: + error = ufm_set_freq(sc, addr); + break; + case FM_GET_FREQ: + error = ufm_get_freq(sc, addr); + break; + case FM_START: + error = ufm_start(sc, addr); + break; + case FM_STOP: + error = ufm_stop(sc, addr); + break; + case FM_GET_STAT: + error = ufm_get_stat(sc, addr); + break; + default: + error = ENOTTY; + break; + } + return (error); +} diff --git a/sys/bus/u4b/net/if_aue.c b/sys/bus/u4b/net/if_aue.c new file mode 100644 index 0000000000..fdee6b7232 --- /dev/null +++ b/sys/bus/u4b/net/if_aue.c @@ -0,0 +1,1062 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . All rights reserved. + * + * Copyright (c) 2006 + * Alfred Perlstein . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * ADMtek AN986 Pegasus and AN8511 Pegasus II USB to ethernet driver. + * Datasheet is available from http://www.admtek.com.tw. + * + * Written by Bill Paul + * Electrical Engineering Department + * Columbia University, New York City + * + * SMP locking by Alfred Perlstein . + * RED Inc. + */ + +/* + * The Pegasus chip uses four USB "endpoints" to provide 10/100 ethernet + * support: the control endpoint for reading/writing registers, burst + * read endpoint for packet reception, burst write for packet transmission + * and one for "interrupts." The chip uses the same RX filter scheme + * as the other ADMtek ethernet parts: one perfect filter entry for the + * the station address and a 64-bit multicast hash table. The chip supports + * both MII and HomePNA attachments. + * + * Since the maximum data transfer speed of USB is supposed to be 12Mbps, + * you're never really going to get 100Mbps speeds from this device. I + * think the idea is to allow the device to connect to 10 or 100Mbps + * networks, not necessarily to provide 100Mbps performance. Also, since + * the controller uses an external PHY chip, it's possible that board + * designers might simply choose a 10Mbps PHY. + * + * Registers are accessed using uether_do_request(). Packet + * transfers are done using usbd_transfer() and friends. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR aue_debug +#include +#include + +#include +#include + +#ifdef USB_DEBUG +static int aue_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, aue, CTLFLAG_RW, 0, "USB aue"); +SYSCTL_INT(_hw_usb_aue, OID_AUTO, debug, CTLFLAG_RW, &aue_debug, 0, + "Debug level"); +#endif + +/* + * Various supported device vendors/products. + */ +static const STRUCT_USB_HOST_ID aue_devs[] = { +#define AUE_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } + AUE_DEV(3COM, 3C460B, AUE_FLAG_PII), + AUE_DEV(ABOCOM, DSB650TX_PNA, 0), + AUE_DEV(ABOCOM, UFE1000, AUE_FLAG_LSYS), + AUE_DEV(ABOCOM, XX10, 0), + AUE_DEV(ABOCOM, XX1, AUE_FLAG_PNA | AUE_FLAG_PII), + AUE_DEV(ABOCOM, XX2, AUE_FLAG_PII), + AUE_DEV(ABOCOM, XX4, AUE_FLAG_PNA), + AUE_DEV(ABOCOM, XX5, AUE_FLAG_PNA), + AUE_DEV(ABOCOM, XX6, AUE_FLAG_PII), + AUE_DEV(ABOCOM, XX7, AUE_FLAG_PII), + AUE_DEV(ABOCOM, XX8, AUE_FLAG_PII), + AUE_DEV(ABOCOM, XX9, AUE_FLAG_PNA), + AUE_DEV(ACCTON, SS1001, AUE_FLAG_PII), + AUE_DEV(ACCTON, USB320_EC, 0), + AUE_DEV(ADMTEK, PEGASUSII_2, AUE_FLAG_PII), + AUE_DEV(ADMTEK, PEGASUSII_3, AUE_FLAG_PII), + AUE_DEV(ADMTEK, PEGASUSII_4, AUE_FLAG_PII), + AUE_DEV(ADMTEK, PEGASUSII, AUE_FLAG_PII), + AUE_DEV(ADMTEK, PEGASUS, AUE_FLAG_PNA | AUE_FLAG_DUAL_PHY), + AUE_DEV(AEI, FASTETHERNET, AUE_FLAG_PII), + AUE_DEV(ALLIEDTELESYN, ATUSB100, AUE_FLAG_PII), + AUE_DEV(ATEN, UC110T, AUE_FLAG_PII), + AUE_DEV(BELKIN, USB2LAN, AUE_FLAG_PII), + AUE_DEV(BILLIONTON, USB100, 0), + AUE_DEV(BILLIONTON, USBE100, AUE_FLAG_PII), + AUE_DEV(BILLIONTON, USBEL100, 0), + AUE_DEV(BILLIONTON, USBLP100, AUE_FLAG_PNA), + AUE_DEV(COREGA, FETHER_USB_TXS, AUE_FLAG_PII), + AUE_DEV(COREGA, FETHER_USB_TX, 0), + AUE_DEV(DLINK, DSB650TX1, AUE_FLAG_LSYS), + AUE_DEV(DLINK, DSB650TX2, AUE_FLAG_LSYS | AUE_FLAG_PII), + AUE_DEV(DLINK, DSB650TX3, AUE_FLAG_LSYS | AUE_FLAG_PII), + AUE_DEV(DLINK, DSB650TX4, AUE_FLAG_LSYS | AUE_FLAG_PII), + AUE_DEV(DLINK, DSB650TX_PNA, AUE_FLAG_PNA), + AUE_DEV(DLINK, DSB650TX, AUE_FLAG_LSYS), + AUE_DEV(DLINK, DSB650, AUE_FLAG_LSYS), + AUE_DEV(ELCON, PLAN, AUE_FLAG_PNA | AUE_FLAG_PII), + AUE_DEV(ELECOM, LDUSB20, AUE_FLAG_PII), + AUE_DEV(ELECOM, LDUSBLTX, AUE_FLAG_PII), + AUE_DEV(ELECOM, LDUSBTX0, 0), + AUE_DEV(ELECOM, LDUSBTX1, AUE_FLAG_LSYS), + AUE_DEV(ELECOM, LDUSBTX2, 0), + AUE_DEV(ELECOM, LDUSBTX3, AUE_FLAG_LSYS), + AUE_DEV(ELSA, USB2ETHERNET, 0), + AUE_DEV(GIGABYTE, GNBR402W, 0), + AUE_DEV(HAWKING, UF100, AUE_FLAG_PII), + AUE_DEV(HP, HN210E, AUE_FLAG_PII), + AUE_DEV(IODATA, USBETTXS, AUE_FLAG_PII), + AUE_DEV(IODATA, USBETTX, 0), + AUE_DEV(KINGSTON, KNU101TX, 0), + AUE_DEV(LINKSYS, USB100H1, AUE_FLAG_LSYS | AUE_FLAG_PNA), + AUE_DEV(LINKSYS, USB100TX, AUE_FLAG_LSYS), + AUE_DEV(LINKSYS, USB10TA, AUE_FLAG_LSYS), + AUE_DEV(LINKSYS, USB10TX1, AUE_FLAG_LSYS | AUE_FLAG_PII), + AUE_DEV(LINKSYS, USB10TX2, AUE_FLAG_LSYS | AUE_FLAG_PII), + AUE_DEV(LINKSYS, USB10T, AUE_FLAG_LSYS), + AUE_DEV(MELCO, LUA2TX5, AUE_FLAG_PII), + AUE_DEV(MELCO, LUATX1, 0), + AUE_DEV(MELCO, LUATX5, 0), + AUE_DEV(MICROSOFT, MN110, AUE_FLAG_PII), + AUE_DEV(NETGEAR, FA101, AUE_FLAG_PII), + AUE_DEV(SIEMENS, SPEEDSTREAM, AUE_FLAG_PII), + AUE_DEV(SIIG2, USBTOETHER, AUE_FLAG_PII), + AUE_DEV(SMARTBRIDGES, SMARTNIC, AUE_FLAG_PII), + AUE_DEV(SMC, 2202USB, 0), + AUE_DEV(SMC, 2206USB, AUE_FLAG_PII), + AUE_DEV(SOHOWARE, NUB100, 0), + AUE_DEV(SOHOWARE, NUB110, AUE_FLAG_PII), +#undef AUE_DEV +}; + +/* prototypes */ + +static device_probe_t aue_probe; +static device_attach_t aue_attach; +static device_detach_t aue_detach; +static miibus_readreg_t aue_miibus_readreg; +static miibus_writereg_t aue_miibus_writereg; +static miibus_statchg_t aue_miibus_statchg; + +static usb_callback_t aue_intr_callback; +static usb_callback_t aue_bulk_read_callback; +static usb_callback_t aue_bulk_write_callback; + +static uether_fn_t aue_attach_post; +static uether_fn_t aue_init; +static uether_fn_t aue_stop; +static uether_fn_t aue_start; +static uether_fn_t aue_tick; +static uether_fn_t aue_setmulti; +static uether_fn_t aue_setpromisc; + +static uint8_t aue_csr_read_1(struct aue_softc *, uint16_t); +static uint16_t aue_csr_read_2(struct aue_softc *, uint16_t); +static void aue_csr_write_1(struct aue_softc *, uint16_t, uint8_t); +static void aue_csr_write_2(struct aue_softc *, uint16_t, uint16_t); +static void aue_eeprom_getword(struct aue_softc *, int, uint16_t *); +static void aue_read_eeprom(struct aue_softc *, uint8_t *, uint16_t, + uint16_t); +static void aue_reset(struct aue_softc *); +static void aue_reset_pegasus_II(struct aue_softc *); + +static int aue_ifmedia_upd(struct ifnet *); +static void aue_ifmedia_sts(struct ifnet *, struct ifmediareq *); + +static const struct usb_config aue_config[AUE_N_TRANSFER] = { + + [AUE_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = (MCLBYTES + 2), + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = aue_bulk_write_callback, + .timeout = 10000, /* 10 seconds */ + }, + + [AUE_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = (MCLBYTES + 4 + ETHER_CRC_LEN), + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = aue_bulk_read_callback, + }, + + [AUE_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = aue_intr_callback, + }, +}; + +static device_method_t aue_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, aue_probe), + DEVMETHOD(device_attach, aue_attach), + DEVMETHOD(device_detach, aue_detach), + + /* MII interface */ + DEVMETHOD(miibus_readreg, aue_miibus_readreg), + DEVMETHOD(miibus_writereg, aue_miibus_writereg), + DEVMETHOD(miibus_statchg, aue_miibus_statchg), + + DEVMETHOD_END +}; + +static driver_t aue_driver = { + .name = "aue", + .methods = aue_methods, + .size = sizeof(struct aue_softc) +}; + +static devclass_t aue_devclass; + +DRIVER_MODULE(aue, uhub, aue_driver, aue_devclass, NULL, 0); +DRIVER_MODULE(miibus, aue, miibus_driver, miibus_devclass, 0, 0); +MODULE_DEPEND(aue, uether, 1, 1, 1); +MODULE_DEPEND(aue, usb, 1, 1, 1); +MODULE_DEPEND(aue, ether, 1, 1, 1); +MODULE_DEPEND(aue, miibus, 1, 1, 1); +MODULE_VERSION(aue, 1); + +static const struct usb_ether_methods aue_ue_methods = { + .ue_attach_post = aue_attach_post, + .ue_start = aue_start, + .ue_init = aue_init, + .ue_stop = aue_stop, + .ue_tick = aue_tick, + .ue_setmulti = aue_setmulti, + .ue_setpromisc = aue_setpromisc, + .ue_mii_upd = aue_ifmedia_upd, + .ue_mii_sts = aue_ifmedia_sts, +}; + +#define AUE_SETBIT(sc, reg, x) \ + aue_csr_write_1(sc, reg, aue_csr_read_1(sc, reg) | (x)) + +#define AUE_CLRBIT(sc, reg, x) \ + aue_csr_write_1(sc, reg, aue_csr_read_1(sc, reg) & ~(x)) + +static uint8_t +aue_csr_read_1(struct aue_softc *sc, uint16_t reg) +{ + struct usb_device_request req; + usb_error_t err; + uint8_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = AUE_UR_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + err = uether_do_request(&sc->sc_ue, &req, &val, 1000); + if (err) + return (0); + return (val); +} + +static uint16_t +aue_csr_read_2(struct aue_softc *sc, uint16_t reg) +{ + struct usb_device_request req; + usb_error_t err; + uint16_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = AUE_UR_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 2); + + err = uether_do_request(&sc->sc_ue, &req, &val, 1000); + if (err) + return (0); + return (le16toh(val)); +} + +static void +aue_csr_write_1(struct aue_softc *sc, uint16_t reg, uint8_t val) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = AUE_UR_WRITEREG; + req.wValue[0] = val; + req.wValue[1] = 0; + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + if (uether_do_request(&sc->sc_ue, &req, &val, 1000)) { + /* error ignored */ + } +} + +static void +aue_csr_write_2(struct aue_softc *sc, uint16_t reg, uint16_t val) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = AUE_UR_WRITEREG; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 2); + + val = htole16(val); + + if (uether_do_request(&sc->sc_ue, &req, &val, 1000)) { + /* error ignored */ + } +} + +/* + * Read a word of data stored in the EEPROM at address 'addr.' + */ +static void +aue_eeprom_getword(struct aue_softc *sc, int addr, uint16_t *dest) +{ + int i; + uint16_t word = 0; + + aue_csr_write_1(sc, AUE_EE_REG, addr); + aue_csr_write_1(sc, AUE_EE_CTL, AUE_EECTL_READ); + + for (i = 0; i != AUE_TIMEOUT; i++) { + if (aue_csr_read_1(sc, AUE_EE_CTL) & AUE_EECTL_DONE) + break; + if (uether_pause(&sc->sc_ue, hz / 100)) + break; + } + + if (i == AUE_TIMEOUT) + device_printf(sc->sc_ue.ue_dev, "EEPROM read timed out\n"); + + word = aue_csr_read_2(sc, AUE_EE_DATA); + *dest = word; +} + +/* + * Read a sequence of words from the EEPROM. + */ +static void +aue_read_eeprom(struct aue_softc *sc, uint8_t *dest, + uint16_t off, uint16_t len) +{ + uint16_t *ptr = (uint16_t *)dest; + int i; + + for (i = 0; i != len; i++, ptr++) + aue_eeprom_getword(sc, off + i, ptr); +} + +static int +aue_miibus_readreg(device_t dev, int phy, int reg) +{ + struct aue_softc *sc = device_get_softc(dev); + int i, locked; + uint16_t val = 0; + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + AUE_LOCK(sc); + + /* + * The Am79C901 HomePNA PHY actually contains two transceivers: a 1Mbps + * HomePNA PHY and a 10Mbps full/half duplex ethernet PHY with NWAY + * autoneg. However in the ADMtek adapter, only the 1Mbps PHY is + * actually connected to anything, so we ignore the 10Mbps one. It + * happens to be configured for MII address 3, so we filter that out. + */ + if (sc->sc_flags & AUE_FLAG_DUAL_PHY) { + if (phy == 3) + goto done; +#if 0 + if (phy != 1) + goto done; +#endif + } + aue_csr_write_1(sc, AUE_PHY_ADDR, phy); + aue_csr_write_1(sc, AUE_PHY_CTL, reg | AUE_PHYCTL_READ); + + for (i = 0; i != AUE_TIMEOUT; i++) { + if (aue_csr_read_1(sc, AUE_PHY_CTL) & AUE_PHYCTL_DONE) + break; + if (uether_pause(&sc->sc_ue, hz / 100)) + break; + } + + if (i == AUE_TIMEOUT) + device_printf(sc->sc_ue.ue_dev, "MII read timed out\n"); + + val = aue_csr_read_2(sc, AUE_PHY_DATA); + +done: + if (!locked) + AUE_UNLOCK(sc); + return (val); +} + +static int +aue_miibus_writereg(device_t dev, int phy, int reg, int data) +{ + struct aue_softc *sc = device_get_softc(dev); + int i; + int locked; + + if (phy == 3) + return (0); + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + AUE_LOCK(sc); + + aue_csr_write_2(sc, AUE_PHY_DATA, data); + aue_csr_write_1(sc, AUE_PHY_ADDR, phy); + aue_csr_write_1(sc, AUE_PHY_CTL, reg | AUE_PHYCTL_WRITE); + + for (i = 0; i != AUE_TIMEOUT; i++) { + if (aue_csr_read_1(sc, AUE_PHY_CTL) & AUE_PHYCTL_DONE) + break; + if (uether_pause(&sc->sc_ue, hz / 100)) + break; + } + + if (i == AUE_TIMEOUT) + device_printf(sc->sc_ue.ue_dev, "MII write timed out\n"); + + if (!locked) + AUE_UNLOCK(sc); + return (0); +} + +static void +aue_miibus_statchg(device_t dev) +{ + struct aue_softc *sc = device_get_softc(dev); + struct mii_data *mii = GET_MII(sc); + int locked; + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + AUE_LOCK(sc); + + AUE_CLRBIT(sc, AUE_CTL0, AUE_CTL0_RX_ENB | AUE_CTL0_TX_ENB); + if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX) + AUE_SETBIT(sc, AUE_CTL1, AUE_CTL1_SPEEDSEL); + else + AUE_CLRBIT(sc, AUE_CTL1, AUE_CTL1_SPEEDSEL); + + if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) + AUE_SETBIT(sc, AUE_CTL1, AUE_CTL1_DUPLEX); + else + AUE_CLRBIT(sc, AUE_CTL1, AUE_CTL1_DUPLEX); + + AUE_SETBIT(sc, AUE_CTL0, AUE_CTL0_RX_ENB | AUE_CTL0_TX_ENB); + + /* + * Set the LED modes on the LinkSys adapter. + * This turns on the 'dual link LED' bin in the auxmode + * register of the Broadcom PHY. + */ + if (sc->sc_flags & AUE_FLAG_LSYS) { + uint16_t auxmode; + + auxmode = aue_miibus_readreg(dev, 0, 0x1b); + aue_miibus_writereg(dev, 0, 0x1b, auxmode | 0x04); + } + if (!locked) + AUE_UNLOCK(sc); +} + +#define AUE_BITS 6 +static void +aue_setmulti(struct usb_ether *ue) +{ + struct aue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + struct ifmultiaddr *ifma; + uint32_t h = 0; + uint32_t i; + uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + AUE_LOCK_ASSERT(sc, MA_OWNED); + + if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { + AUE_SETBIT(sc, AUE_CTL0, AUE_CTL0_ALLMULTI); + return; + } + + AUE_CLRBIT(sc, AUE_CTL0, AUE_CTL0_ALLMULTI); + + /* now program new ones */ + if_maddr_rlock(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + h = ether_crc32_le(LLADDR((struct sockaddr_dl *) + ifma->ifma_addr), ETHER_ADDR_LEN) & ((1 << AUE_BITS) - 1); + hashtbl[(h >> 3)] |= 1 << (h & 0x7); + } + if_maddr_runlock(ifp); + + /* write the hashtable */ + for (i = 0; i != 8; i++) + aue_csr_write_1(sc, AUE_MAR0 + i, hashtbl[i]); +} + +static void +aue_reset_pegasus_II(struct aue_softc *sc) +{ + /* Magic constants taken from Linux driver. */ + aue_csr_write_1(sc, AUE_REG_1D, 0); + aue_csr_write_1(sc, AUE_REG_7B, 2); +#if 0 + if ((sc->sc_flags & HAS_HOME_PNA) && mii_mode) + aue_csr_write_1(sc, AUE_REG_81, 6); + else +#endif + aue_csr_write_1(sc, AUE_REG_81, 2); +} + +static void +aue_reset(struct aue_softc *sc) +{ + int i; + + AUE_SETBIT(sc, AUE_CTL1, AUE_CTL1_RESETMAC); + + for (i = 0; i != AUE_TIMEOUT; i++) { + if (!(aue_csr_read_1(sc, AUE_CTL1) & AUE_CTL1_RESETMAC)) + break; + if (uether_pause(&sc->sc_ue, hz / 100)) + break; + } + + if (i == AUE_TIMEOUT) + device_printf(sc->sc_ue.ue_dev, "reset failed\n"); + + /* + * The PHY(s) attached to the Pegasus chip may be held + * in reset until we flip on the GPIO outputs. Make sure + * to set the GPIO pins high so that the PHY(s) will + * be enabled. + * + * NOTE: We used to force all of the GPIO pins low first and then + * enable the ones we want. This has been changed to better + * match the ADMtek's reference design to avoid setting the + * power-down configuration line of the PHY at the same time + * it is reset. + */ + aue_csr_write_1(sc, AUE_GPIO0, AUE_GPIO_SEL0|AUE_GPIO_SEL1); + aue_csr_write_1(sc, AUE_GPIO0, AUE_GPIO_SEL0|AUE_GPIO_SEL1|AUE_GPIO_OUT0); + + if (sc->sc_flags & AUE_FLAG_LSYS) { + /* Grrr. LinkSys has to be different from everyone else. */ + aue_csr_write_1(sc, AUE_GPIO0, AUE_GPIO_SEL0|AUE_GPIO_SEL1); + aue_csr_write_1(sc, AUE_GPIO0, + AUE_GPIO_SEL0|AUE_GPIO_SEL1|AUE_GPIO_OUT0); + } + if (sc->sc_flags & AUE_FLAG_PII) + aue_reset_pegasus_II(sc); + + /* Wait a little while for the chip to get its brains in order: */ + uether_pause(&sc->sc_ue, hz / 100); +} + +static void +aue_attach_post(struct usb_ether *ue) +{ + struct aue_softc *sc = uether_getsc(ue); + + /* reset the adapter */ + aue_reset(sc); + + /* get station address from the EEPROM */ + aue_read_eeprom(sc, ue->ue_eaddr, 0, 3); +} + +/* + * Probe for a Pegasus chip. + */ +static int +aue_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != AUE_CONFIG_INDEX) + return (ENXIO); + if (uaa->info.bIfaceIndex != AUE_IFACE_IDX) + return (ENXIO); + /* + * Belkin USB Bluetooth dongles of the F8T012xx1 model series conflict + * with older Belkin USB2LAN adapters. Skip if_aue if we detect one of + * the devices that look like Bluetooth adapters. + */ + if (uaa->info.idVendor == USB_VENDOR_BELKIN && + uaa->info.idProduct == USB_PRODUCT_BELKIN_F8T012 && + uaa->info.bcdDevice == 0x0413) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(aue_devs, sizeof(aue_devs), uaa)); +} + +/* + * Attach the interface. Allocate softc structures, do ifmedia + * setup and ethernet/BPF attach. + */ +static int +aue_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct aue_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + uint8_t iface_index; + int error; + + sc->sc_flags = USB_GET_DRIVER_INFO(uaa); + + if (uaa->info.bcdDevice >= 0x0201) { + /* XXX currently undocumented */ + sc->sc_flags |= AUE_FLAG_VER_2; + } + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + iface_index = AUE_IFACE_IDX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, aue_config, AUE_N_TRANSFER, + sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB transfers failed\n"); + goto detach; + } + + ue->ue_sc = sc; + ue->ue_dev = dev; + ue->ue_udev = uaa->device; + ue->ue_mtx = &sc->sc_mtx; + ue->ue_methods = &aue_ue_methods; + + error = uether_ifattach(ue); + if (error) { + device_printf(dev, "could not attach interface\n"); + goto detach; + } + return (0); /* success */ + +detach: + aue_detach(dev); + return (ENXIO); /* failure */ +} + +static int +aue_detach(device_t dev) +{ + struct aue_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + + usbd_transfer_unsetup(sc->sc_xfer, AUE_N_TRANSFER); + uether_ifdetach(ue); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +aue_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct aue_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct aue_intrpkt pkt; + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) && + actlen >= sizeof(pkt)) { + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &pkt, sizeof(pkt)); + + if (pkt.aue_txstat0) + ifp->if_oerrors++; + if (pkt.aue_txstat0 & (AUE_TXSTAT0_LATECOLL & + AUE_TXSTAT0_EXCESSCOLL)) + ifp->if_collisions++; + } + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +aue_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct aue_softc *sc = usbd_xfer_softc(xfer); + struct usb_ether *ue = &sc->sc_ue; + struct ifnet *ifp = uether_getifp(ue); + struct aue_rxpkt stat; + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + pc = usbd_xfer_get_frame(xfer, 0); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "received %d bytes\n", actlen); + + if (sc->sc_flags & AUE_FLAG_VER_2) { + + if (actlen == 0) { + ifp->if_ierrors++; + goto tr_setup; + } + } else { + + if (actlen <= sizeof(stat) + ETHER_CRC_LEN) { + ifp->if_ierrors++; + goto tr_setup; + } + usbd_copy_out(pc, actlen - sizeof(stat), &stat, + sizeof(stat)); + + /* + * turn off all the non-error bits in the rx status + * word: + */ + stat.aue_rxstat &= AUE_RXSTAT_MASK; + if (stat.aue_rxstat) { + ifp->if_ierrors++; + goto tr_setup; + } + /* No errors; receive the packet. */ + actlen -= (sizeof(stat) + ETHER_CRC_LEN); + } + uether_rxbuf(ue, pc, 0, actlen); + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + uether_rxflush(ue); + return; + + default: /* Error */ + DPRINTF("bulk read error, %s\n", + usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +aue_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct aue_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct usb_page_cache *pc; + struct mbuf *m; + uint8_t buf[2]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + pc = usbd_xfer_get_frame(xfer, 0); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer of %d bytes complete\n", actlen); + ifp->if_opackets++; + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + if ((sc->sc_flags & AUE_FLAG_LINK) == 0) { + /* + * don't send anything if there is no link ! + */ + return; + } + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) + return; + if (m->m_pkthdr.len > MCLBYTES) + m->m_pkthdr.len = MCLBYTES; + if (sc->sc_flags & AUE_FLAG_VER_2) { + + usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len); + + usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); + + } else { + + usbd_xfer_set_frame_len(xfer, 0, (m->m_pkthdr.len + 2)); + + /* + * The ADMtek documentation says that the + * packet length is supposed to be specified + * in the first two bytes of the transfer, + * however it actually seems to ignore this + * info and base the frame size on the bulk + * transfer length. + */ + buf[0] = (uint8_t)(m->m_pkthdr.len); + buf[1] = (uint8_t)(m->m_pkthdr.len >> 8); + + usbd_copy_in(pc, 0, buf, 2); + usbd_m_copy_in(pc, 2, m, 0, m->m_pkthdr.len); + } + + /* + * if there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + + m_freem(m); + + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usbd_errstr(error)); + + ifp->if_oerrors++; + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +aue_tick(struct usb_ether *ue) +{ + struct aue_softc *sc = uether_getsc(ue); + struct mii_data *mii = GET_MII(sc); + + AUE_LOCK_ASSERT(sc, MA_OWNED); + + mii_tick(mii); + if ((sc->sc_flags & AUE_FLAG_LINK) == 0 + && mii->mii_media_status & IFM_ACTIVE && + IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { + sc->sc_flags |= AUE_FLAG_LINK; + aue_start(ue); + } +} + +static void +aue_start(struct usb_ether *ue) +{ + struct aue_softc *sc = uether_getsc(ue); + + /* + * start the USB transfers, if not already started: + */ + usbd_transfer_start(sc->sc_xfer[AUE_INTR_DT_RD]); + usbd_transfer_start(sc->sc_xfer[AUE_BULK_DT_RD]); + usbd_transfer_start(sc->sc_xfer[AUE_BULK_DT_WR]); +} + +static void +aue_init(struct usb_ether *ue) +{ + struct aue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + int i; + + AUE_LOCK_ASSERT(sc, MA_OWNED); + + /* + * Cancel pending I/O + */ + aue_reset(sc); + + /* Set MAC address */ + for (i = 0; i != ETHER_ADDR_LEN; i++) + aue_csr_write_1(sc, AUE_PAR0 + i, IF_LLADDR(ifp)[i]); + + /* update promiscuous setting */ + aue_setpromisc(ue); + + /* Load the multicast filter. */ + aue_setmulti(ue); + + /* Enable RX and TX */ + aue_csr_write_1(sc, AUE_CTL0, AUE_CTL0_RXSTAT_APPEND | AUE_CTL0_RX_ENB); + AUE_SETBIT(sc, AUE_CTL0, AUE_CTL0_TX_ENB); + AUE_SETBIT(sc, AUE_CTL2, AUE_CTL2_EP3_CLR); + + usbd_xfer_set_stall(sc->sc_xfer[AUE_BULK_DT_WR]); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + aue_start(ue); +} + +static void +aue_setpromisc(struct usb_ether *ue) +{ + struct aue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + AUE_LOCK_ASSERT(sc, MA_OWNED); + + /* if we want promiscuous mode, set the allframes bit: */ + if (ifp->if_flags & IFF_PROMISC) + AUE_SETBIT(sc, AUE_CTL2, AUE_CTL2_RX_PROMISC); + else + AUE_CLRBIT(sc, AUE_CTL2, AUE_CTL2_RX_PROMISC); +} + +/* + * Set media options. + */ +static int +aue_ifmedia_upd(struct ifnet *ifp) +{ + struct aue_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + struct mii_softc *miisc; + + AUE_LOCK_ASSERT(sc, MA_OWNED); + + sc->sc_flags &= ~AUE_FLAG_LINK; + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) + PHY_RESET(miisc); + mii_mediachg(mii); + return (0); +} + +/* + * Report current media status. + */ +static void +aue_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct aue_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + + AUE_LOCK(sc); + mii_pollstat(mii); + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; + AUE_UNLOCK(sc); +} + +/* + * Stop the adapter and free any mbufs allocated to the + * RX and TX lists. + */ +static void +aue_stop(struct usb_ether *ue) +{ + struct aue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + AUE_LOCK_ASSERT(sc, MA_OWNED); + + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + sc->sc_flags &= ~AUE_FLAG_LINK; + + /* + * stop all the transfers, if not already stopped: + */ + usbd_transfer_stop(sc->sc_xfer[AUE_BULK_DT_WR]); + usbd_transfer_stop(sc->sc_xfer[AUE_BULK_DT_RD]); + usbd_transfer_stop(sc->sc_xfer[AUE_INTR_DT_RD]); + + aue_csr_write_1(sc, AUE_CTL0, 0); + aue_csr_write_1(sc, AUE_CTL1, 0); + aue_reset(sc); +} diff --git a/sys/bus/u4b/net/if_auereg.h b/sys/bus/u4b/net/if_auereg.h new file mode 100644 index 0000000000..4d0843eb8b --- /dev/null +++ b/sys/bus/u4b/net/if_auereg.h @@ -0,0 +1,220 @@ +/*- + * Copyright (c) 1997, 1998, 1999 + * Bill Paul . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Register definitions for ADMtek Pegasus AN986 USB to Ethernet + * chip. The Pegasus uses a total of four USB endpoints: the control + * endpoint (0), a bulk read endpoint for receiving packets (1), + * a bulk write endpoint for sending packets (2) and an interrupt + * endpoint for passing RX and TX status (3). Endpoint 0 is used + * to read and write the ethernet module's registers. All registers + * are 8 bits wide. + * + * Packet transfer is done in 64 byte chunks. The last chunk in a + * transfer is denoted by having a length less that 64 bytes. For + * the RX case, the data includes an optional RX status word. + */ + +#define AUE_UR_READREG 0xF0 +#define AUE_UR_WRITEREG 0xF1 + +#define AUE_CONFIG_INDEX 0 /* config number 1 */ +#define AUE_IFACE_IDX 0 + +/* + * Note that while the ADMtek technically has four endpoints, the control + * endpoint (endpoint 0) is regarded as special by the USB code and drivers + * don't have direct access to it (we access it using usbd_do_request() + * when reading/writing registers. Consequently, our endpoint indexes + * don't match those in the ADMtek Pegasus manual: we consider the RX data + * endpoint to be index 0 and work up from there. + */ +enum { + AUE_BULK_DT_WR, + AUE_BULK_DT_RD, + AUE_INTR_DT_RD, + AUE_N_TRANSFER, +}; + +#define AUE_INTR_PKTLEN 0x8 + +#define AUE_CTL0 0x00 +#define AUE_CTL1 0x01 +#define AUE_CTL2 0x02 +#define AUE_MAR0 0x08 +#define AUE_MAR1 0x09 +#define AUE_MAR2 0x0A +#define AUE_MAR3 0x0B +#define AUE_MAR4 0x0C +#define AUE_MAR5 0x0D +#define AUE_MAR6 0x0E +#define AUE_MAR7 0x0F +#define AUE_MAR AUE_MAR0 +#define AUE_PAR0 0x10 +#define AUE_PAR1 0x11 +#define AUE_PAR2 0x12 +#define AUE_PAR3 0x13 +#define AUE_PAR4 0x14 +#define AUE_PAR5 0x15 +#define AUE_PAR AUE_PAR0 +#define AUE_PAUSE0 0x18 +#define AUE_PAUSE1 0x19 +#define AUE_PAUSE AUE_PAUSE0 +#define AUE_RX_FLOWCTL_CNT 0x1A +#define AUE_RX_FLOWCTL_FIFO 0x1B +#define AUE_REG_1D 0x1D +#define AUE_EE_REG 0x20 +#define AUE_EE_DATA0 0x21 +#define AUE_EE_DATA1 0x22 +#define AUE_EE_DATA AUE_EE_DATA0 +#define AUE_EE_CTL 0x23 +#define AUE_PHY_ADDR 0x25 +#define AUE_PHY_DATA0 0x26 +#define AUE_PHY_DATA1 0x27 +#define AUE_PHY_DATA AUE_PHY_DATA0 +#define AUE_PHY_CTL 0x28 +#define AUE_USB_STS 0x2A +#define AUE_TXSTAT0 0x2B +#define AUE_TXSTAT1 0x2C +#define AUE_TXSTAT AUE_TXSTAT0 +#define AUE_RXSTAT 0x2D +#define AUE_PKTLOST0 0x2E +#define AUE_PKTLOST1 0x2F +#define AUE_PKTLOST AUE_PKTLOST0 + +#define AUE_REG_7B 0x7B +#define AUE_GPIO0 0x7E +#define AUE_GPIO1 0x7F +#define AUE_REG_81 0x81 + +#define AUE_CTL0_INCLUDE_RXCRC 0x01 +#define AUE_CTL0_ALLMULTI 0x02 +#define AUE_CTL0_STOP_BACKOFF 0x04 +#define AUE_CTL0_RXSTAT_APPEND 0x08 +#define AUE_CTL0_WAKEON_ENB 0x10 +#define AUE_CTL0_RXPAUSE_ENB 0x20 +#define AUE_CTL0_RX_ENB 0x40 +#define AUE_CTL0_TX_ENB 0x80 + +#define AUE_CTL1_HOMELAN 0x04 +#define AUE_CTL1_RESETMAC 0x08 +#define AUE_CTL1_SPEEDSEL 0x10 /* 0 = 10mbps, 1 = 100mbps */ +#define AUE_CTL1_DUPLEX 0x20 /* 0 = half, 1 = full */ +#define AUE_CTL1_DELAYHOME 0x40 + +#define AUE_CTL2_EP3_CLR 0x01 /* reading EP3 clrs status regs */ +#define AUE_CTL2_RX_BADFRAMES 0x02 +#define AUE_CTL2_RX_PROMISC 0x04 +#define AUE_CTL2_LOOPBACK 0x08 +#define AUE_CTL2_EEPROMWR_ENB 0x10 +#define AUE_CTL2_EEPROM_LOAD 0x20 + +#define AUE_EECTL_WRITE 0x01 +#define AUE_EECTL_READ 0x02 +#define AUE_EECTL_DONE 0x04 + +#define AUE_PHYCTL_PHYREG 0x1F +#define AUE_PHYCTL_WRITE 0x20 +#define AUE_PHYCTL_READ 0x40 +#define AUE_PHYCTL_DONE 0x80 + +#define AUE_USBSTS_SUSPEND 0x01 +#define AUE_USBSTS_RESUME 0x02 + +#define AUE_TXSTAT0_JABTIMO 0x04 +#define AUE_TXSTAT0_CARLOSS 0x08 +#define AUE_TXSTAT0_NOCARRIER 0x10 +#define AUE_TXSTAT0_LATECOLL 0x20 +#define AUE_TXSTAT0_EXCESSCOLL 0x40 +#define AUE_TXSTAT0_UNDERRUN 0x80 + +#define AUE_TXSTAT1_PKTCNT 0x0F +#define AUE_TXSTAT1_FIFO_EMPTY 0x40 +#define AUE_TXSTAT1_FIFO_FULL 0x80 + +#define AUE_RXSTAT_OVERRUN 0x01 +#define AUE_RXSTAT_PAUSE 0x02 + +#define AUE_GPIO_IN0 0x01 +#define AUE_GPIO_OUT0 0x02 +#define AUE_GPIO_SEL0 0x04 +#define AUE_GPIO_IN1 0x08 +#define AUE_GPIO_OUT1 0x10 +#define AUE_GPIO_SEL1 0x20 + +#define AUE_TIMEOUT 100 /* 10*ms */ +#define AUE_MIN_FRAMELEN 60 + +#define AUE_RXSTAT_MCAST 0x01 +#define AUE_RXSTAT_GIANT 0x02 +#define AUE_RXSTAT_RUNT 0x04 +#define AUE_RXSTAT_CRCERR 0x08 +#define AUE_RXSTAT_DRIBBLE 0x10 +#define AUE_RXSTAT_MASK 0x1E + +#define GET_MII(sc) uether_getmii(&(sc)->sc_ue) + +struct aue_intrpkt { + uint8_t aue_txstat0; + uint8_t aue_txstat1; + uint8_t aue_rxstat; + uint8_t aue_rxlostpkt0; + uint8_t aue_rxlostpkt1; + uint8_t aue_wakeupstat; + uint8_t aue_rsvd; +} __packed; + +struct aue_rxpkt { + uint16_t aue_pktlen; + uint8_t aue_rxstat; + uint8_t pad; +} __packed; + +struct aue_softc { + struct usb_ether sc_ue; + struct mtx sc_mtx; + struct usb_xfer *sc_xfer[AUE_N_TRANSFER]; + + int sc_flags; +#define AUE_FLAG_LSYS 0x0001 /* use Linksys reset */ +#define AUE_FLAG_PNA 0x0002 /* has Home PNA */ +#define AUE_FLAG_PII 0x0004 /* Pegasus II chip */ +#define AUE_FLAG_LINK 0x0008 /* wait for link to come up */ +#define AUE_FLAG_VER_2 0x0200 /* chip is version 2 */ +#define AUE_FLAG_DUAL_PHY 0x0400 /* chip has two transcivers */ +}; + +#define AUE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define AUE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define AUE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t) diff --git a/sys/bus/u4b/net/if_axe.c b/sys/bus/u4b/net/if_axe.c new file mode 100644 index 0000000000..02bb358de2 --- /dev/null +++ b/sys/bus/u4b/net/if_axe.c @@ -0,0 +1,1505 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000-2003 + * Bill Paul . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * ASIX Electronics AX88172/AX88178/AX88778 USB 2.0 ethernet driver. + * Used in the LinkSys USB200M and various other adapters. + * + * Manuals available from: + * http://www.asix.com.tw/datasheet/mac/Ax88172.PDF + * Note: you need the manual for the AX88170 chip (USB 1.x ethernet + * controller) to find the definitions for the RX control register. + * http://www.asix.com.tw/datasheet/mac/Ax88170.PDF + * + * Written by Bill Paul + * Senior Engineer + * Wind River Systems + */ + +/* + * The AX88172 provides USB ethernet supports at 10 and 100Mbps. + * It uses an external PHY (reference designs use a RealTek chip), + * and has a 64-bit multicast hash filter. There is some information + * missing from the manual which one needs to know in order to make + * the chip function: + * + * - You must set bit 7 in the RX control register, otherwise the + * chip won't receive any packets. + * - You must initialize all 3 IPG registers, or you won't be able + * to send any packets. + * + * Note that this device appears to only support loading the station + * address via autload from the EEPROM (i.e. there's no way to manaully + * set it). + * + * (Adam Weinberger wanted me to name this driver if_gir.c.) + */ + +/* + * Ax88178 and Ax88772 support backported from the OpenBSD driver. + * 2007/02/12, J.R. Oldroyd, fbsd@opal.com + * + * Manual here: + * http://www.asix.com.tw/FrootAttach/datasheet/AX88178_datasheet_Rev10.pdf + * http://www.asix.com.tw/FrootAttach/datasheet/AX88772_datasheet_Rev10.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR axe_debug +#include +#include + +#include +#include + +/* + * AXE_178_MAX_FRAME_BURST + * max frame burst size for Ax88178 and Ax88772 + * 0 2048 bytes + * 1 4096 bytes + * 2 8192 bytes + * 3 16384 bytes + * use the largest your system can handle without USB stalling. + * + * NB: 88772 parts appear to generate lots of input errors with + * a 2K rx buffer and 8K is only slightly faster than 4K on an + * EHCI port on a T42 so change at your own risk. + */ +#define AXE_178_MAX_FRAME_BURST 1 + +#define AXE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) + +#ifdef USB_DEBUG +static int axe_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, axe, CTLFLAG_RW, 0, "USB axe"); +SYSCTL_INT(_hw_usb_axe, OID_AUTO, debug, CTLFLAG_RW, &axe_debug, 0, + "Debug level"); +#endif + +/* + * Various supported device vendors/products. + */ +static const STRUCT_USB_HOST_ID axe_devs[] = { +#define AXE_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } + AXE_DEV(ABOCOM, UF200, 0), + AXE_DEV(ACERCM, EP1427X2, 0), + AXE_DEV(APPLE, ETHERNET, AXE_FLAG_772), + AXE_DEV(ASIX, AX88172, 0), + AXE_DEV(ASIX, AX88178, AXE_FLAG_178), + AXE_DEV(ASIX, AX88772, AXE_FLAG_772), + AXE_DEV(ASIX, AX88772A, AXE_FLAG_772A), + AXE_DEV(ASIX, AX88772B, AXE_FLAG_772B), + AXE_DEV(ASIX, AX88772B_1, AXE_FLAG_772B), + AXE_DEV(ATEN, UC210T, 0), + AXE_DEV(BELKIN, F5D5055, AXE_FLAG_178), + AXE_DEV(BILLIONTON, USB2AR, 0), + AXE_DEV(CISCOLINKSYS, USB200MV2, AXE_FLAG_772A), + AXE_DEV(COREGA, FETHER_USB2_TX, 0), + AXE_DEV(DLINK, DUBE100, 0), + AXE_DEV(DLINK, DUBE100B1, AXE_FLAG_772), + AXE_DEV(GOODWAY, GWUSB2E, 0), + AXE_DEV(IODATA, ETGUS2, AXE_FLAG_178), + AXE_DEV(JVC, MP_PRX1, 0), + AXE_DEV(LINKSYS2, USB200M, 0), + AXE_DEV(LINKSYS4, USB1000, AXE_FLAG_178), + AXE_DEV(LOGITEC, LAN_GTJU2A, AXE_FLAG_178), + AXE_DEV(MELCO, LUAU2KTX, 0), + AXE_DEV(MELCO, LUA3U2AGT, AXE_FLAG_178), + AXE_DEV(NETGEAR, FA120, 0), + AXE_DEV(OQO, ETHER01PLUS, AXE_FLAG_772), + AXE_DEV(PLANEX3, GU1000T, AXE_FLAG_178), + AXE_DEV(SITECOM, LN029, 0), + AXE_DEV(SITECOMEU, LN028, AXE_FLAG_178), + AXE_DEV(SYSTEMTALKS, SGCX2UL, 0), +#undef AXE_DEV +}; + +static device_probe_t axe_probe; +static device_attach_t axe_attach; +static device_detach_t axe_detach; + +static usb_callback_t axe_bulk_read_callback; +static usb_callback_t axe_bulk_write_callback; + +static miibus_readreg_t axe_miibus_readreg; +static miibus_writereg_t axe_miibus_writereg; +static miibus_statchg_t axe_miibus_statchg; + +static uether_fn_t axe_attach_post; +static uether_fn_t axe_init; +static uether_fn_t axe_stop; +static uether_fn_t axe_start; +static uether_fn_t axe_tick; +static uether_fn_t axe_setmulti; +static uether_fn_t axe_setpromisc; + +static int axe_attach_post_sub(struct usb_ether *); +static int axe_ifmedia_upd(struct ifnet *); +static void axe_ifmedia_sts(struct ifnet *, struct ifmediareq *); +static int axe_cmd(struct axe_softc *, int, int, int, void *); +static void axe_ax88178_init(struct axe_softc *); +static void axe_ax88772_init(struct axe_softc *); +static void axe_ax88772_phywake(struct axe_softc *); +static void axe_ax88772a_init(struct axe_softc *); +static void axe_ax88772b_init(struct axe_softc *); +static int axe_get_phyno(struct axe_softc *, int); +static int axe_ioctl(struct ifnet *, u_long, caddr_t); +static int axe_rx_frame(struct usb_ether *, struct usb_page_cache *, int); +static int axe_rxeof(struct usb_ether *, struct usb_page_cache *, + unsigned int offset, unsigned int, struct axe_csum_hdr *); +static void axe_csum_cfg(struct usb_ether *); + +static const struct usb_config axe_config[AXE_N_TRANSFER] = { + + [AXE_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .frames = 16, + .bufsize = 16 * MCLBYTES, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = axe_bulk_write_callback, + .timeout = 10000, /* 10 seconds */ + }, + + [AXE_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = 16384, /* bytes */ + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = axe_bulk_read_callback, + .timeout = 0, /* no timeout */ + }, +}; + +static const struct ax88772b_mfb ax88772b_mfb_table[] = { + { 0x8000, 0x8001, 2048 }, + { 0x8100, 0x8147, 4096}, + { 0x8200, 0x81EB, 6144}, + { 0x8300, 0x83D7, 8192}, + { 0x8400, 0x851E, 16384}, + { 0x8500, 0x8666, 20480}, + { 0x8600, 0x87AE, 24576}, + { 0x8700, 0x8A3D, 32768} +}; + +static device_method_t axe_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, axe_probe), + DEVMETHOD(device_attach, axe_attach), + DEVMETHOD(device_detach, axe_detach), + + /* MII interface */ + DEVMETHOD(miibus_readreg, axe_miibus_readreg), + DEVMETHOD(miibus_writereg, axe_miibus_writereg), + DEVMETHOD(miibus_statchg, axe_miibus_statchg), + + DEVMETHOD_END +}; + +static driver_t axe_driver = { + .name = "axe", + .methods = axe_methods, + .size = sizeof(struct axe_softc), +}; + +static devclass_t axe_devclass; + +DRIVER_MODULE(axe, uhub, axe_driver, axe_devclass, NULL, 0); +DRIVER_MODULE(miibus, axe, miibus_driver, miibus_devclass, 0, 0); +MODULE_DEPEND(axe, uether, 1, 1, 1); +MODULE_DEPEND(axe, usb, 1, 1, 1); +MODULE_DEPEND(axe, ether, 1, 1, 1); +MODULE_DEPEND(axe, miibus, 1, 1, 1); +MODULE_VERSION(axe, 1); + +static const struct usb_ether_methods axe_ue_methods = { + .ue_attach_post = axe_attach_post, + .ue_attach_post_sub = axe_attach_post_sub, + .ue_start = axe_start, + .ue_init = axe_init, + .ue_stop = axe_stop, + .ue_tick = axe_tick, + .ue_setmulti = axe_setmulti, + .ue_setpromisc = axe_setpromisc, + .ue_mii_upd = axe_ifmedia_upd, + .ue_mii_sts = axe_ifmedia_sts, +}; + +static int +axe_cmd(struct axe_softc *sc, int cmd, int index, int val, void *buf) +{ + struct usb_device_request req; + usb_error_t err; + + AXE_LOCK_ASSERT(sc, MA_OWNED); + + req.bmRequestType = (AXE_CMD_IS_WRITE(cmd) ? + UT_WRITE_VENDOR_DEVICE : + UT_READ_VENDOR_DEVICE); + req.bRequest = AXE_CMD_CMD(cmd); + USETW(req.wValue, val); + USETW(req.wIndex, index); + USETW(req.wLength, AXE_CMD_LEN(cmd)); + + err = uether_do_request(&sc->sc_ue, &req, buf, 1000); + + return (err); +} + +static int +axe_miibus_readreg(device_t dev, int phy, int reg) +{ + struct axe_softc *sc = device_get_softc(dev); + uint16_t val; + int locked; + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + AXE_LOCK(sc); + + axe_cmd(sc, AXE_CMD_MII_OPMODE_SW, 0, 0, NULL); + axe_cmd(sc, AXE_CMD_MII_READ_REG, reg, phy, &val); + axe_cmd(sc, AXE_CMD_MII_OPMODE_HW, 0, 0, NULL); + + val = le16toh(val); + if (AXE_IS_772(sc) && reg == MII_BMSR) { + /* + * BMSR of AX88772 indicates that it supports extended + * capability but the extended status register is + * revered for embedded ethernet PHY. So clear the + * extended capability bit of BMSR. + */ + val &= ~BMSR_EXTCAP; + } + + if (!locked) + AXE_UNLOCK(sc); + return (val); +} + +static int +axe_miibus_writereg(device_t dev, int phy, int reg, int val) +{ + struct axe_softc *sc = device_get_softc(dev); + int locked; + + val = htole32(val); + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + AXE_LOCK(sc); + + axe_cmd(sc, AXE_CMD_MII_OPMODE_SW, 0, 0, NULL); + axe_cmd(sc, AXE_CMD_MII_WRITE_REG, reg, phy, &val); + axe_cmd(sc, AXE_CMD_MII_OPMODE_HW, 0, 0, NULL); + + if (!locked) + AXE_UNLOCK(sc); + return (0); +} + +static void +axe_miibus_statchg(device_t dev) +{ + struct axe_softc *sc = device_get_softc(dev); + struct mii_data *mii = GET_MII(sc); + struct ifnet *ifp; + uint16_t val; + int err, locked; + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + AXE_LOCK(sc); + + ifp = uether_getifp(&sc->sc_ue); + if (mii == NULL || ifp == NULL || + (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + goto done; + + sc->sc_flags &= ~AXE_FLAG_LINK; + if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == + (IFM_ACTIVE | IFM_AVALID)) { + switch (IFM_SUBTYPE(mii->mii_media_active)) { + case IFM_10_T: + case IFM_100_TX: + sc->sc_flags |= AXE_FLAG_LINK; + break; + case IFM_1000_T: + if ((sc->sc_flags & AXE_FLAG_178) == 0) + break; + sc->sc_flags |= AXE_FLAG_LINK; + break; + default: + break; + } + } + + /* Lost link, do nothing. */ + if ((sc->sc_flags & AXE_FLAG_LINK) == 0) + goto done; + + val = 0; + if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { + val |= AXE_MEDIA_FULL_DUPLEX; + if (AXE_IS_178_FAMILY(sc)) { + if ((IFM_OPTIONS(mii->mii_media_active) & + IFM_ETH_TXPAUSE) != 0) + val |= AXE_178_MEDIA_TXFLOW_CONTROL_EN; + if ((IFM_OPTIONS(mii->mii_media_active) & + IFM_ETH_RXPAUSE) != 0) + val |= AXE_178_MEDIA_RXFLOW_CONTROL_EN; + } + } + if (AXE_IS_178_FAMILY(sc)) { + val |= AXE_178_MEDIA_RX_EN | AXE_178_MEDIA_MAGIC; + if ((sc->sc_flags & AXE_FLAG_178) != 0) + val |= AXE_178_MEDIA_ENCK; + switch (IFM_SUBTYPE(mii->mii_media_active)) { + case IFM_1000_T: + val |= AXE_178_MEDIA_GMII | AXE_178_MEDIA_ENCK; + break; + case IFM_100_TX: + val |= AXE_178_MEDIA_100TX; + break; + case IFM_10_T: + /* doesn't need to be handled */ + break; + } + } + err = axe_cmd(sc, AXE_CMD_WRITE_MEDIA, 0, val, NULL); + if (err) + device_printf(dev, "media change failed, error %d\n", err); +done: + if (!locked) + AXE_UNLOCK(sc); +} + +/* + * Set media options. + */ +static int +axe_ifmedia_upd(struct ifnet *ifp) +{ + struct axe_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + struct mii_softc *miisc; + int error; + + AXE_LOCK_ASSERT(sc, MA_OWNED); + + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) + PHY_RESET(miisc); + error = mii_mediachg(mii); + return (error); +} + +/* + * Report current media status. + */ +static void +axe_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct axe_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + + AXE_LOCK(sc); + mii_pollstat(mii); + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; + AXE_UNLOCK(sc); +} + +static void +axe_setmulti(struct usb_ether *ue) +{ + struct axe_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + struct ifmultiaddr *ifma; + uint32_t h = 0; + uint16_t rxmode; + uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + AXE_LOCK_ASSERT(sc, MA_OWNED); + + axe_cmd(sc, AXE_CMD_RXCTL_READ, 0, 0, &rxmode); + rxmode = le16toh(rxmode); + + if (ifp->if_flags & (IFF_ALLMULTI | IFF_PROMISC)) { + rxmode |= AXE_RXCMD_ALLMULTI; + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); + return; + } + rxmode &= ~AXE_RXCMD_ALLMULTI; + + if_maddr_rlock(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) + { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + h = ether_crc32_be(LLADDR((struct sockaddr_dl *) + ifma->ifma_addr), ETHER_ADDR_LEN) >> 26; + hashtbl[h / 8] |= 1 << (h % 8); + } + if_maddr_runlock(ifp); + + axe_cmd(sc, AXE_CMD_WRITE_MCAST, 0, 0, (void *)&hashtbl); + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); +} + +static int +axe_get_phyno(struct axe_softc *sc, int sel) +{ + int phyno; + + switch (AXE_PHY_TYPE(sc->sc_phyaddrs[sel])) { + case PHY_TYPE_100_HOME: + case PHY_TYPE_GIG: + phyno = AXE_PHY_NO(sc->sc_phyaddrs[sel]); + break; + case PHY_TYPE_SPECIAL: + /* FALLTHROUGH */ + case PHY_TYPE_RSVD: + /* FALLTHROUGH */ + case PHY_TYPE_NON_SUP: + /* FALLTHROUGH */ + default: + phyno = -1; + break; + } + + return (phyno); +} + +#define AXE_GPIO_WRITE(x, y) do { \ + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, (x), NULL); \ + uether_pause(ue, (y)); \ +} while (0) + +static void +axe_ax88178_init(struct axe_softc *sc) +{ + struct usb_ether *ue; + int gpio0, ledmode, phymode; + uint16_t eeprom, val; + + ue = &sc->sc_ue; + axe_cmd(sc, AXE_CMD_SROM_WR_ENABLE, 0, 0, NULL); + /* XXX magic */ + axe_cmd(sc, AXE_CMD_SROM_READ, 0, 0x0017, &eeprom); + eeprom = le16toh(eeprom); + axe_cmd(sc, AXE_CMD_SROM_WR_DISABLE, 0, 0, NULL); + + /* if EEPROM is invalid we have to use to GPIO0 */ + if (eeprom == 0xffff) { + phymode = AXE_PHY_MODE_MARVELL; + gpio0 = 1; + ledmode = 0; + } else { + phymode = eeprom & 0x7f; + gpio0 = (eeprom & 0x80) ? 0 : 1; + ledmode = eeprom >> 8; + } + + if (bootverbose) + device_printf(sc->sc_ue.ue_dev, + "EEPROM data : 0x%04x, phymode : 0x%02x\n", eeprom, + phymode); + /* Program GPIOs depending on PHY hardware. */ + switch (phymode) { + case AXE_PHY_MODE_MARVELL: + if (gpio0 == 1) { + AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO0_EN, + hz / 32); + AXE_GPIO_WRITE(AXE_GPIO0_EN | AXE_GPIO2 | AXE_GPIO2_EN, + hz / 32); + AXE_GPIO_WRITE(AXE_GPIO0_EN | AXE_GPIO2_EN, hz / 4); + AXE_GPIO_WRITE(AXE_GPIO0_EN | AXE_GPIO2 | AXE_GPIO2_EN, + hz / 32); + } else { + AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO1 | + AXE_GPIO1_EN, hz / 3); + if (ledmode == 1) { + AXE_GPIO_WRITE(AXE_GPIO1_EN, hz / 3); + AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN, + hz / 3); + } else { + AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | + AXE_GPIO2 | AXE_GPIO2_EN, hz / 32); + AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | + AXE_GPIO2_EN, hz / 4); + AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | + AXE_GPIO2 | AXE_GPIO2_EN, hz / 32); + } + } + break; + case AXE_PHY_MODE_CICADA: + case AXE_PHY_MODE_CICADA_V2: + case AXE_PHY_MODE_CICADA_V2_ASIX: + if (gpio0 == 1) + AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO0 | + AXE_GPIO0_EN, hz / 32); + else + AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO1 | + AXE_GPIO1_EN, hz / 32); + break; + case AXE_PHY_MODE_AGERE: + AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO1 | + AXE_GPIO1_EN, hz / 32); + AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | AXE_GPIO2 | + AXE_GPIO2_EN, hz / 32); + AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | AXE_GPIO2_EN, hz / 4); + AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | AXE_GPIO2 | + AXE_GPIO2_EN, hz / 32); + break; + case AXE_PHY_MODE_REALTEK_8211CL: + case AXE_PHY_MODE_REALTEK_8211BN: + case AXE_PHY_MODE_REALTEK_8251CL: + val = gpio0 == 1 ? AXE_GPIO0 | AXE_GPIO0_EN : + AXE_GPIO1 | AXE_GPIO1_EN; + AXE_GPIO_WRITE(val, hz / 32); + AXE_GPIO_WRITE(val | AXE_GPIO2 | AXE_GPIO2_EN, hz / 32); + AXE_GPIO_WRITE(val | AXE_GPIO2_EN, hz / 4); + AXE_GPIO_WRITE(val | AXE_GPIO2 | AXE_GPIO2_EN, hz / 32); + if (phymode == AXE_PHY_MODE_REALTEK_8211CL) { + axe_miibus_writereg(ue->ue_dev, sc->sc_phyno, + 0x1F, 0x0005); + axe_miibus_writereg(ue->ue_dev, sc->sc_phyno, + 0x0C, 0x0000); + val = axe_miibus_readreg(ue->ue_dev, sc->sc_phyno, + 0x0001); + axe_miibus_writereg(ue->ue_dev, sc->sc_phyno, + 0x01, val | 0x0080); + axe_miibus_writereg(ue->ue_dev, sc->sc_phyno, + 0x1F, 0x0000); + } + break; + default: + /* Unknown PHY model or no need to program GPIOs. */ + break; + } + + /* soft reset */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_CLEAR, NULL); + uether_pause(ue, hz / 4); + + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_PRL | AXE_178_RESET_MAGIC, NULL); + uether_pause(ue, hz / 4); + /* Enable MII/GMII/RGMII interface to work with external PHY. */ + axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0, NULL); + uether_pause(ue, hz / 4); + + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); +} + +static void +axe_ax88772_init(struct axe_softc *sc) +{ + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x00b0, NULL); + uether_pause(&sc->sc_ue, hz / 16); + + if (sc->sc_phyno == AXE_772_PHY_NO_EPHY) { + /* ask for the embedded PHY */ + axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0x01, NULL); + uether_pause(&sc->sc_ue, hz / 64); + + /* power down and reset state, pin reset state */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_CLEAR, NULL); + uether_pause(&sc->sc_ue, hz / 16); + + /* power down/reset state, pin operating state */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_IPPD | AXE_SW_RESET_PRL, NULL); + uether_pause(&sc->sc_ue, hz / 4); + + /* power up, reset */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_PRL, NULL); + + /* power up, operating */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_IPRL | AXE_SW_RESET_PRL, NULL); + } else { + /* ask for external PHY */ + axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0x00, NULL); + uether_pause(&sc->sc_ue, hz / 64); + + /* power down internal PHY */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_IPPD | AXE_SW_RESET_PRL, NULL); + } + + uether_pause(&sc->sc_ue, hz / 4); + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); +} + +static void +axe_ax88772_phywake(struct axe_softc *sc) +{ + struct usb_ether *ue; + + ue = &sc->sc_ue; + if (sc->sc_phyno == AXE_772_PHY_NO_EPHY) { + /* Manually select internal(embedded) PHY - MAC mode. */ + axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, AXE_SW_PHY_SELECT_SS_ENB | + AXE_SW_PHY_SELECT_EMBEDDED | AXE_SW_PHY_SELECT_SS_MII, + NULL); + uether_pause(&sc->sc_ue, hz / 32); + } else { + /* + * Manually select external PHY - MAC mode. + * Reverse MII/RMII is for AX88772A PHY mode. + */ + axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, AXE_SW_PHY_SELECT_SS_ENB | + AXE_SW_PHY_SELECT_EXT | AXE_SW_PHY_SELECT_SS_MII, NULL); + uether_pause(&sc->sc_ue, hz / 32); + } + /* Take PHY out of power down. */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_IPPD | + AXE_SW_RESET_IPRL, NULL); + uether_pause(&sc->sc_ue, hz / 4); + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_IPRL, NULL); + uether_pause(&sc->sc_ue, hz); + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_CLEAR, NULL); + uether_pause(&sc->sc_ue, hz / 32); + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_IPRL, NULL); + uether_pause(&sc->sc_ue, hz / 32); +} + +static void +axe_ax88772a_init(struct axe_softc *sc) +{ + struct usb_ether *ue; + + ue = &sc->sc_ue; + /* Reload EEPROM. */ + AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM, hz / 32); + axe_ax88772_phywake(sc); + /* Stop MAC. */ + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); +} + +static void +axe_ax88772b_init(struct axe_softc *sc) +{ + struct usb_ether *ue; + uint16_t eeprom; + uint8_t *eaddr; + int i; + + ue = &sc->sc_ue; + /* Reload EEPROM. */ + AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM, hz / 32); + /* + * Save PHY power saving configuration(high byte) and + * clear EEPROM checksum value(low byte). + */ + axe_cmd(sc, AXE_CMD_SROM_READ, 0, AXE_EEPROM_772B_PHY_PWRCFG, &eeprom); + sc->sc_pwrcfg = le16toh(eeprom) & 0xFF00; + + /* + * Auto-loaded default station address from internal ROM is + * 00:00:00:00:00:00 such that an explicit access to EEPROM + * is required to get real station address. + */ + eaddr = ue->ue_eaddr; + for (i = 0; i < ETHER_ADDR_LEN / 2; i++) { + axe_cmd(sc, AXE_CMD_SROM_READ, 0, AXE_EEPROM_772B_NODE_ID + i, + &eeprom); + eeprom = le16toh(eeprom); + *eaddr++ = (uint8_t)(eeprom & 0xFF); + *eaddr++ = (uint8_t)((eeprom >> 8) & 0xFF); + } + /* Wakeup PHY. */ + axe_ax88772_phywake(sc); + /* Stop MAC. */ + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); +} + +#undef AXE_GPIO_WRITE + +static void +axe_reset(struct axe_softc *sc) +{ + struct usb_config_descriptor *cd; + usb_error_t err; + + cd = usbd_get_config_descriptor(sc->sc_ue.ue_udev); + + err = usbd_req_set_config(sc->sc_ue.ue_udev, &sc->sc_mtx, + cd->bConfigurationValue); + if (err) + DPRINTF("reset failed (ignored)\n"); + + /* Wait a little while for the chip to get its brains in order. */ + uether_pause(&sc->sc_ue, hz / 100); + + /* Reinitialize controller to achieve full reset. */ + if (sc->sc_flags & AXE_FLAG_178) + axe_ax88178_init(sc); + else if (sc->sc_flags & AXE_FLAG_772) + axe_ax88772_init(sc); + else if (sc->sc_flags & AXE_FLAG_772A) + axe_ax88772a_init(sc); + else if (sc->sc_flags & AXE_FLAG_772B) + axe_ax88772b_init(sc); +} + +static void +axe_attach_post(struct usb_ether *ue) +{ + struct axe_softc *sc = uether_getsc(ue); + + /* + * Load PHY indexes first. Needed by axe_xxx_init(). + */ + axe_cmd(sc, AXE_CMD_READ_PHYID, 0, 0, sc->sc_phyaddrs); + if (bootverbose) + device_printf(sc->sc_ue.ue_dev, "PHYADDR 0x%02x:0x%02x\n", + sc->sc_phyaddrs[0], sc->sc_phyaddrs[1]); + sc->sc_phyno = axe_get_phyno(sc, AXE_PHY_SEL_PRI); + if (sc->sc_phyno == -1) + sc->sc_phyno = axe_get_phyno(sc, AXE_PHY_SEL_SEC); + if (sc->sc_phyno == -1) { + device_printf(sc->sc_ue.ue_dev, + "no valid PHY address found, assuming PHY address 0\n"); + sc->sc_phyno = 0; + } + + /* Initialize controller and get station address. */ + if (sc->sc_flags & AXE_FLAG_178) { + axe_ax88178_init(sc); + sc->sc_tx_bufsz = 16 * 1024; + axe_cmd(sc, AXE_178_CMD_READ_NODEID, 0, 0, ue->ue_eaddr); + } else if (sc->sc_flags & AXE_FLAG_772) { + axe_ax88772_init(sc); + sc->sc_tx_bufsz = 8 * 1024; + axe_cmd(sc, AXE_178_CMD_READ_NODEID, 0, 0, ue->ue_eaddr); + } else if (sc->sc_flags & AXE_FLAG_772A) { + axe_ax88772a_init(sc); + sc->sc_tx_bufsz = 8 * 1024; + axe_cmd(sc, AXE_178_CMD_READ_NODEID, 0, 0, ue->ue_eaddr); + } else if (sc->sc_flags & AXE_FLAG_772B) { + axe_ax88772b_init(sc); + sc->sc_tx_bufsz = 8 * 1024; + } else + axe_cmd(sc, AXE_172_CMD_READ_NODEID, 0, 0, ue->ue_eaddr); + + /* + * Fetch IPG values. + */ + if (sc->sc_flags & (AXE_FLAG_772A | AXE_FLAG_772B)) { + /* Set IPG values. */ + sc->sc_ipgs[0] = 0x15; + sc->sc_ipgs[1] = 0x16; + sc->sc_ipgs[2] = 0x1A; + } else + axe_cmd(sc, AXE_CMD_READ_IPG012, 0, 0, sc->sc_ipgs); +} + +static int +axe_attach_post_sub(struct usb_ether *ue) +{ + struct axe_softc *sc; + struct ifnet *ifp; + u_int adv_pause; + int error; + + sc = uether_getsc(ue); + ifp = ue->ue_ifp; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_start = uether_start; + ifp->if_ioctl = axe_ioctl; + ifp->if_init = uether_init; + IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); + ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; + IFQ_SET_READY(&ifp->if_snd); + + if (AXE_IS_178_FAMILY(sc)) + ifp->if_capabilities |= IFCAP_VLAN_MTU; + if (sc->sc_flags & AXE_FLAG_772B) { + ifp->if_capabilities |= IFCAP_TXCSUM | IFCAP_RXCSUM; + ifp->if_hwassist = AXE_CSUM_FEATURES; + /* + * Checksum offloading of AX88772B also works with VLAN + * tagged frames but there is no way to take advantage + * of the feature because vlan(4) assumes + * IFCAP_VLAN_HWTAGGING is prerequisite condition to + * support checksum offloading with VLAN. VLAN hardware + * tagging support of AX88772B is very limited so it's + * not possible to announce IFCAP_VLAN_HWTAGGING. + */ + } + ifp->if_capenable = ifp->if_capabilities; + if (sc->sc_flags & (AXE_FLAG_772A | AXE_FLAG_772B | AXE_FLAG_178)) + adv_pause = MIIF_DOPAUSE; + else + adv_pause = 0; + mtx_lock(&Giant); + error = mii_attach(ue->ue_dev, &ue->ue_miibus, ifp, + uether_ifmedia_upd, ue->ue_methods->ue_mii_sts, + BMSR_DEFCAPMASK, sc->sc_phyno, MII_OFFSET_ANY, adv_pause); + mtx_unlock(&Giant); + + return (error); +} + +/* + * Probe for a AX88172 chip. + */ +static int +axe_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != AXE_CONFIG_IDX) + return (ENXIO); + if (uaa->info.bIfaceIndex != AXE_IFACE_IDX) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(axe_devs, sizeof(axe_devs), uaa)); +} + +/* + * Attach the interface. Allocate softc structures, do ifmedia + * setup and ethernet/BPF attach. + */ +static int +axe_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct axe_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + uint8_t iface_index; + int error; + + sc->sc_flags = USB_GET_DRIVER_INFO(uaa); + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + iface_index = AXE_IFACE_IDX; + error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, + axe_config, AXE_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB transfers failed\n"); + goto detach; + } + + ue->ue_sc = sc; + ue->ue_dev = dev; + ue->ue_udev = uaa->device; + ue->ue_mtx = &sc->sc_mtx; + ue->ue_methods = &axe_ue_methods; + + error = uether_ifattach(ue); + if (error) { + device_printf(dev, "could not attach interface\n"); + goto detach; + } + return (0); /* success */ + +detach: + axe_detach(dev); + return (ENXIO); /* failure */ +} + +static int +axe_detach(device_t dev) +{ + struct axe_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + + usbd_transfer_unsetup(sc->sc_xfer, AXE_N_TRANSFER); + uether_ifdetach(ue); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +#if (AXE_BULK_BUF_SIZE >= 0x10000) +#error "Please update axe_bulk_read_callback()!" +#endif + +static void +axe_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct axe_softc *sc = usbd_xfer_softc(xfer); + struct usb_ether *ue = &sc->sc_ue; + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + axe_rx_frame(ue, pc, actlen); + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + uether_rxflush(ue); + return; + + default: /* Error */ + DPRINTF("bulk read error, %s\n", usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static int +axe_rx_frame(struct usb_ether *ue, struct usb_page_cache *pc, int actlen) +{ + struct axe_softc *sc; + struct axe_sframe_hdr hdr; + struct axe_csum_hdr csum_hdr; + int error, len, pos; + + sc = uether_getsc(ue); + pos = 0; + len = 0; + error = 0; + if ((sc->sc_flags & AXE_FLAG_STD_FRAME) != 0) { + while (pos < actlen) { + if ((pos + sizeof(hdr)) > actlen) { + /* too little data */ + error = EINVAL; + break; + } + usbd_copy_out(pc, pos, &hdr, sizeof(hdr)); + + if ((hdr.len ^ hdr.ilen) != sc->sc_lenmask) { + /* we lost sync */ + error = EINVAL; + break; + } + pos += sizeof(hdr); + len = le16toh(hdr.len); + if (pos + len > actlen) { + /* invalid length */ + error = EINVAL; + break; + } + axe_rxeof(ue, pc, pos, len, NULL); + pos += len + (len % 2); + } + } else if ((sc->sc_flags & AXE_FLAG_CSUM_FRAME) != 0) { + while (pos < actlen) { + if ((pos + sizeof(csum_hdr)) > actlen) { + /* too little data */ + error = EINVAL; + break; + } + usbd_copy_out(pc, pos, &csum_hdr, sizeof(csum_hdr)); + + csum_hdr.len = le16toh(csum_hdr.len); + csum_hdr.ilen = le16toh(csum_hdr.ilen); + csum_hdr.cstatus = le16toh(csum_hdr.cstatus); + if ((AXE_CSUM_RXBYTES(csum_hdr.len) ^ + AXE_CSUM_RXBYTES(csum_hdr.ilen)) != + sc->sc_lenmask) { + /* we lost sync */ + error = EINVAL; + break; + } + /* + * Get total transferred frame length including + * checksum header. The length should be multiple + * of 4. + */ + len = sizeof(csum_hdr) + AXE_CSUM_RXBYTES(csum_hdr.len); + len = (len + 3) & ~3; + if (pos + len > actlen) { + /* invalid length */ + error = EINVAL; + break; + } + axe_rxeof(ue, pc, pos + sizeof(csum_hdr), + AXE_CSUM_RXBYTES(csum_hdr.len), &csum_hdr); + pos += len; + } + } else + axe_rxeof(ue, pc, 0, actlen, NULL); + + if (error != 0) + ue->ue_ifp->if_ierrors++; + return (error); +} + +static int +axe_rxeof(struct usb_ether *ue, struct usb_page_cache *pc, unsigned int offset, + unsigned int len, struct axe_csum_hdr *csum_hdr) +{ + struct ifnet *ifp = ue->ue_ifp; + struct mbuf *m; + + if (len < ETHER_HDR_LEN || len > MCLBYTES - ETHER_ALIGN) { + ifp->if_ierrors++; + return (EINVAL); + } + + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (m == NULL) { + ifp->if_iqdrops++; + return (ENOMEM); + } + m->m_len = m->m_pkthdr.len = MCLBYTES; + m_adj(m, ETHER_ALIGN); + + usbd_copy_out(pc, offset, mtod(m, uint8_t *), len); + + ifp->if_ipackets++; + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = len; + + if (csum_hdr != NULL && csum_hdr->cstatus & AXE_CSUM_HDR_L3_TYPE_IPV4) { + if ((csum_hdr->cstatus & (AXE_CSUM_HDR_L4_CSUM_ERR | + AXE_CSUM_HDR_L3_CSUM_ERR)) == 0) { + m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED | + CSUM_IP_VALID; + if ((csum_hdr->cstatus & AXE_CSUM_HDR_L4_TYPE_MASK) == + AXE_CSUM_HDR_L4_TYPE_TCP || + (csum_hdr->cstatus & AXE_CSUM_HDR_L4_TYPE_MASK) == + AXE_CSUM_HDR_L4_TYPE_UDP) { + m->m_pkthdr.csum_flags |= + CSUM_DATA_VALID | CSUM_PSEUDO_HDR; + m->m_pkthdr.csum_data = 0xffff; + } + } + } + + _IF_ENQUEUE(&ue->ue_rxq, m); + return (0); +} + +#if ((AXE_BULK_BUF_SIZE >= 0x10000) || (AXE_BULK_BUF_SIZE < (MCLBYTES+4))) +#error "Please update axe_bulk_write_callback()!" +#endif + +static void +axe_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct axe_softc *sc = usbd_xfer_softc(xfer); + struct axe_sframe_hdr hdr; + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct usb_page_cache *pc; + struct mbuf *m; + int nframes, pos; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + if ((sc->sc_flags & AXE_FLAG_LINK) == 0 || + (ifp->if_drv_flags & IFF_DRV_OACTIVE) != 0) { + /* + * Don't send anything if there is no link or + * controller is busy. + */ + return; + } + + for (nframes = 0; nframes < 16 && + !IFQ_DRV_IS_EMPTY(&ifp->if_snd); nframes++) { + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + usbd_xfer_set_frame_offset(xfer, nframes * MCLBYTES, + nframes); + pos = 0; + pc = usbd_xfer_get_frame(xfer, nframes); + if (AXE_IS_178_FAMILY(sc)) { + hdr.len = htole16(m->m_pkthdr.len); + hdr.ilen = ~hdr.len; + /* + * If upper stack computed checksum, driver + * should tell controller not to insert + * computed checksum for checksum offloading + * enabled controller. + */ + if (ifp->if_capabilities & IFCAP_TXCSUM) { + if ((m->m_pkthdr.csum_flags & + AXE_CSUM_FEATURES) != 0) + hdr.len |= htole16( + AXE_TX_CSUM_PSEUDO_HDR); + else + hdr.len |= htole16( + AXE_TX_CSUM_DIS); + } + usbd_copy_in(pc, pos, &hdr, sizeof(hdr)); + pos += sizeof(hdr); + usbd_m_copy_in(pc, pos, m, 0, m->m_pkthdr.len); + pos += m->m_pkthdr.len; + if ((pos % 512) == 0) { + hdr.len = 0; + hdr.ilen = 0xffff; + usbd_copy_in(pc, pos, &hdr, + sizeof(hdr)); + pos += sizeof(hdr); + } + } else { + usbd_m_copy_in(pc, pos, m, 0, m->m_pkthdr.len); + pos += m->m_pkthdr.len; + } + + /* + * XXX + * Update TX packet counter here. This is not + * correct way but it seems that there is no way + * to know how many packets are sent at the end + * of transfer because controller combines + * multiple writes into single one if there is + * room in TX buffer of controller. + */ + ifp->if_opackets++; + + /* + * if there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + + m_freem(m); + + /* Set frame length. */ + usbd_xfer_set_frame_len(xfer, nframes, pos); + } + if (nframes != 0) { + usbd_xfer_set_frames(xfer, nframes); + usbd_transfer_submit(xfer); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + } + return; + /* NOTREACHED */ + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usbd_errstr(error)); + + ifp->if_oerrors++; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +axe_tick(struct usb_ether *ue) +{ + struct axe_softc *sc = uether_getsc(ue); + struct mii_data *mii = GET_MII(sc); + + AXE_LOCK_ASSERT(sc, MA_OWNED); + + mii_tick(mii); + if ((sc->sc_flags & AXE_FLAG_LINK) == 0) { + axe_miibus_statchg(ue->ue_dev); + if ((sc->sc_flags & AXE_FLAG_LINK) != 0) + axe_start(ue); + } +} + +static void +axe_start(struct usb_ether *ue) +{ + struct axe_softc *sc = uether_getsc(ue); + + /* + * start the USB transfers, if not already started: + */ + usbd_transfer_start(sc->sc_xfer[AXE_BULK_DT_RD]); + usbd_transfer_start(sc->sc_xfer[AXE_BULK_DT_WR]); +} + +static void +axe_csum_cfg(struct usb_ether *ue) +{ + struct axe_softc *sc; + struct ifnet *ifp; + uint16_t csum1, csum2; + + sc = uether_getsc(ue); + AXE_LOCK_ASSERT(sc, MA_OWNED); + + if ((sc->sc_flags & AXE_FLAG_772B) != 0) { + ifp = uether_getifp(ue); + csum1 = 0; + csum2 = 0; + if ((ifp->if_capenable & IFCAP_TXCSUM) != 0) + csum1 |= AXE_TXCSUM_IP | AXE_TXCSUM_TCP | + AXE_TXCSUM_UDP; + axe_cmd(sc, AXE_772B_CMD_WRITE_TXCSUM, csum2, csum1, NULL); + csum1 = 0; + csum2 = 0; + if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) + csum1 |= AXE_RXCSUM_IP | AXE_RXCSUM_IPVE | + AXE_RXCSUM_TCP | AXE_RXCSUM_UDP | AXE_RXCSUM_ICMP | + AXE_RXCSUM_IGMP; + axe_cmd(sc, AXE_772B_CMD_WRITE_RXCSUM, csum2, csum1, NULL); + } +} + +static void +axe_init(struct usb_ether *ue) +{ + struct axe_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + uint16_t rxmode; + + AXE_LOCK_ASSERT(sc, MA_OWNED); + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) + return; + + /* Cancel pending I/O */ + axe_stop(ue); + + axe_reset(sc); + + /* Set MAC address and transmitter IPG values. */ + if (AXE_IS_178_FAMILY(sc)) { + axe_cmd(sc, AXE_178_CMD_WRITE_NODEID, 0, 0, IF_LLADDR(ifp)); + axe_cmd(sc, AXE_178_CMD_WRITE_IPG012, sc->sc_ipgs[2], + (sc->sc_ipgs[1] << 8) | (sc->sc_ipgs[0]), NULL); + } else { + axe_cmd(sc, AXE_172_CMD_WRITE_NODEID, 0, 0, IF_LLADDR(ifp)); + axe_cmd(sc, AXE_172_CMD_WRITE_IPG0, 0, sc->sc_ipgs[0], NULL); + axe_cmd(sc, AXE_172_CMD_WRITE_IPG1, 0, sc->sc_ipgs[1], NULL); + axe_cmd(sc, AXE_172_CMD_WRITE_IPG2, 0, sc->sc_ipgs[2], NULL); + } + + if (AXE_IS_178_FAMILY(sc)) { + sc->sc_flags &= ~(AXE_FLAG_STD_FRAME | AXE_FLAG_CSUM_FRAME); + if ((sc->sc_flags & AXE_FLAG_772B) != 0) + sc->sc_lenmask = AXE_CSUM_HDR_LEN_MASK; + else + sc->sc_lenmask = AXE_HDR_LEN_MASK; + if ((sc->sc_flags & AXE_FLAG_772B) != 0 && + (ifp->if_capenable & IFCAP_RXCSUM) != 0) + sc->sc_flags |= AXE_FLAG_CSUM_FRAME; + else + sc->sc_flags |= AXE_FLAG_STD_FRAME; + } + + /* Configure TX/RX checksum offloading. */ + axe_csum_cfg(ue); + + if (sc->sc_flags & AXE_FLAG_772B) { + /* AX88772B uses different maximum frame burst configuration. */ + axe_cmd(sc, AXE_772B_CMD_RXCTL_WRITE_CFG, + ax88772b_mfb_table[AX88772B_MFB_16K].threshold, + ax88772b_mfb_table[AX88772B_MFB_16K].byte_cnt, NULL); + } + + /* Enable receiver, set RX mode. */ + rxmode = (AXE_RXCMD_MULTICAST | AXE_RXCMD_ENABLE); + if (AXE_IS_178_FAMILY(sc)) { + if (sc->sc_flags & AXE_FLAG_772B) { + /* + * Select RX header format type 1. Aligning IP + * header on 4 byte boundary is not needed when + * checksum offloading feature is not used + * because we always copy the received frame in + * RX handler. When RX checksum offloading is + * active, aligning IP header is required to + * reflect actual frame length including RX + * header size. + */ + rxmode |= AXE_772B_RXCMD_HDR_TYPE_1; + if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) + rxmode |= AXE_772B_RXCMD_IPHDR_ALIGN; + } else { + /* + * Default Rx buffer size is too small to get + * maximum performance. + */ + rxmode |= AXE_178_RXCMD_MFB_16384; + } + } else { + rxmode |= AXE_172_RXCMD_UNICAST; + } + + /* If we want promiscuous mode, set the allframes bit. */ + if (ifp->if_flags & IFF_PROMISC) + rxmode |= AXE_RXCMD_PROMISC; + + if (ifp->if_flags & IFF_BROADCAST) + rxmode |= AXE_RXCMD_BROADCAST; + + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); + + /* Load the multicast filter. */ + axe_setmulti(ue); + + usbd_xfer_set_stall(sc->sc_xfer[AXE_BULK_DT_WR]); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + /* Switch to selected media. */ + axe_ifmedia_upd(ifp); +} + +static void +axe_setpromisc(struct usb_ether *ue) +{ + struct axe_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + uint16_t rxmode; + + axe_cmd(sc, AXE_CMD_RXCTL_READ, 0, 0, &rxmode); + + rxmode = le16toh(rxmode); + + if (ifp->if_flags & IFF_PROMISC) { + rxmode |= AXE_RXCMD_PROMISC; + } else { + rxmode &= ~AXE_RXCMD_PROMISC; + } + + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); + + axe_setmulti(ue); +} + +static void +axe_stop(struct usb_ether *ue) +{ + struct axe_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + AXE_LOCK_ASSERT(sc, MA_OWNED); + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + sc->sc_flags &= ~AXE_FLAG_LINK; + + /* + * stop all the transfers, if not already stopped: + */ + usbd_transfer_stop(sc->sc_xfer[AXE_BULK_DT_WR]); + usbd_transfer_stop(sc->sc_xfer[AXE_BULK_DT_RD]); +} + +static int +axe_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct usb_ether *ue = ifp->if_softc; + struct axe_softc *sc; + struct ifreq *ifr; + int error, mask, reinit; + + sc = uether_getsc(ue); + ifr = (struct ifreq *)data; + error = 0; + reinit = 0; + if (cmd == SIOCSIFCAP) { + AXE_LOCK(sc); + mask = ifr->ifr_reqcap ^ ifp->if_capenable; + if ((mask & IFCAP_TXCSUM) != 0 && + (ifp->if_capabilities & IFCAP_TXCSUM) != 0) { + ifp->if_capenable ^= IFCAP_TXCSUM; + if ((ifp->if_capenable & IFCAP_TXCSUM) != 0) + ifp->if_hwassist |= AXE_CSUM_FEATURES; + else + ifp->if_hwassist &= ~AXE_CSUM_FEATURES; + reinit++; + } + if ((mask & IFCAP_RXCSUM) != 0 && + (ifp->if_capabilities & IFCAP_RXCSUM) != 0) { + ifp->if_capenable ^= IFCAP_RXCSUM; + reinit++; + } + if (reinit > 0 && ifp->if_drv_flags & IFF_DRV_RUNNING) + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + else + reinit = 0; + AXE_UNLOCK(sc); + if (reinit > 0) + uether_init(ue); + } else + error = uether_ioctl(ifp, cmd, data); + + return (error); +} diff --git a/sys/bus/u4b/net/if_axereg.h b/sys/bus/u4b/net/if_axereg.h new file mode 100644 index 0000000000..ab2b28d24f --- /dev/null +++ b/sys/bus/u4b/net/if_axereg.h @@ -0,0 +1,364 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000-2003 + * Bill Paul . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Definitions for the ASIX Electronics AX88172, AX88178 + * and AX88772 to ethernet controllers. + */ + +/* + * Vendor specific commands. ASIX conveniently doesn't document the 'set + * NODEID' command in their datasheet (thanks a lot guys). + * To make handling these commands easier, I added some extra data which is + * decided by the axe_cmd() routine. Commands are encoded in 16 bits, with + * the format: LDCC. L and D are both nibbles in the high byte. L represents + * the data length (0 to 15) and D represents the direction (0 for vendor read, + * 1 for vendor write). CC is the command byte, as specified in the manual. + */ + +#define AXE_CMD_IS_WRITE(x) (((x) & 0x0F00) >> 8) +#define AXE_CMD_LEN(x) (((x) & 0xF000) >> 12) +#define AXE_CMD_CMD(x) ((x) & 0x00FF) + +#define AXE_172_CMD_READ_RXTX_SRAM 0x2002 +#define AXE_182_CMD_READ_RXTX_SRAM 0x8002 +#define AXE_172_CMD_WRITE_RX_SRAM 0x0103 +#define AXE_182_CMD_WRITE_RXTX_SRAM 0x8103 +#define AXE_172_CMD_WRITE_TX_SRAM 0x0104 +#define AXE_CMD_MII_OPMODE_SW 0x0106 +#define AXE_CMD_MII_READ_REG 0x2007 +#define AXE_CMD_MII_WRITE_REG 0x2108 +#define AXE_CMD_MII_READ_OPMODE 0x1009 +#define AXE_CMD_MII_OPMODE_HW 0x010A +#define AXE_CMD_SROM_READ 0x200B +#define AXE_CMD_SROM_WRITE 0x010C +#define AXE_CMD_SROM_WR_ENABLE 0x010D +#define AXE_CMD_SROM_WR_DISABLE 0x010E +#define AXE_CMD_RXCTL_READ 0x200F +#define AXE_CMD_RXCTL_WRITE 0x0110 +#define AXE_CMD_READ_IPG012 0x3011 +#define AXE_172_CMD_WRITE_IPG0 0x0112 +#define AXE_178_CMD_WRITE_IPG012 0x0112 +#define AXE_172_CMD_WRITE_IPG1 0x0113 +#define AXE_178_CMD_READ_NODEID 0x6013 +#define AXE_172_CMD_WRITE_IPG2 0x0114 +#define AXE_178_CMD_WRITE_NODEID 0x6114 +#define AXE_CMD_READ_MCAST 0x8015 +#define AXE_CMD_WRITE_MCAST 0x8116 +#define AXE_172_CMD_READ_NODEID 0x6017 +#define AXE_172_CMD_WRITE_NODEID 0x6118 + +#define AXE_CMD_READ_PHYID 0x2019 +#define AXE_172_CMD_READ_MEDIA 0x101A +#define AXE_178_CMD_READ_MEDIA 0x201A +#define AXE_CMD_WRITE_MEDIA 0x011B +#define AXE_CMD_READ_MONITOR_MODE 0x101C +#define AXE_CMD_WRITE_MONITOR_MODE 0x011D +#define AXE_CMD_READ_GPIO 0x101E +#define AXE_CMD_WRITE_GPIO 0x011F + +#define AXE_CMD_SW_RESET_REG 0x0120 +#define AXE_CMD_SW_PHY_STATUS 0x0021 +#define AXE_CMD_SW_PHY_SELECT 0x0122 + +/* AX88772A and AX88772B only. */ +#define AXE_CMD_READ_VLAN_CTRL 0x4027 +#define AXE_CMD_WRITE_VLAN_CTRL 0x4028 + +#define AXE_772B_CMD_RXCTL_WRITE_CFG 0x012A +#define AXE_772B_CMD_READ_RXCSUM 0x002B +#define AXE_772B_CMD_WRITE_RXCSUM 0x012C +#define AXE_772B_CMD_READ_TXCSUM 0x002D +#define AXE_772B_CMD_WRITE_TXCSUM 0x012E + +#define AXE_SW_RESET_CLEAR 0x00 +#define AXE_SW_RESET_RR 0x01 +#define AXE_SW_RESET_RT 0x02 +#define AXE_SW_RESET_PRTE 0x04 +#define AXE_SW_RESET_PRL 0x08 +#define AXE_SW_RESET_BZ 0x10 +#define AXE_SW_RESET_IPRL 0x20 +#define AXE_SW_RESET_IPPD 0x40 + +/* AX88178 documentation says to always write this bit... */ +#define AXE_178_RESET_MAGIC 0x40 + +#define AXE_178_MEDIA_GMII 0x0001 +#define AXE_MEDIA_FULL_DUPLEX 0x0002 +#define AXE_172_MEDIA_TX_ABORT_ALLOW 0x0004 + +/* AX88178/88772 documentation says to always write 1 to bit 2 */ +#define AXE_178_MEDIA_MAGIC 0x0004 +/* AX88772 documentation says to always write 0 to bit 3 */ +#define AXE_178_MEDIA_ENCK 0x0008 +#define AXE_172_MEDIA_FLOW_CONTROL_EN 0x0010 +#define AXE_178_MEDIA_RXFLOW_CONTROL_EN 0x0010 +#define AXE_178_MEDIA_TXFLOW_CONTROL_EN 0x0020 +#define AXE_178_MEDIA_JUMBO_EN 0x0040 +#define AXE_178_MEDIA_LTPF_ONLY 0x0080 +#define AXE_178_MEDIA_RX_EN 0x0100 +#define AXE_178_MEDIA_100TX 0x0200 +#define AXE_178_MEDIA_SBP 0x0800 +#define AXE_178_MEDIA_SUPERMAC 0x1000 + +#define AXE_RXCMD_PROMISC 0x0001 +#define AXE_RXCMD_ALLMULTI 0x0002 +#define AXE_172_RXCMD_UNICAST 0x0004 +#define AXE_178_RXCMD_KEEP_INVALID_CRC 0x0004 +#define AXE_RXCMD_BROADCAST 0x0008 +#define AXE_RXCMD_MULTICAST 0x0010 +#define AXE_RXCMD_ACCEPT_RUNT 0x0040 /* AX88772B */ +#define AXE_RXCMD_ENABLE 0x0080 +#define AXE_178_RXCMD_MFB_MASK 0x0300 +#define AXE_178_RXCMD_MFB_2048 0x0000 +#define AXE_178_RXCMD_MFB_4096 0x0100 +#define AXE_178_RXCMD_MFB_8192 0x0200 +#define AXE_178_RXCMD_MFB_16384 0x0300 +#define AXE_772B_RXCMD_HDR_TYPE_0 0x0000 +#define AXE_772B_RXCMD_HDR_TYPE_1 0x0100 +#define AXE_772B_RXCMD_IPHDR_ALIGN 0x0200 +#define AXE_772B_RXCMD_ADD_CHKSUM 0x0400 +#define AXE_RXCMD_LOOPBACK 0x1000 /* AX88772A/AX88772B */ + +#define AXE_PHY_SEL_PRI 1 +#define AXE_PHY_SEL_SEC 0 +#define AXE_PHY_TYPE_MASK 0xE0 +#define AXE_PHY_TYPE_SHIFT 5 +#define AXE_PHY_TYPE(x) \ + (((x) & AXE_PHY_TYPE_MASK) >> AXE_PHY_TYPE_SHIFT) + +#define PHY_TYPE_100_HOME 0 /* 10/100 or 1M HOME PHY */ +#define PHY_TYPE_GIG 1 /* Gigabit PHY */ +#define PHY_TYPE_SPECIAL 4 /* Special case */ +#define PHY_TYPE_RSVD 5 /* Reserved */ +#define PHY_TYPE_NON_SUP 7 /* Non-supported PHY */ + +#define AXE_PHY_NO_MASK 0x1F +#define AXE_PHY_NO(x) ((x) & AXE_PHY_NO_MASK) + +#define AXE_772_PHY_NO_EPHY 0x10 /* Embedded 10/100 PHY of AX88772 */ + +#define AXE_GPIO0_EN 0x01 +#define AXE_GPIO0 0x02 +#define AXE_GPIO1_EN 0x04 +#define AXE_GPIO1 0x08 +#define AXE_GPIO2_EN 0x10 +#define AXE_GPIO2 0x20 +#define AXE_GPIO_RELOAD_EEPROM 0x80 + +#define AXE_PHY_MODE_MARVELL 0x00 +#define AXE_PHY_MODE_CICADA 0x01 +#define AXE_PHY_MODE_AGERE 0x02 +#define AXE_PHY_MODE_CICADA_V2 0x05 +#define AXE_PHY_MODE_AGERE_GMII 0x06 +#define AXE_PHY_MODE_CICADA_V2_ASIX 0x09 +#define AXE_PHY_MODE_REALTEK_8211CL 0x0C +#define AXE_PHY_MODE_REALTEK_8211BN 0x0D +#define AXE_PHY_MODE_REALTEK_8251CL 0x0E +#define AXE_PHY_MODE_ATTANSIC 0x40 + +/* AX88772A/AX88772B only. */ +#define AXE_SW_PHY_SELECT_EXT 0x0000 +#define AXE_SW_PHY_SELECT_EMBEDDED 0x0001 +#define AXE_SW_PHY_SELECT_AUTO 0x0002 +#define AXE_SW_PHY_SELECT_SS_MII 0x0004 +#define AXE_SW_PHY_SELECT_SS_RVRS_MII 0x0008 +#define AXE_SW_PHY_SELECT_SS_RVRS_RMII 0x000C +#define AXE_SW_PHY_SELECT_SS_ENB 0x0010 + +/* AX88772A/AX88772B VLAN control. */ +#define AXE_VLAN_CTRL_ENB 0x00001000 +#define AXE_VLAN_CTRL_STRIP 0x00002000 +#define AXE_VLAN_CTRL_VID1_MASK 0x00000FFF +#define AXE_VLAN_CTRL_VID2_MASK 0x0FFF0000 + +#define AXE_RXCSUM_IP 0x0001 +#define AXE_RXCSUM_IPVE 0x0002 +#define AXE_RXCSUM_IPV6E 0x0004 +#define AXE_RXCSUM_TCP 0x0008 +#define AXE_RXCSUM_UDP 0x0010 +#define AXE_RXCSUM_ICMP 0x0020 +#define AXE_RXCSUM_IGMP 0x0040 +#define AXE_RXCSUM_ICMP6 0x0080 +#define AXE_RXCSUM_TCPV6 0x0100 +#define AXE_RXCSUM_UDPV6 0x0200 +#define AXE_RXCSUM_ICMPV6 0x0400 +#define AXE_RXCSUM_IGMPV6 0x0800 +#define AXE_RXCSUM_ICMP6V6 0x1000 +#define AXE_RXCSUM_FOPC 0x8000 + +#define AXE_RXCSUM_64TE 0x0100 +#define AXE_RXCSUM_PPPOE 0x0200 +#define AXE_RXCSUM_RPCE 0x8000 + +#define AXE_TXCSUM_IP 0x0001 +#define AXE_TXCSUM_TCP 0x0002 +#define AXE_TXCSUM_UDP 0x0004 +#define AXE_TXCSUM_ICMP 0x0008 +#define AXE_TXCSUM_IGMP 0x0010 +#define AXE_TXCSUM_ICMP6 0x0020 +#define AXE_TXCSUM_TCPV6 0x0100 +#define AXE_TXCSUM_UDPV6 0x0200 +#define AXE_TXCSUM_ICMPV6 0x0400 +#define AXE_TXCSUM_IGMPV6 0x0800 +#define AXE_TXCSUM_ICMP6V6 0x1000 + +#define AXE_TXCSUM_64TE 0x0001 +#define AXE_TXCSUM_PPPOE 0x0002 + +#define AXE_BULK_BUF_SIZE 16384 /* bytes */ + +#define AXE_CTL_READ 0x01 +#define AXE_CTL_WRITE 0x02 + +#define AXE_CONFIG_IDX 0 /* config number 1 */ +#define AXE_IFACE_IDX 0 + +/* EEPROM Map. */ +#define AXE_EEPROM_772B_NODE_ID 0x04 +#define AXE_EEPROM_772B_PHY_PWRCFG 0x18 + +struct ax88772b_mfb { + int byte_cnt; + int threshold; + int size; +}; +#define AX88772B_MFB_2K 0 +#define AX88772B_MFB_4K 1 +#define AX88772B_MFB_6K 2 +#define AX88772B_MFB_8K 3 +#define AX88772B_MFB_16K 4 +#define AX88772B_MFB_20K 5 +#define AX88772B_MFB_24K 6 +#define AX88772B_MFB_32K 7 + +struct axe_sframe_hdr { + uint16_t len; +#define AXE_HDR_LEN_MASK 0xFFFF + uint16_t ilen; +} __packed; + +#define AXE_TX_CSUM_PSEUDO_HDR 0x4000 +#define AXE_TX_CSUM_DIS 0x8000 + +/* + * When RX checksum offloading is enabled, AX88772B uses new RX header + * format and it's not compatible with previous RX header format. In + * addition, IP header align option should be enabled to get correct + * frame size including RX header. Total transferred size including + * the RX header is multiple of 4 and controller will pad necessary + * bytes if the length is not multiple of 4. + * This driver does not enable partial checksum feature which will + * compute 16bit checksum from 14th byte to the end of the frame. If + * this feature is enabled, computed checksum value is embedded into + * RX header which in turn means it uses different RX header format. + */ +struct axe_csum_hdr { + uint16_t len; +#define AXE_CSUM_HDR_LEN_MASK 0x07FF +#define AXE_CSUM_HDR_CRC_ERR 0x1000 +#define AXE_CSUM_HDR_MII_ERR 0x2000 +#define AXE_CSUM_HDR_RUNT 0x4000 +#define AXE_CSUM_HDR_BMCAST 0x8000 + uint16_t ilen; + uint16_t cstatus; +#define AXE_CSUM_HDR_VLAN_MASK 0x0007 +#define AXE_CSUM_HDR_VLAN_STRIP 0x0008 +#define AXE_CSUM_HDR_VLAN_PRI_MASK 0x0070 +#define AXE_CSUM_HDR_L4_CSUM_ERR 0x0100 +#define AXE_CSUM_HDR_L3_CSUM_ERR 0x0200 +#define AXE_CSUM_HDR_L4_TYPE_UDP 0x0400 +#define AXE_CSUM_HDR_L4_TYPE_ICMP 0x0800 +#define AXE_CSUM_HDR_L4_TYPE_IGMP 0x0C00 +#define AXE_CSUM_HDR_L4_TYPE_TCP 0x1000 +#define AXE_CSUM_HDR_L4_TYPE_TCPV6 0x1400 +#define AXE_CSUM_HDR_L4_TYPE_MASK 0x1C00 +#define AXE_CSUM_HDR_L3_TYPE_IPV4 0x2000 +#define AXE_CSUM_HDR_L3_TYPE_IPV6 0x4000 + +#ifdef AXE_APPEND_PARTIAL_CSUM + /* + * These members present only when partial checksum + * offloading is enabled. The checksum value is simple + * 16bit sum of received frame starting at offset 14 of + * the frame to the end of the frame excluding FCS bytes. + */ + uint16_t csum_value; + uint16_t dummy; +#endif +} __packed; + +#define AXE_CSUM_RXBYTES(x) ((x) & AXE_CSUM_HDR_LEN_MASK) + +#define GET_MII(sc) uether_getmii(&(sc)->sc_ue) + +/* The interrupt endpoint is currently unused by the ASIX part. */ +enum { + AXE_BULK_DT_WR, + AXE_BULK_DT_RD, + AXE_N_TRANSFER, +}; + +struct axe_softc { + struct usb_ether sc_ue; + struct mtx sc_mtx; + struct usb_xfer *sc_xfer[AXE_N_TRANSFER]; + int sc_phyno; + + int sc_flags; +#define AXE_FLAG_LINK 0x0001 +#define AXE_FLAG_STD_FRAME 0x0010 +#define AXE_FLAG_CSUM_FRAME 0x0020 +#define AXE_FLAG_772 0x1000 /* AX88772 */ +#define AXE_FLAG_772A 0x2000 /* AX88772A */ +#define AXE_FLAG_772B 0x4000 /* AX88772B */ +#define AXE_FLAG_178 0x8000 /* AX88178 */ + + uint8_t sc_ipgs[3]; + uint8_t sc_phyaddrs[2]; + uint16_t sc_pwrcfg; + uint16_t sc_lenmask; + int sc_tx_bufsz; +}; + +#define AXE_IS_178_FAMILY(sc) \ + ((sc)->sc_flags & (AXE_FLAG_772 | AXE_FLAG_772A | AXE_FLAG_772B | \ + AXE_FLAG_178)) + +#define AXE_IS_772(sc) \ + ((sc)->sc_flags & (AXE_FLAG_772 | AXE_FLAG_772A | AXE_FLAG_772B)) + +#define AXE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define AXE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define AXE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t) diff --git a/sys/bus/u4b/net/if_cdce.c b/sys/bus/u4b/net/if_cdce.c new file mode 100644 index 0000000000..eabf9c68d6 --- /dev/null +++ b/sys/bus/u4b/net/if_cdce.c @@ -0,0 +1,1417 @@ +/* $NetBSD: if_cdce.c,v 1.4 2004/10/24 12:50:54 augustss Exp $ */ + +/*- + * Copyright (c) 1997, 1998, 1999, 2000-2003 Bill Paul + * Copyright (c) 2003-2005 Craig Boston + * Copyright (c) 2004 Daniel Hartmeier + * Copyright (c) 2009 Hans Petter Selasky + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul, THE VOICES IN HIS HEAD OR + * THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * USB Communication Device Class (Ethernet Networking Control Model) + * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf + */ + +/* + * USB Network Control Model (NCM) + * http://www.usb.org/developers/devclass_docs/NCM10.zip + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR cdce_debug +#include +#include +#include "usb_if.h" + +#include +#include + +static device_probe_t cdce_probe; +static device_attach_t cdce_attach; +static device_detach_t cdce_detach; +static device_suspend_t cdce_suspend; +static device_resume_t cdce_resume; +static usb_handle_request_t cdce_handle_request; + +static usb_callback_t cdce_bulk_write_callback; +static usb_callback_t cdce_bulk_read_callback; +static usb_callback_t cdce_intr_read_callback; +static usb_callback_t cdce_intr_write_callback; + +#if CDCE_HAVE_NCM +static usb_callback_t cdce_ncm_bulk_write_callback; +static usb_callback_t cdce_ncm_bulk_read_callback; +#endif + +static uether_fn_t cdce_attach_post; +static uether_fn_t cdce_init; +static uether_fn_t cdce_stop; +static uether_fn_t cdce_start; +static uether_fn_t cdce_setmulti; +static uether_fn_t cdce_setpromisc; + +static uint32_t cdce_m_crc32(struct mbuf *, uint32_t, uint32_t); + +#ifdef USB_DEBUG +static int cdce_debug = 0; +static int cdce_tx_interval = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, cdce, CTLFLAG_RW, 0, "USB CDC-Ethernet"); +SYSCTL_INT(_hw_usb_cdce, OID_AUTO, debug, CTLFLAG_RW, &cdce_debug, 0, + "Debug level"); +SYSCTL_INT(_hw_usb_cdce, OID_AUTO, interval, CTLFLAG_RW, &cdce_tx_interval, 0, + "NCM transmit interval in ms"); +#endif + +static const struct usb_config cdce_config[CDCE_N_TRANSFER] = { + + [CDCE_BULK_RX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_RX, + .if_index = 0, + .frames = CDCE_FRAMES_MAX, + .bufsize = (CDCE_FRAMES_MAX * MCLBYTES), + .flags = {.pipe_bof = 1,.short_frames_ok = 1,.short_xfer_ok = 1,.ext_buffer = 1,}, + .callback = cdce_bulk_read_callback, + .timeout = 0, /* no timeout */ + .usb_mode = USB_MODE_DUAL, /* both modes */ + }, + + [CDCE_BULK_TX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_TX, + .if_index = 0, + .frames = CDCE_FRAMES_MAX, + .bufsize = (CDCE_FRAMES_MAX * MCLBYTES), + .flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1,}, + .callback = cdce_bulk_write_callback, + .timeout = 10000, /* 10 seconds */ + .usb_mode = USB_MODE_DUAL, /* both modes */ + }, + + [CDCE_INTR_RX] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_RX, + .if_index = 1, + .bufsize = CDCE_IND_SIZE_MAX, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, + .callback = cdce_intr_read_callback, + .timeout = 0, + .usb_mode = USB_MODE_HOST, + }, + + [CDCE_INTR_TX] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_TX, + .if_index = 1, + .bufsize = CDCE_IND_SIZE_MAX, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,}, + .callback = cdce_intr_write_callback, + .timeout = 10000, /* 10 seconds */ + .usb_mode = USB_MODE_DEVICE, + }, +}; + +#if CDCE_HAVE_NCM +static const struct usb_config cdce_ncm_config[CDCE_N_TRANSFER] = { + + [CDCE_BULK_RX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_RX, + .if_index = 0, + .frames = CDCE_NCM_RX_FRAMES_MAX, + .bufsize = (CDCE_NCM_RX_FRAMES_MAX * CDCE_NCM_RX_MAXLEN), + .flags = {.pipe_bof = 1,.short_frames_ok = 1,.short_xfer_ok = 1,}, + .callback = cdce_ncm_bulk_read_callback, + .timeout = 0, /* no timeout */ + .usb_mode = USB_MODE_DUAL, /* both modes */ + }, + + [CDCE_BULK_TX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_TX, + .if_index = 0, + .frames = CDCE_NCM_TX_FRAMES_MAX, + .bufsize = (CDCE_NCM_TX_FRAMES_MAX * CDCE_NCM_TX_MAXLEN), + .flags = {.pipe_bof = 1,}, + .callback = cdce_ncm_bulk_write_callback, + .timeout = 10000, /* 10 seconds */ + .usb_mode = USB_MODE_DUAL, /* both modes */ + }, + + [CDCE_INTR_RX] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_RX, + .if_index = 1, + .bufsize = CDCE_IND_SIZE_MAX, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, + .callback = cdce_intr_read_callback, + .timeout = 0, + .usb_mode = USB_MODE_HOST, + }, + + [CDCE_INTR_TX] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_TX, + .if_index = 1, + .bufsize = CDCE_IND_SIZE_MAX, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,}, + .callback = cdce_intr_write_callback, + .timeout = 10000, /* 10 seconds */ + .usb_mode = USB_MODE_DEVICE, + }, +}; +#endif + +static device_method_t cdce_methods[] = { + /* USB interface */ + DEVMETHOD(usb_handle_request, cdce_handle_request), + + /* Device interface */ + DEVMETHOD(device_probe, cdce_probe), + DEVMETHOD(device_attach, cdce_attach), + DEVMETHOD(device_detach, cdce_detach), + DEVMETHOD(device_suspend, cdce_suspend), + DEVMETHOD(device_resume, cdce_resume), + + {0, 0} +}; + +static driver_t cdce_driver = { + .name = "cdce", + .methods = cdce_methods, + .size = sizeof(struct cdce_softc), +}; + +static devclass_t cdce_devclass; + +DRIVER_MODULE(cdce, uhub, cdce_driver, cdce_devclass, NULL, 0); +MODULE_VERSION(cdce, 1); +MODULE_DEPEND(cdce, uether, 1, 1, 1); +MODULE_DEPEND(cdce, usb, 1, 1, 1); +MODULE_DEPEND(cdce, ether, 1, 1, 1); + +static const struct usb_ether_methods cdce_ue_methods = { + .ue_attach_post = cdce_attach_post, + .ue_start = cdce_start, + .ue_init = cdce_init, + .ue_stop = cdce_stop, + .ue_setmulti = cdce_setmulti, + .ue_setpromisc = cdce_setpromisc, +}; + +static const STRUCT_USB_HOST_ID cdce_host_devs[] = { + {USB_VPI(USB_VENDOR_ACERLABS, USB_PRODUCT_ACERLABS_M5632, CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_AMBIT, USB_PRODUCT_AMBIT_NTL_250, CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_IPAQLINUX, CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_GMATE, USB_PRODUCT_GMATE_YP3X00, CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN2, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_ETHERNETGADGET, CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2501, CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5500, CDCE_FLAG_ZAURUS)}, + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5600, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLA300, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC700, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC750, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, +}; + +static const STRUCT_USB_DUAL_ID cdce_dual_devs[] = { + {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, 0)}, + {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_MOBILE_DIRECT_LINE_MODEL, 0)}, + {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_NETWORK_CONTROL_MODEL, 0)}, +}; + +#if CDCE_HAVE_NCM +/*------------------------------------------------------------------------* + * cdce_ncm_init + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +cdce_ncm_init(struct cdce_softc *sc) +{ + struct usb_ncm_parameters temp; + struct usb_device_request req; + struct usb_ncm_func_descriptor *ufd; + uint8_t value[8]; + int err; + + ufd = usbd_find_descriptor(sc->sc_ue.ue_udev, NULL, + sc->sc_ifaces_index[1], UDESC_CS_INTERFACE, 0 - 1, + UCDC_NCM_FUNC_DESC_SUBTYPE, 0 - 1); + + /* verify length of NCM functional descriptor */ + if (ufd != NULL) { + if (ufd->bLength < sizeof(*ufd)) + ufd = NULL; + else + DPRINTFN(1, "Found NCM functional descriptor.\n"); + } + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UCDC_NCM_GET_NTB_PARAMETERS; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_ifaces_index[1]; + req.wIndex[1] = 0; + USETW(req.wLength, sizeof(temp)); + + err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req, + &temp, 0, NULL, 1000 /* ms */); + if (err) + return (1); + + /* Read correct set of parameters according to device mode */ + + if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) { + sc->sc_ncm.rx_max = UGETDW(temp.dwNtbInMaxSize); + sc->sc_ncm.tx_max = UGETDW(temp.dwNtbOutMaxSize); + sc->sc_ncm.tx_remainder = UGETW(temp.wNdpOutPayloadRemainder); + sc->sc_ncm.tx_modulus = UGETW(temp.wNdpOutDivisor); + sc->sc_ncm.tx_struct_align = UGETW(temp.wNdpOutAlignment); + sc->sc_ncm.tx_nframe = UGETW(temp.wNtbOutMaxDatagrams); + } else { + sc->sc_ncm.rx_max = UGETDW(temp.dwNtbOutMaxSize); + sc->sc_ncm.tx_max = UGETDW(temp.dwNtbInMaxSize); + sc->sc_ncm.tx_remainder = UGETW(temp.wNdpInPayloadRemainder); + sc->sc_ncm.tx_modulus = UGETW(temp.wNdpInDivisor); + sc->sc_ncm.tx_struct_align = UGETW(temp.wNdpInAlignment); + sc->sc_ncm.tx_nframe = UGETW(temp.wNtbOutMaxDatagrams); + } + + /* Verify maximum receive length */ + + if ((sc->sc_ncm.rx_max < 32) || + (sc->sc_ncm.rx_max > CDCE_NCM_RX_MAXLEN)) { + DPRINTFN(1, "Using default maximum receive length\n"); + sc->sc_ncm.rx_max = CDCE_NCM_RX_MAXLEN; + } + + /* Verify maximum transmit length */ + + if ((sc->sc_ncm.tx_max < 32) || + (sc->sc_ncm.tx_max > CDCE_NCM_TX_MAXLEN)) { + DPRINTFN(1, "Using default maximum transmit length\n"); + sc->sc_ncm.tx_max = CDCE_NCM_TX_MAXLEN; + } + + /* + * Verify that the structure alignment is: + * - power of two + * - not greater than the maximum transmit length + * - not less than four bytes + */ + if ((sc->sc_ncm.tx_struct_align < 4) || + (sc->sc_ncm.tx_struct_align != + ((-sc->sc_ncm.tx_struct_align) & sc->sc_ncm.tx_struct_align)) || + (sc->sc_ncm.tx_struct_align >= sc->sc_ncm.tx_max)) { + DPRINTFN(1, "Using default other alignment: 4 bytes\n"); + sc->sc_ncm.tx_struct_align = 4; + } + + /* + * Verify that the payload alignment is: + * - power of two + * - not greater than the maximum transmit length + * - not less than four bytes + */ + if ((sc->sc_ncm.tx_modulus < 4) || + (sc->sc_ncm.tx_modulus != + ((-sc->sc_ncm.tx_modulus) & sc->sc_ncm.tx_modulus)) || + (sc->sc_ncm.tx_modulus >= sc->sc_ncm.tx_max)) { + DPRINTFN(1, "Using default transmit modulus: 4 bytes\n"); + sc->sc_ncm.tx_modulus = 4; + } + + /* Verify that the payload remainder */ + + if ((sc->sc_ncm.tx_remainder >= sc->sc_ncm.tx_modulus)) { + DPRINTFN(1, "Using default transmit remainder: 0 bytes\n"); + sc->sc_ncm.tx_remainder = 0; + } + + /* + * Offset the TX remainder so that IP packet payload starts at + * the tx_modulus. This is not too clear in the specification. + */ + + sc->sc_ncm.tx_remainder = + (sc->sc_ncm.tx_remainder - ETHER_HDR_LEN) & + (sc->sc_ncm.tx_modulus - 1); + + /* Verify max datagrams */ + + if (sc->sc_ncm.tx_nframe == 0 || + sc->sc_ncm.tx_nframe > (CDCE_NCM_SUBFRAMES_MAX - 1)) { + DPRINTFN(1, "Using default max " + "subframes: %u units\n", CDCE_NCM_SUBFRAMES_MAX - 1); + /* need to reserve one entry for zero padding */ + sc->sc_ncm.tx_nframe = (CDCE_NCM_SUBFRAMES_MAX - 1); + } + + /* Additional configuration, will fail in device side mode, which is OK. */ + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_NCM_SET_NTB_INPUT_SIZE; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_ifaces_index[1]; + req.wIndex[1] = 0; + + if (ufd != NULL && + (ufd->bmNetworkCapabilities & UCDC_NCM_CAP_MAX_DGRAM)) { + USETW(req.wLength, 8); + USETDW(value, sc->sc_ncm.rx_max); + USETW(value + 4, (CDCE_NCM_SUBFRAMES_MAX - 1)); + USETW(value + 6, 0); + } else { + USETW(req.wLength, 4); + USETDW(value, sc->sc_ncm.rx_max); + } + + err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req, + &value, 0, NULL, 1000 /* ms */); + if (err) { + DPRINTFN(1, "Setting input size " + "to %u failed.\n", sc->sc_ncm.rx_max); + } + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_NCM_SET_CRC_MODE; + USETW(req.wValue, 0); /* no CRC */ + req.wIndex[0] = sc->sc_ifaces_index[1]; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req, + NULL, 0, NULL, 1000 /* ms */); + if (err) { + DPRINTFN(1, "Setting CRC mode to off failed.\n"); + } + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_NCM_SET_NTB_FORMAT; + USETW(req.wValue, 0); /* NTB-16 */ + req.wIndex[0] = sc->sc_ifaces_index[1]; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req, + NULL, 0, NULL, 1000 /* ms */); + if (err) { + DPRINTFN(1, "Setting NTB format to 16-bit failed.\n"); + } + + return (0); /* success */ +} +#endif + +static int +cdce_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + int error; + + error = usbd_lookup_id_by_uaa(cdce_host_devs, sizeof(cdce_host_devs), uaa); + if (error) + error = usbd_lookup_id_by_uaa(cdce_dual_devs, sizeof(cdce_dual_devs), uaa); + return (error); +} + +static void +cdce_attach_post(struct usb_ether *ue) +{ + /* no-op */ + return; +} + +static int +cdce_attach(device_t dev) +{ + struct cdce_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct usb_interface *iface; + const struct usb_cdc_union_descriptor *ud; + const struct usb_interface_descriptor *id; + const struct usb_cdc_ethernet_descriptor *ued; + const struct usb_config *pcfg; + int error; + uint8_t i; + uint8_t data_iface_no; + char eaddr_str[5 * ETHER_ADDR_LEN]; /* approx */ + + sc->sc_flags = USB_GET_DRIVER_INFO(uaa); + sc->sc_ue.ue_udev = uaa->device; + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + ud = usbd_find_descriptor + (uaa->device, NULL, uaa->info.bIfaceIndex, + UDESC_CS_INTERFACE, 0 - 1, UDESCSUB_CDC_UNION, 0 - 1); + + if ((ud == NULL) || (ud->bLength < sizeof(*ud)) || + (sc->sc_flags & CDCE_FLAG_NO_UNION)) { + DPRINTFN(1, "No union descriptor!\n"); + sc->sc_ifaces_index[0] = uaa->info.bIfaceIndex; + sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex; + goto alloc_transfers; + } + data_iface_no = ud->bSlaveInterface[0]; + + for (i = 0;; i++) { + + iface = usbd_get_iface(uaa->device, i); + + if (iface) { + + id = usbd_get_interface_descriptor(iface); + + if (id && (id->bInterfaceNumber == data_iface_no)) { + sc->sc_ifaces_index[0] = i; + sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex; + usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); + break; + } + } else { + device_printf(dev, "no data interface found\n"); + goto detach; + } + } + + /* + * + * + * The Data Class interface of a networking device shall have + * a minimum of two interface settings. The first setting + * (the default interface setting) includes no endpoints and + * therefore no networking traffic is exchanged whenever the + * default interface setting is selected. One or more + * additional interface settings are used for normal + * operation, and therefore each includes a pair of endpoints + * (one IN, and one OUT) to exchange network traffic. Select + * an alternate interface setting to initialize the network + * aspects of the device and to enable the exchange of + * network traffic. + * + * + * + * Some devices, most notably cable modems, include interface + * settings that have no IN or OUT endpoint, therefore loop + * through the list of all available interface settings + * looking for one with both IN and OUT endpoints. + */ + +alloc_transfers: + + pcfg = cdce_config; /* Default Configuration */ + + for (i = 0; i != 32; i++) { + + error = usbd_set_alt_interface_index(uaa->device, + sc->sc_ifaces_index[0], i); + if (error) + break; +#if CDCE_HAVE_NCM + if ((i == 0) && (cdce_ncm_init(sc) == 0)) + pcfg = cdce_ncm_config; +#endif + error = usbd_transfer_setup(uaa->device, + sc->sc_ifaces_index, sc->sc_xfer, + pcfg, CDCE_N_TRANSFER, sc, &sc->sc_mtx); + + if (error == 0) + break; + } + + if (error || (i == 32)) { + device_printf(dev, "No valid alternate " + "setting found\n"); + goto detach; + } + + ued = usbd_find_descriptor + (uaa->device, NULL, uaa->info.bIfaceIndex, + UDESC_CS_INTERFACE, 0 - 1, UDESCSUB_CDC_ENF, 0 - 1); + + if ((ued == NULL) || (ued->bLength < sizeof(*ued))) { + error = USB_ERR_INVAL; + } else { + error = usbd_req_get_string_any(uaa->device, NULL, + eaddr_str, sizeof(eaddr_str), ued->iMacAddress); + } + + if (error) { + + /* fake MAC address */ + + device_printf(dev, "faking MAC address\n"); + sc->sc_ue.ue_eaddr[0] = 0x2a; + memcpy(&sc->sc_ue.ue_eaddr[1], &ticks, sizeof(uint32_t)); + sc->sc_ue.ue_eaddr[5] = device_get_unit(dev); + + } else { + + memset(sc->sc_ue.ue_eaddr, 0, sizeof(sc->sc_ue.ue_eaddr)); + + for (i = 0; i != (ETHER_ADDR_LEN * 2); i++) { + + char c = eaddr_str[i]; + + if ('0' <= c && c <= '9') + c -= '0'; + else if (c != 0) + c -= 'A' - 10; + else + break; + + c &= 0xf; + + if ((i & 1) == 0) + c <<= 4; + sc->sc_ue.ue_eaddr[i / 2] |= c; + } + + if (uaa->usb_mode == USB_MODE_DEVICE) { + /* + * Do not use the same MAC address like the peer ! + */ + sc->sc_ue.ue_eaddr[5] ^= 0xFF; + } + } + + ue->ue_sc = sc; + ue->ue_dev = dev; + ue->ue_udev = uaa->device; + ue->ue_mtx = &sc->sc_mtx; + ue->ue_methods = &cdce_ue_methods; + + error = uether_ifattach(ue); + if (error) { + device_printf(dev, "could not attach interface\n"); + goto detach; + } + return (0); /* success */ + +detach: + cdce_detach(dev); + return (ENXIO); /* failure */ +} + +static int +cdce_detach(device_t dev) +{ + struct cdce_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + + /* stop all USB transfers first */ + usbd_transfer_unsetup(sc->sc_xfer, CDCE_N_TRANSFER); + uether_ifdetach(ue); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +cdce_start(struct usb_ether *ue) +{ + struct cdce_softc *sc = uether_getsc(ue); + + /* + * Start the USB transfers, if not already started: + */ + usbd_transfer_start(sc->sc_xfer[CDCE_BULK_TX]); + usbd_transfer_start(sc->sc_xfer[CDCE_BULK_RX]); +} + +static void +cdce_free_queue(struct mbuf **ppm, uint8_t n) +{ + uint8_t x; + for (x = 0; x != n; x++) { + if (ppm[x] != NULL) { + m_freem(ppm[x]); + ppm[x] = NULL; + } + } +} + +static void +cdce_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct cdce_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct mbuf *m; + struct mbuf *mt; + uint32_t crc; + uint8_t x; + int actlen, aframes; + + usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); + + DPRINTFN(1, "\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete: %u bytes in %u frames\n", + actlen, aframes); + + ifp->if_opackets++; + + /* free all previous TX buffers */ + cdce_free_queue(sc->sc_tx_buf, CDCE_FRAMES_MAX); + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + for (x = 0; x != CDCE_FRAMES_MAX; x++) { + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) + break; + + if (sc->sc_flags & CDCE_FLAG_ZAURUS) { + /* + * Zaurus wants a 32-bit CRC appended + * to every frame + */ + + crc = cdce_m_crc32(m, 0, m->m_pkthdr.len); + crc = htole32(crc); + + if (!m_append(m, 4, (void *)&crc)) { + m_freem(m); + ifp->if_oerrors++; + continue; + } + } + if (m->m_len != m->m_pkthdr.len) { + mt = m_defrag(m, M_DONTWAIT); + if (mt == NULL) { + m_freem(m); + ifp->if_oerrors++; + continue; + } + m = mt; + } + if (m->m_pkthdr.len > MCLBYTES) { + m->m_pkthdr.len = MCLBYTES; + } + sc->sc_tx_buf[x] = m; + usbd_xfer_set_frame_data(xfer, x, m->m_data, m->m_len); + + /* + * If there's a BPF listener, bounce a copy of + * this frame to him: + */ + BPF_MTAP(ifp, m); + } + if (x != 0) { + usbd_xfer_set_frames(xfer, x); + + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usbd_errstr(error)); + + /* free all previous TX buffers */ + cdce_free_queue(sc->sc_tx_buf, CDCE_FRAMES_MAX); + + /* count output errors */ + ifp->if_oerrors++; + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static int32_t +cdce_m_crc32_cb(void *arg, void *src, uint32_t count) +{ + uint32_t *p_crc = arg; + + *p_crc = crc32_raw(src, count, *p_crc); + return (0); +} + +static uint32_t +cdce_m_crc32(struct mbuf *m, uint32_t src_offset, uint32_t src_len) +{ + uint32_t crc = 0xFFFFFFFF; + int error; + + error = m_apply(m, src_offset, src_len, cdce_m_crc32_cb, &crc); + return (crc ^ 0xFFFFFFFF); +} + +static void +cdce_init(struct usb_ether *ue) +{ + struct cdce_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + CDCE_LOCK_ASSERT(sc, MA_OWNED); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + /* start interrupt transfer */ + usbd_transfer_start(sc->sc_xfer[CDCE_INTR_RX]); + usbd_transfer_start(sc->sc_xfer[CDCE_INTR_TX]); + + /* stall data write direction, which depends on USB mode */ + usbd_xfer_set_stall(sc->sc_xfer[CDCE_BULK_TX]); + + /* start data transfers */ + cdce_start(ue); +} + +static void +cdce_stop(struct usb_ether *ue) +{ + struct cdce_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + CDCE_LOCK_ASSERT(sc, MA_OWNED); + + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + + /* + * stop all the transfers, if not already stopped: + */ + usbd_transfer_stop(sc->sc_xfer[CDCE_BULK_RX]); + usbd_transfer_stop(sc->sc_xfer[CDCE_BULK_TX]); + usbd_transfer_stop(sc->sc_xfer[CDCE_INTR_RX]); + usbd_transfer_stop(sc->sc_xfer[CDCE_INTR_TX]); +} + +static void +cdce_setmulti(struct usb_ether *ue) +{ + /* no-op */ + return; +} + +static void +cdce_setpromisc(struct usb_ether *ue) +{ + /* no-op */ + return; +} + +static int +cdce_suspend(device_t dev) +{ + device_printf(dev, "Suspending\n"); + return (0); +} + +static int +cdce_resume(device_t dev) +{ + device_printf(dev, "Resuming\n"); + return (0); +} + +static void +cdce_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct cdce_softc *sc = usbd_xfer_softc(xfer); + struct mbuf *m; + uint8_t x; + int actlen, aframes, len; + + usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("received %u bytes in %u frames\n", actlen, aframes); + + for (x = 0; x != aframes; x++) { + + m = sc->sc_rx_buf[x]; + sc->sc_rx_buf[x] = NULL; + len = usbd_xfer_frame_len(xfer, x); + + /* Strip off CRC added by Zaurus, if any */ + if ((sc->sc_flags & CDCE_FLAG_ZAURUS) && len >= 14) + len -= 4; + + if (len < sizeof(struct ether_header)) { + m_freem(m); + continue; + } + /* queue up mbuf */ + uether_rxmbuf(&sc->sc_ue, m, len); + } + + /* FALLTHROUGH */ + case USB_ST_SETUP: + /* + * TODO: Implement support for multi frame transfers, + * when the USB hardware supports it. + */ + for (x = 0; x != 1; x++) { + if (sc->sc_rx_buf[x] == NULL) { + m = uether_newbuf(); + if (m == NULL) + goto tr_stall; + sc->sc_rx_buf[x] = m; + } else { + m = sc->sc_rx_buf[x]; + } + + usbd_xfer_set_frame_data(xfer, x, m->m_data, m->m_len); + } + /* set number of frames and start hardware */ + usbd_xfer_set_frames(xfer, x); + usbd_transfer_submit(xfer); + /* flush any received frames */ + uether_rxflush(&sc->sc_ue); + break; + + default: /* Error */ + DPRINTF("error = %s\n", + usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { +tr_stall: + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + usbd_xfer_set_frames(xfer, 0); + usbd_transfer_submit(xfer); + break; + } + + /* need to free the RX-mbufs when we are cancelled */ + cdce_free_queue(sc->sc_rx_buf, CDCE_FRAMES_MAX); + break; + } +} + +static void +cdce_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("Received %d bytes\n", actlen); + + /* TODO: decode some indications */ + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* start clear stall */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +cdce_intr_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("Transferred %d bytes\n", actlen); + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: +#if 0 + usbd_xfer_set_frame_len(xfer, 0, XXX); + usbd_transfer_submit(xfer); +#endif + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* start clear stall */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static int +cdce_handle_request(device_t dev, + const void *req, void **pptr, uint16_t *plen, + uint16_t offset, uint8_t *pstate) +{ + return (ENXIO); /* use builtin handler */ +} + +#if CDCE_HAVE_NCM +static void +cdce_ncm_tx_zero(struct usb_page_cache *pc, + uint32_t start, uint32_t end) +{ + if (start >= CDCE_NCM_TX_MAXLEN) + return; + if (end > CDCE_NCM_TX_MAXLEN) + end = CDCE_NCM_TX_MAXLEN; + + usbd_frame_zero(pc, start, end - start); +} + +static uint8_t +cdce_ncm_fill_tx_frames(struct usb_xfer *xfer, uint8_t index) +{ + struct cdce_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, index); + struct mbuf *m; + uint32_t rem; + uint32_t offset; + uint32_t last_offset; + uint16_t n; + uint8_t retval; + + usbd_xfer_set_frame_offset(xfer, index * CDCE_NCM_TX_MAXLEN, index); + + offset = sizeof(sc->sc_ncm.hdr) + + sizeof(sc->sc_ncm.dpt) + sizeof(sc->sc_ncm.dp); + + /* Store last valid offset before alignment */ + last_offset = offset; + + /* Align offset */ + offset = CDCE_NCM_ALIGN(sc->sc_ncm.tx_remainder, + offset, sc->sc_ncm.tx_modulus); + + /* Zero pad */ + cdce_ncm_tx_zero(pc, last_offset, offset); + + /* buffer full */ + retval = 2; + + for (n = 0; n != sc->sc_ncm.tx_nframe; n++) { + + /* check if end of transmit buffer is reached */ + + if (offset >= sc->sc_ncm.tx_max) + break; + + /* compute maximum buffer size */ + + rem = sc->sc_ncm.tx_max - offset; + + IFQ_DRV_DEQUEUE(&(ifp->if_snd), m); + + if (m == NULL) { + /* buffer not full */ + retval = 1; + break; + } + + if (m->m_pkthdr.len > rem) { + if (n == 0) { + /* The frame won't fit in our buffer */ + DPRINTFN(1, "Frame too big to be transmitted!\n"); + m_freem(m); + ifp->if_oerrors++; + n--; + continue; + } + /* Wait till next buffer becomes ready */ + IFQ_DRV_PREPEND(&(ifp->if_snd), m); + break; + } + usbd_m_copy_in(pc, offset, m, 0, m->m_pkthdr.len); + + USETW(sc->sc_ncm.dp[n].wFrameLength, m->m_pkthdr.len); + USETW(sc->sc_ncm.dp[n].wFrameIndex, offset); + + /* Update offset */ + offset += m->m_pkthdr.len; + + /* Store last valid offset before alignment */ + last_offset = offset; + + /* Align offset */ + offset = CDCE_NCM_ALIGN(sc->sc_ncm.tx_remainder, + offset, sc->sc_ncm.tx_modulus); + + /* Zero pad */ + cdce_ncm_tx_zero(pc, last_offset, offset); + + /* + * If there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + + /* Free mbuf */ + + m_freem(m); + + /* Pre-increment interface counter */ + + ifp->if_opackets++; + } + + if (n == 0) + return (0); + + rem = (sizeof(sc->sc_ncm.dpt) + (4 * n) + 4); + + USETW(sc->sc_ncm.dpt.wLength, rem); + + /* zero the rest of the data pointer entries */ + for (; n != CDCE_NCM_SUBFRAMES_MAX; n++) { + USETW(sc->sc_ncm.dp[n].wFrameLength, 0); + USETW(sc->sc_ncm.dp[n].wFrameIndex, 0); + } + + offset = last_offset; + + /* Align offset */ + offset = CDCE_NCM_ALIGN(0, offset, CDCE_NCM_TX_MINLEN); + + /* Optimise, save bandwidth and force short termination */ + if (offset >= sc->sc_ncm.tx_max) + offset = sc->sc_ncm.tx_max; + else + offset ++; + + /* Zero pad */ + cdce_ncm_tx_zero(pc, last_offset, offset); + + /* set frame length */ + usbd_xfer_set_frame_len(xfer, index, offset); + + /* Fill out 16-bit header */ + sc->sc_ncm.hdr.dwSignature[0] = 'N'; + sc->sc_ncm.hdr.dwSignature[1] = 'C'; + sc->sc_ncm.hdr.dwSignature[2] = 'M'; + sc->sc_ncm.hdr.dwSignature[3] = 'H'; + USETW(sc->sc_ncm.hdr.wHeaderLength, sizeof(sc->sc_ncm.hdr)); + USETW(sc->sc_ncm.hdr.wBlockLength, offset); + USETW(sc->sc_ncm.hdr.wSequence, sc->sc_ncm.tx_seq); + USETW(sc->sc_ncm.hdr.wDptIndex, sizeof(sc->sc_ncm.hdr)); + + sc->sc_ncm.tx_seq++; + + /* Fill out 16-bit frame table header */ + sc->sc_ncm.dpt.dwSignature[0] = 'N'; + sc->sc_ncm.dpt.dwSignature[1] = 'C'; + sc->sc_ncm.dpt.dwSignature[2] = 'M'; + sc->sc_ncm.dpt.dwSignature[3] = '0'; + USETW(sc->sc_ncm.dpt.wNextNdpIndex, 0); /* reserved */ + + usbd_copy_in(pc, 0, &(sc->sc_ncm.hdr), sizeof(sc->sc_ncm.hdr)); + usbd_copy_in(pc, sizeof(sc->sc_ncm.hdr), &(sc->sc_ncm.dpt), + sizeof(sc->sc_ncm.dpt)); + usbd_copy_in(pc, sizeof(sc->sc_ncm.hdr) + sizeof(sc->sc_ncm.dpt), + &(sc->sc_ncm.dp), sizeof(sc->sc_ncm.dp)); + return (retval); +} + +static void +cdce_ncm_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct cdce_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + uint16_t x; + uint8_t temp; + int actlen; + int aframes; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); + + DPRINTFN(10, "transfer complete: " + "%u bytes in %u frames\n", actlen, aframes); + + case USB_ST_SETUP: + for (x = 0; x != CDCE_NCM_TX_FRAMES_MAX; x++) { + temp = cdce_ncm_fill_tx_frames(xfer, x); + if (temp == 0) + break; + if (temp == 1) { + x++; + break; + } + } + + if (x != 0) { +#ifdef USB_DEBUG + usbd_xfer_set_interval(xfer, cdce_tx_interval); +#endif + usbd_xfer_set_frames(xfer, x); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + DPRINTFN(10, "Transfer error: %s\n", + usbd_errstr(error)); + + /* update error counter */ + ifp->if_oerrors += 1; + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + usbd_xfer_set_frames(xfer, 0); + usbd_transfer_submit(xfer); + } + break; + } +} + +static void +cdce_ncm_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct cdce_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, 0); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct mbuf *m; + int sumdata; + int sumlen; + int actlen; + int aframes; + int temp; + int nframes; + int x; + int offset; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + usbd_xfer_status(xfer, &actlen, &sumlen, &aframes, NULL); + + DPRINTFN(1, "received %u bytes in %u frames\n", + actlen, aframes); + + if (actlen < (sizeof(sc->sc_ncm.hdr) + + sizeof(sc->sc_ncm.dpt))) { + DPRINTFN(1, "frame too short\n"); + goto tr_setup; + } + usbd_copy_out(pc, 0, &(sc->sc_ncm.hdr), + sizeof(sc->sc_ncm.hdr)); + + if ((sc->sc_ncm.hdr.dwSignature[0] != 'N') || + (sc->sc_ncm.hdr.dwSignature[1] != 'C') || + (sc->sc_ncm.hdr.dwSignature[2] != 'M') || + (sc->sc_ncm.hdr.dwSignature[3] != 'H')) { + DPRINTFN(1, "invalid HDR signature: " + "0x%02x:0x%02x:0x%02x:0x%02x\n", + sc->sc_ncm.hdr.dwSignature[0], + sc->sc_ncm.hdr.dwSignature[1], + sc->sc_ncm.hdr.dwSignature[2], + sc->sc_ncm.hdr.dwSignature[3]); + goto tr_stall; + } + temp = UGETW(sc->sc_ncm.hdr.wBlockLength); + if (temp > sumlen) { + DPRINTFN(1, "unsupported block length %u/%u\n", + temp, sumlen); + goto tr_stall; + } + temp = UGETW(sc->sc_ncm.hdr.wDptIndex); + if ((temp + sizeof(sc->sc_ncm.dpt)) > actlen) { + DPRINTFN(1, "invalid DPT index: 0x%04x\n", temp); + goto tr_stall; + } + usbd_copy_out(pc, temp, &(sc->sc_ncm.dpt), + sizeof(sc->sc_ncm.dpt)); + + if ((sc->sc_ncm.dpt.dwSignature[0] != 'N') || + (sc->sc_ncm.dpt.dwSignature[1] != 'C') || + (sc->sc_ncm.dpt.dwSignature[2] != 'M') || + (sc->sc_ncm.dpt.dwSignature[3] != '0')) { + DPRINTFN(1, "invalid DPT signature" + "0x%02x:0x%02x:0x%02x:0x%02x\n", + sc->sc_ncm.dpt.dwSignature[0], + sc->sc_ncm.dpt.dwSignature[1], + sc->sc_ncm.dpt.dwSignature[2], + sc->sc_ncm.dpt.dwSignature[3]); + goto tr_stall; + } + nframes = UGETW(sc->sc_ncm.dpt.wLength) / 4; + + /* Subtract size of header and last zero padded entry */ + if (nframes >= (2 + 1)) + nframes -= (2 + 1); + else + nframes = 0; + + DPRINTFN(1, "nframes = %u\n", nframes); + + temp += sizeof(sc->sc_ncm.dpt); + + if ((temp + (4 * nframes)) > actlen) + goto tr_stall; + + if (nframes > CDCE_NCM_SUBFRAMES_MAX) { + DPRINTFN(1, "Truncating number of frames from %u to %u\n", + nframes, CDCE_NCM_SUBFRAMES_MAX); + nframes = CDCE_NCM_SUBFRAMES_MAX; + } + usbd_copy_out(pc, temp, &(sc->sc_ncm.dp), (4 * nframes)); + + sumdata = 0; + + for (x = 0; x != nframes; x++) { + + offset = UGETW(sc->sc_ncm.dp[x].wFrameIndex); + temp = UGETW(sc->sc_ncm.dp[x].wFrameLength); + + if ((offset == 0) || + (temp < sizeof(struct ether_header)) || + (temp > (MCLBYTES - ETHER_ALIGN))) { + DPRINTFN(1, "NULL frame detected at %d\n", x); + m = NULL; + /* silently ignore this frame */ + continue; + } else if ((offset + temp) > actlen) { + DPRINTFN(1, "invalid frame " + "detected at %d\n", x); + m = NULL; + /* silently ignore this frame */ + continue; + } else if (temp > (MHLEN - ETHER_ALIGN)) { + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + } else { + m = m_gethdr(M_DONTWAIT, MT_DATA); + } + + DPRINTFN(16, "frame %u, offset = %u, length = %u \n", + x, offset, temp); + + /* check if we have a buffer */ + if (m) { + m_adj(m, ETHER_ALIGN); + + usbd_copy_out(pc, offset, m->m_data, temp); + + /* enqueue */ + uether_rxmbuf(&sc->sc_ue, m, temp); + + sumdata += temp; + } else { + ifp->if_ierrors++; + } + } + + DPRINTFN(1, "Efficiency: %u/%u bytes\n", sumdata, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, sc->sc_ncm.rx_max); + usbd_xfer_set_frames(xfer, 1); + usbd_transfer_submit(xfer); + uether_rxflush(&sc->sc_ue); /* must be last */ + break; + + default: /* Error */ + DPRINTFN(1, "error = %s\n", + usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { +tr_stall: + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + usbd_xfer_set_frames(xfer, 0); + usbd_transfer_submit(xfer); + } + break; + } +} +#endif diff --git a/sys/bus/u4b/net/if_cdcereg.h b/sys/bus/u4b/net/if_cdcereg.h new file mode 100644 index 0000000000..c470b368e0 --- /dev/null +++ b/sys/bus/u4b/net/if_cdcereg.h @@ -0,0 +1,101 @@ +/*- + * Copyright (c) 2003-2005 Craig Boston + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul, THE VOICES IN HIS HEAD OR + * THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _USB_IF_CDCEREG_H_ +#define _USB_IF_CDCEREG_H_ + +#define CDCE_FRAMES_MAX 8 /* units */ +#define CDCE_IND_SIZE_MAX 32 /* bytes */ + +#define CDCE_NCM_TX_MINLEN 512 /* bytes, must be power of two */ +#define CDCE_NCM_TX_MAXLEN (16384 + 4) /* bytes, must be short terminated */ +#define CDCE_NCM_TX_FRAMES_MAX 8 /* units */ + +#define CDCE_NCM_RX_MAXLEN (1UL << 14) /* bytes */ +#define CDCE_NCM_RX_FRAMES_MAX 1 /* units */ + +#define CDCE_NCM_SUBFRAMES_MAX 32 /* units */ + +#define CDCE_NCM_ALIGN(rem,off,mod) \ + ((uint32_t)(((uint32_t)(rem)) - \ + ((uint32_t)((-(uint32_t)(off)) & (-(uint32_t)(mod)))))) + +#ifndef CDCE_HAVE_NCM +#define CDCE_HAVE_NCM 1 +#endif + +enum { + CDCE_BULK_RX, + CDCE_BULK_TX, + CDCE_INTR_RX, + CDCE_INTR_TX, + CDCE_N_TRANSFER, +}; + +struct cdce_ncm { + struct usb_ncm16_hdr hdr; + struct usb_ncm16_dpt dpt; + struct usb_ncm16_dp dp[CDCE_NCM_SUBFRAMES_MAX]; + uint32_t rx_max; + uint32_t tx_max; + uint16_t tx_remainder; + uint16_t tx_modulus; + uint16_t tx_struct_align; + uint16_t tx_seq; + uint16_t tx_nframe; +}; + +struct cdce_softc { + struct usb_ether sc_ue; + struct mtx sc_mtx; +#if CDCE_HAVE_NCM + struct cdce_ncm sc_ncm; +#endif + struct usb_xfer *sc_xfer[CDCE_N_TRANSFER]; + struct mbuf *sc_rx_buf[CDCE_FRAMES_MAX]; + struct mbuf *sc_tx_buf[CDCE_FRAMES_MAX]; + + int sc_flags; +#define CDCE_FLAG_ZAURUS 0x0001 +#define CDCE_FLAG_NO_UNION 0x0002 +#define CDCE_FLAG_RX_DATA 0x0010 + + uint8_t sc_eaddr_str_index; + uint8_t sc_ifaces_index[2]; +}; + +#define CDCE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define CDCE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define CDCE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t) +#endif /* _USB_IF_CDCEREG_H_ */ diff --git a/sys/bus/u4b/net/if_cue.c b/sys/bus/u4b/net/if_cue.c new file mode 100644 index 0000000000..7466ba9342 --- /dev/null +++ b/sys/bus/u4b/net/if_cue.c @@ -0,0 +1,650 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * CATC USB-EL1210A USB to ethernet driver. Used in the CATC Netmate + * adapters and others. + * + * Written by Bill Paul + * Electrical Engineering Department + * Columbia University, New York City + */ + +/* + * The CATC USB-EL1210A provides USB ethernet support at 10Mbps. The + * RX filter uses a 512-bit multicast hash table, single perfect entry + * for the station address, and promiscuous mode. Unlike the ADMtek + * and KLSI chips, the CATC ASIC supports read and write combining + * mode where multiple packets can be transfered using a single bulk + * transaction, which helps performance a great deal. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR cue_debug +#include +#include + +#include +#include + +/* + * Various supported device vendors/products. + */ + +/* Belkin F5U111 adapter covered by NETMATE entry */ + +static const STRUCT_USB_HOST_ID cue_devs[] = { +#define CUE_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } + CUE_DEV(CATC, NETMATE), + CUE_DEV(CATC, NETMATE2), + CUE_DEV(SMARTBRIDGES, SMARTLINK), +#undef CUE_DEV +}; + +/* prototypes */ + +static device_probe_t cue_probe; +static device_attach_t cue_attach; +static device_detach_t cue_detach; + +static usb_callback_t cue_bulk_read_callback; +static usb_callback_t cue_bulk_write_callback; + +static uether_fn_t cue_attach_post; +static uether_fn_t cue_init; +static uether_fn_t cue_stop; +static uether_fn_t cue_start; +static uether_fn_t cue_tick; +static uether_fn_t cue_setmulti; +static uether_fn_t cue_setpromisc; + +static uint8_t cue_csr_read_1(struct cue_softc *, uint16_t); +static uint16_t cue_csr_read_2(struct cue_softc *, uint8_t); +static int cue_csr_write_1(struct cue_softc *, uint16_t, uint16_t); +static int cue_mem(struct cue_softc *, uint8_t, uint16_t, void *, int); +static int cue_getmac(struct cue_softc *, void *); +static uint32_t cue_mchash(const uint8_t *); +static void cue_reset(struct cue_softc *); + +#ifdef USB_DEBUG +static int cue_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, cue, CTLFLAG_RW, 0, "USB cue"); +SYSCTL_INT(_hw_usb_cue, OID_AUTO, debug, CTLFLAG_RW, &cue_debug, 0, + "Debug level"); +#endif + +static const struct usb_config cue_config[CUE_N_TRANSFER] = { + + [CUE_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = (MCLBYTES + 2), + .flags = {.pipe_bof = 1,}, + .callback = cue_bulk_write_callback, + .timeout = 10000, /* 10 seconds */ + }, + + [CUE_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = (MCLBYTES + 2), + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = cue_bulk_read_callback, + }, +}; + +static device_method_t cue_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, cue_probe), + DEVMETHOD(device_attach, cue_attach), + DEVMETHOD(device_detach, cue_detach), + + {0, 0} +}; + +static driver_t cue_driver = { + .name = "cue", + .methods = cue_methods, + .size = sizeof(struct cue_softc), +}; + +static devclass_t cue_devclass; + +DRIVER_MODULE(cue, uhub, cue_driver, cue_devclass, NULL, 0); +MODULE_DEPEND(cue, uether, 1, 1, 1); +MODULE_DEPEND(cue, usb, 1, 1, 1); +MODULE_DEPEND(cue, ether, 1, 1, 1); +MODULE_VERSION(cue, 1); + +static const struct usb_ether_methods cue_ue_methods = { + .ue_attach_post = cue_attach_post, + .ue_start = cue_start, + .ue_init = cue_init, + .ue_stop = cue_stop, + .ue_tick = cue_tick, + .ue_setmulti = cue_setmulti, + .ue_setpromisc = cue_setpromisc, +}; + +#define CUE_SETBIT(sc, reg, x) \ + cue_csr_write_1(sc, reg, cue_csr_read_1(sc, reg) | (x)) + +#define CUE_CLRBIT(sc, reg, x) \ + cue_csr_write_1(sc, reg, cue_csr_read_1(sc, reg) & ~(x)) + +static uint8_t +cue_csr_read_1(struct cue_softc *sc, uint16_t reg) +{ + struct usb_device_request req; + uint8_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = CUE_CMD_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + if (uether_do_request(&sc->sc_ue, &req, &val, 1000)) { + /* ignore any errors */ + } + return (val); +} + +static uint16_t +cue_csr_read_2(struct cue_softc *sc, uint8_t reg) +{ + struct usb_device_request req; + uint16_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = CUE_CMD_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 2); + + (void)uether_do_request(&sc->sc_ue, &req, &val, 1000); + return (le16toh(val)); +} + +static int +cue_csr_write_1(struct cue_softc *sc, uint16_t reg, uint16_t val) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = CUE_CMD_WRITEREG; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + return (uether_do_request(&sc->sc_ue, &req, NULL, 1000)); +} + +static int +cue_mem(struct cue_softc *sc, uint8_t cmd, uint16_t addr, void *buf, int len) +{ + struct usb_device_request req; + + if (cmd == CUE_CMD_READSRAM) + req.bmRequestType = UT_READ_VENDOR_DEVICE; + else + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = cmd; + USETW(req.wValue, 0); + USETW(req.wIndex, addr); + USETW(req.wLength, len); + + return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); +} + +static int +cue_getmac(struct cue_softc *sc, void *buf) +{ + struct usb_device_request req; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = CUE_CMD_GET_MACADDR; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, ETHER_ADDR_LEN); + + return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); +} + +#define CUE_BITS 9 + +static uint32_t +cue_mchash(const uint8_t *addr) +{ + uint32_t crc; + + /* Compute CRC for the address value. */ + crc = ether_crc32_le(addr, ETHER_ADDR_LEN); + + return (crc & ((1 << CUE_BITS) - 1)); +} + +static void +cue_setpromisc(struct usb_ether *ue) +{ + struct cue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + CUE_LOCK_ASSERT(sc, MA_OWNED); + + /* if we want promiscuous mode, set the allframes bit */ + if (ifp->if_flags & IFF_PROMISC) + CUE_SETBIT(sc, CUE_ETHCTL, CUE_ETHCTL_PROMISC); + else + CUE_CLRBIT(sc, CUE_ETHCTL, CUE_ETHCTL_PROMISC); + + /* write multicast hash-bits */ + cue_setmulti(ue); +} + +static void +cue_setmulti(struct usb_ether *ue) +{ + struct cue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + struct ifmultiaddr *ifma; + uint32_t h = 0, i; + uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + CUE_LOCK_ASSERT(sc, MA_OWNED); + + if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { + for (i = 0; i < 8; i++) + hashtbl[i] = 0xff; + cue_mem(sc, CUE_CMD_WRITESRAM, CUE_MCAST_TABLE_ADDR, + &hashtbl, 8); + return; + } + + /* now program new ones */ + if_maddr_rlock(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) + { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + h = cue_mchash(LLADDR((struct sockaddr_dl *)ifma->ifma_addr)); + hashtbl[h >> 3] |= 1 << (h & 0x7); + } + if_maddr_runlock(ifp); + + /* + * Also include the broadcast address in the filter + * so we can receive broadcast frames. + */ + if (ifp->if_flags & IFF_BROADCAST) { + h = cue_mchash(ifp->if_broadcastaddr); + hashtbl[h >> 3] |= 1 << (h & 0x7); + } + + cue_mem(sc, CUE_CMD_WRITESRAM, CUE_MCAST_TABLE_ADDR, &hashtbl, 8); +} + +static void +cue_reset(struct cue_softc *sc) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = CUE_CMD_RESET; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + if (uether_do_request(&sc->sc_ue, &req, NULL, 1000)) { + /* ignore any errors */ + } + + /* + * wait a little while for the chip to get its brains in order: + */ + uether_pause(&sc->sc_ue, hz / 100); +} + +static void +cue_attach_post(struct usb_ether *ue) +{ + struct cue_softc *sc = uether_getsc(ue); + + cue_getmac(sc, ue->ue_eaddr); +} + +static int +cue_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != CUE_CONFIG_IDX) + return (ENXIO); + if (uaa->info.bIfaceIndex != CUE_IFACE_IDX) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(cue_devs, sizeof(cue_devs), uaa)); +} + +/* + * Attach the interface. Allocate softc structures, do ifmedia + * setup and ethernet/BPF attach. + */ +static int +cue_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct cue_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + uint8_t iface_index; + int error; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + iface_index = CUE_IFACE_IDX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, cue_config, CUE_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB transfers failed\n"); + goto detach; + } + + ue->ue_sc = sc; + ue->ue_dev = dev; + ue->ue_udev = uaa->device; + ue->ue_mtx = &sc->sc_mtx; + ue->ue_methods = &cue_ue_methods; + + error = uether_ifattach(ue); + if (error) { + device_printf(dev, "could not attach interface\n"); + goto detach; + } + return (0); /* success */ + +detach: + cue_detach(dev); + return (ENXIO); /* failure */ +} + +static int +cue_detach(device_t dev) +{ + struct cue_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + + usbd_transfer_unsetup(sc->sc_xfer, CUE_N_TRANSFER); + uether_ifdetach(ue); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +cue_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct cue_softc *sc = usbd_xfer_softc(xfer); + struct usb_ether *ue = &sc->sc_ue; + struct ifnet *ifp = uether_getifp(ue); + struct usb_page_cache *pc; + uint8_t buf[2]; + int len; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen <= (2 + sizeof(struct ether_header))) { + ifp->if_ierrors++; + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, 2); + actlen -= 2; + len = buf[0] | (buf[1] << 8); + len = min(actlen, len); + + uether_rxbuf(ue, pc, 2, len); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + uether_rxflush(ue); + return; + + default: /* Error */ + DPRINTF("bulk read error, %s\n", + usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +cue_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct cue_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct usb_page_cache *pc; + struct mbuf *m; + uint8_t buf[2]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + ifp->if_opackets++; + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) + return; + if (m->m_pkthdr.len > MCLBYTES) + m->m_pkthdr.len = MCLBYTES; + usbd_xfer_set_frame_len(xfer, 0, (m->m_pkthdr.len + 2)); + + /* the first two bytes are the frame length */ + + buf[0] = (uint8_t)(m->m_pkthdr.len); + buf[1] = (uint8_t)(m->m_pkthdr.len >> 8); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, buf, 2); + usbd_m_copy_in(pc, 2, m, 0, m->m_pkthdr.len); + + /* + * If there's a BPF listener, bounce a copy of this frame + * to him. + */ + BPF_MTAP(ifp, m); + + m_freem(m); + + usbd_transfer_submit(xfer); + + return; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usbd_errstr(error)); + + ifp->if_oerrors++; + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +cue_tick(struct usb_ether *ue) +{ + struct cue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + CUE_LOCK_ASSERT(sc, MA_OWNED); + + ifp->if_collisions += cue_csr_read_2(sc, CUE_TX_SINGLECOLL); + ifp->if_collisions += cue_csr_read_2(sc, CUE_TX_MULTICOLL); + ifp->if_collisions += cue_csr_read_2(sc, CUE_TX_EXCESSCOLL); + + if (cue_csr_read_2(sc, CUE_RX_FRAMEERR)) + ifp->if_ierrors++; +} + +static void +cue_start(struct usb_ether *ue) +{ + struct cue_softc *sc = uether_getsc(ue); + + /* + * start the USB transfers, if not already started: + */ + usbd_transfer_start(sc->sc_xfer[CUE_BULK_DT_RD]); + usbd_transfer_start(sc->sc_xfer[CUE_BULK_DT_WR]); +} + +static void +cue_init(struct usb_ether *ue) +{ + struct cue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + int i; + + CUE_LOCK_ASSERT(sc, MA_OWNED); + + /* + * Cancel pending I/O and free all RX/TX buffers. + */ + cue_stop(ue); +#if 0 + cue_reset(sc); +#endif + /* Set MAC address */ + for (i = 0; i < ETHER_ADDR_LEN; i++) + cue_csr_write_1(sc, CUE_PAR0 - i, IF_LLADDR(ifp)[i]); + + /* Enable RX logic. */ + cue_csr_write_1(sc, CUE_ETHCTL, CUE_ETHCTL_RX_ON | CUE_ETHCTL_MCAST_ON); + + /* Load the multicast filter */ + cue_setpromisc(ue); + + /* + * Set the number of RX and TX buffers that we want + * to reserve inside the ASIC. + */ + cue_csr_write_1(sc, CUE_RX_BUFPKTS, CUE_RX_FRAMES); + cue_csr_write_1(sc, CUE_TX_BUFPKTS, CUE_TX_FRAMES); + + /* Set advanced operation modes. */ + cue_csr_write_1(sc, CUE_ADVANCED_OPMODES, + CUE_AOP_EMBED_RXLEN | 0x01);/* 1 wait state */ + + /* Program the LED operation. */ + cue_csr_write_1(sc, CUE_LEDCTL, CUE_LEDCTL_FOLLOW_LINK); + + usbd_xfer_set_stall(sc->sc_xfer[CUE_BULK_DT_WR]); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + cue_start(ue); +} + +/* + * Stop the adapter and free any mbufs allocated to the + * RX and TX lists. + */ +static void +cue_stop(struct usb_ether *ue) +{ + struct cue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + CUE_LOCK_ASSERT(sc, MA_OWNED); + + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + + /* + * stop all the transfers, if not already stopped: + */ + usbd_transfer_stop(sc->sc_xfer[CUE_BULK_DT_WR]); + usbd_transfer_stop(sc->sc_xfer[CUE_BULK_DT_RD]); + + cue_csr_write_1(sc, CUE_ETHCTL, 0); + cue_reset(sc); +} diff --git a/sys/bus/u4b/net/if_cuereg.h b/sys/bus/u4b/net/if_cuereg.h new file mode 100644 index 0000000000..1782c2167f --- /dev/null +++ b/sys/bus/u4b/net/if_cuereg.h @@ -0,0 +1,132 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Definitions for the CATC Netmate II USB to ethernet controller. + */ + +/* Vendor specific control commands. */ +#define CUE_CMD_RESET 0xF4 +#define CUE_CMD_GET_MACADDR 0xF2 +#define CUE_CMD_WRITEREG 0xFA +#define CUE_CMD_READREG 0xFB +#define CUE_CMD_READSRAM 0xF1 +#define CUE_CMD_WRITESRAM 0xFC +/* Internal registers. */ +#define CUE_TX_BUFCNT 0x20 +#define CUE_RX_BUFCNT 0x21 +#define CUE_ADVANCED_OPMODES 0x22 +#define CUE_TX_BUFPKTS 0x23 +#define CUE_RX_BUFPKTS 0x24 +#define CUE_RX_MAXCHAIN 0x25 +#define CUE_ETHCTL 0x60 +#define CUE_ETHSTS 0x61 +#define CUE_PAR5 0x62 +#define CUE_PAR4 0x63 +#define CUE_PAR3 0x64 +#define CUE_PAR2 0x65 +#define CUE_PAR1 0x66 +#define CUE_PAR0 0x67 +/* Error counters, all 16 bits wide. */ +#define CUE_TX_SINGLECOLL 0x69 +#define CUE_TX_MULTICOLL 0x6B +#define CUE_TX_EXCESSCOLL 0x6D +#define CUE_RX_FRAMEERR 0x6F +#define CUE_LEDCTL 0x81 +/* Advenced operating mode register. */ +#define CUE_AOP_SRAMWAITS 0x03 +#define CUE_AOP_EMBED_RXLEN 0x08 +#define CUE_AOP_RXCOMBINE 0x10 +#define CUE_AOP_TXCOMBINE 0x20 +#define CUE_AOP_EVEN_PKT_READS 0x40 +#define CUE_AOP_LOOPBK 0x80 +/* Ethernet control register. */ +#define CUE_ETHCTL_RX_ON 0x01 +#define CUE_ETHCTL_LINK_POLARITY 0x02 +#define CUE_ETHCTL_LINK_FORCE_OK 0x04 +#define CUE_ETHCTL_MCAST_ON 0x08 +#define CUE_ETHCTL_PROMISC 0x10 +/* Ethernet status register. */ +#define CUE_ETHSTS_NO_CARRIER 0x01 +#define CUE_ETHSTS_LATECOLL 0x02 +#define CUE_ETHSTS_EXCESSCOLL 0x04 +#define CUE_ETHSTS_TXBUF_AVAIL 0x08 +#define CUE_ETHSTS_BAD_POLARITY 0x10 +#define CUE_ETHSTS_LINK_OK 0x20 +/* LED control register. */ +#define CUE_LEDCTL_BLINK_1X 0x00 +#define CUE_LEDCTL_BLINK_2X 0x01 +#define CUE_LEDCTL_BLINK_QUARTER_ON 0x02 +#define CUE_LEDCTL_BLINK_QUARTER_OFF 0x03 +#define CUE_LEDCTL_OFF 0x04 +#define CUE_LEDCTL_FOLLOW_LINK 0x08 + +/* + * Address in ASIC's internal SRAM where the multicast hash table lives. + * The table is 64 bytes long, giving us a 512-bit table. We have to set + * the bit that corresponds to the broadcast address in order to enable + * reception of broadcast frames. + */ +#define CUE_MCAST_TABLE_ADDR 0xFA80 + +#define CUE_TIMEOUT 1000 +#define CUE_MIN_FRAMELEN 60 +#define CUE_RX_FRAMES 1 +#define CUE_TX_FRAMES 1 + +#define CUE_CTL_READ 0x01 +#define CUE_CTL_WRITE 0x02 + +#define CUE_CONFIG_IDX 0 /* config number 1 */ +#define CUE_IFACE_IDX 0 + +/* The interrupt endpoint is currently unused by the KLSI part. */ +enum { + CUE_BULK_DT_WR, + CUE_BULK_DT_RD, + CUE_N_TRANSFER, +}; + +struct cue_softc { + struct usb_ether sc_ue; + struct mtx sc_mtx; + struct usb_xfer *sc_xfer[CUE_N_TRANSFER]; + + int sc_flags; +#define CUE_FLAG_LINK 0x0001 /* got a link */ +}; + +#define CUE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define CUE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define CUE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t) diff --git a/sys/bus/u4b/net/if_ipheth.c b/sys/bus/u4b/net/if_ipheth.c new file mode 100644 index 0000000000..ad4b6f1226 --- /dev/null +++ b/sys/bus/u4b/net/if_ipheth.c @@ -0,0 +1,527 @@ +/*- + * Copyright (c) 2010 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2009 Diego Giagio. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Thanks to Diego Giagio for figuring out the programming details for + * the Apple iPhone Ethernet driver. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR ipheth_debug +#include +#include + +#include +#include + +static device_probe_t ipheth_probe; +static device_attach_t ipheth_attach; +static device_detach_t ipheth_detach; + +static usb_callback_t ipheth_bulk_write_callback; +static usb_callback_t ipheth_bulk_read_callback; + +static uether_fn_t ipheth_attach_post; +static uether_fn_t ipheth_tick; +static uether_fn_t ipheth_init; +static uether_fn_t ipheth_stop; +static uether_fn_t ipheth_start; +static uether_fn_t ipheth_setmulti; +static uether_fn_t ipheth_setpromisc; + +#ifdef USB_DEBUG +static int ipheth_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ipheth, CTLFLAG_RW, 0, "USB iPhone ethernet"); +SYSCTL_INT(_hw_usb_ipheth, OID_AUTO, debug, CTLFLAG_RW, &ipheth_debug, 0, "Debug level"); +#endif + +static const struct usb_config ipheth_config[IPHETH_N_TRANSFER] = { + + [IPHETH_BULK_RX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_RX, + .frames = IPHETH_RX_FRAMES_MAX, + .bufsize = (IPHETH_RX_FRAMES_MAX * MCLBYTES), + .flags = {.short_frames_ok = 1,.short_xfer_ok = 1,.ext_buffer = 1,}, + .callback = ipheth_bulk_read_callback, + .timeout = 0, /* no timeout */ + }, + + [IPHETH_BULK_TX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_TX, + .frames = IPHETH_TX_FRAMES_MAX, + .bufsize = (IPHETH_TX_FRAMES_MAX * IPHETH_BUF_SIZE), + .flags = {.force_short_xfer = 1,}, + .callback = ipheth_bulk_write_callback, + .timeout = IPHETH_TX_TIMEOUT, + }, +}; + +static device_method_t ipheth_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ipheth_probe), + DEVMETHOD(device_attach, ipheth_attach), + DEVMETHOD(device_detach, ipheth_detach), + + {0, 0} +}; + +static driver_t ipheth_driver = { + .name = "ipheth", + .methods = ipheth_methods, + .size = sizeof(struct ipheth_softc), +}; + +static devclass_t ipheth_devclass; + +DRIVER_MODULE(ipheth, uhub, ipheth_driver, ipheth_devclass, NULL, 0); +MODULE_VERSION(ipheth, 1); +MODULE_DEPEND(ipheth, uether, 1, 1, 1); +MODULE_DEPEND(ipheth, usb, 1, 1, 1); +MODULE_DEPEND(ipheth, ether, 1, 1, 1); + +static const struct usb_ether_methods ipheth_ue_methods = { + .ue_attach_post = ipheth_attach_post, + .ue_start = ipheth_start, + .ue_init = ipheth_init, + .ue_tick = ipheth_tick, + .ue_stop = ipheth_stop, + .ue_setmulti = ipheth_setmulti, + .ue_setpromisc = ipheth_setpromisc, +}; + +#define IPHETH_ID(v,p,c,sc,pt) \ + USB_VENDOR(v), USB_PRODUCT(p), \ + USB_IFACE_CLASS(c), USB_IFACE_SUBCLASS(sc), \ + USB_IFACE_PROTOCOL(pt) + +static const STRUCT_USB_HOST_ID ipheth_devs[] = { + {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE, + IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, + IPHETH_USBINTF_PROTO)}, + {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_3G, + IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, + IPHETH_USBINTF_PROTO)}, + {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_3GS, + IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, + IPHETH_USBINTF_PROTO)}, + {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_4, + IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, + IPHETH_USBINTF_PROTO)}, +}; + +static int +ipheth_get_mac_addr(struct ipheth_softc *sc) +{ + struct usb_device_request req; + int error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = IPHETH_CMD_GET_MACADDR; + req.wValue[0] = 0; + req.wValue[1] = 0; + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + req.wLength[0] = ETHER_ADDR_LEN; + req.wLength[1] = 0; + + error = usbd_do_request(sc->sc_ue.ue_udev, NULL, &req, sc->sc_data); + + if (error) + return (error); + + memcpy(sc->sc_ue.ue_eaddr, sc->sc_data, ETHER_ADDR_LEN); + + return (0); +} + +static int +ipheth_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(ipheth_devs, sizeof(ipheth_devs), uaa)); +} + +static int +ipheth_attach(device_t dev) +{ + struct ipheth_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + struct usb_attach_arg *uaa = device_get_ivars(dev); + int error; + + sc->sc_iface_no = uaa->info.bIfaceIndex; + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + error = usbd_set_alt_interface_index(uaa->device, + uaa->info.bIfaceIndex, IPHETH_ALT_INTFNUM); + if (error) { + device_printf(dev, "Cannot set alternate setting\n"); + goto detach; + } + error = usbd_transfer_setup(uaa->device, &sc->sc_iface_no, + sc->sc_xfer, ipheth_config, IPHETH_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "Cannot setup USB transfers\n"); + goto detach; + } + ue->ue_sc = sc; + ue->ue_dev = dev; + ue->ue_udev = uaa->device; + ue->ue_mtx = &sc->sc_mtx; + ue->ue_methods = &ipheth_ue_methods; + + error = ipheth_get_mac_addr(sc); + if (error) { + device_printf(dev, "Cannot get MAC address\n"); + goto detach; + } + + error = uether_ifattach(ue); + if (error) { + device_printf(dev, "could not attach interface\n"); + goto detach; + } + return (0); /* success */ + +detach: + ipheth_detach(dev); + return (ENXIO); /* failure */ +} + +static int +ipheth_detach(device_t dev) +{ + struct ipheth_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + + /* stop all USB transfers first */ + usbd_transfer_unsetup(sc->sc_xfer, IPHETH_N_TRANSFER); + + uether_ifdetach(ue); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +ipheth_start(struct usb_ether *ue) +{ + struct ipheth_softc *sc = uether_getsc(ue); + + /* + * Start the USB transfers, if not already started: + */ + usbd_transfer_start(sc->sc_xfer[IPHETH_BULK_TX]); + usbd_transfer_start(sc->sc_xfer[IPHETH_BULK_RX]); +} + +static void +ipheth_stop(struct usb_ether *ue) +{ + struct ipheth_softc *sc = uether_getsc(ue); + + /* + * Stop the USB transfers, if not already stopped: + */ + usbd_transfer_stop(sc->sc_xfer[IPHETH_BULK_TX]); + usbd_transfer_stop(sc->sc_xfer[IPHETH_BULK_RX]); +} + +static void +ipheth_tick(struct usb_ether *ue) +{ + struct ipheth_softc *sc = uether_getsc(ue); + struct usb_device_request req; + int error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = IPHETH_CMD_CARRIER_CHECK; + req.wValue[0] = 0; + req.wValue[1] = 0; + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + req.wLength[0] = IPHETH_CTRL_BUF_SIZE; + req.wLength[1] = 0; + + error = uether_do_request(ue, &req, sc->sc_data, IPHETH_CTRL_TIMEOUT); + + if (error) + return; + + sc->sc_carrier_on = + (sc->sc_data[0] == IPHETH_CARRIER_ON); +} + +static void +ipheth_attach_post(struct usb_ether *ue) +{ + +} + +static void +ipheth_init(struct usb_ether *ue) +{ + struct ipheth_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + IPHETH_LOCK_ASSERT(sc, MA_OWNED); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + /* stall data write direction, which depends on USB mode */ + usbd_xfer_set_stall(sc->sc_xfer[IPHETH_BULK_TX]); + + /* start data transfers */ + ipheth_start(ue); +} + +static void +ipheth_setmulti(struct usb_ether *ue) +{ + +} + +static void +ipheth_setpromisc(struct usb_ether *ue) +{ + +} + +static void +ipheth_free_queue(struct mbuf **ppm, uint8_t n) +{ + uint8_t x; + + for (x = 0; x != n; x++) { + if (ppm[x] != NULL) { + m_freem(ppm[x]); + ppm[x] = NULL; + } + } +} + +static void +ipheth_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ipheth_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct usb_page_cache *pc; + struct mbuf *m; + uint8_t x; + int actlen; + int aframes; + + usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); + + DPRINTFN(1, "\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete: %u bytes in %u frames\n", + actlen, aframes); + + ifp->if_opackets++; + + /* free all previous TX buffers */ + ipheth_free_queue(sc->sc_tx_buf, IPHETH_TX_FRAMES_MAX); + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + for (x = 0; x != IPHETH_TX_FRAMES_MAX; x++) { + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) + break; + + usbd_xfer_set_frame_offset(xfer, + x * IPHETH_BUF_SIZE, x); + + pc = usbd_xfer_get_frame(xfer, x); + + sc->sc_tx_buf[x] = m; + + if (m->m_pkthdr.len > IPHETH_BUF_SIZE) + m->m_pkthdr.len = IPHETH_BUF_SIZE; + + usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); + + usbd_xfer_set_frame_len(xfer, x, IPHETH_BUF_SIZE); + + if (IPHETH_BUF_SIZE != m->m_pkthdr.len) { + usbd_frame_zero(pc, m->m_pkthdr.len, + IPHETH_BUF_SIZE - m->m_pkthdr.len); + } + + /* + * If there's a BPF listener, bounce a copy of + * this frame to him: + */ + BPF_MTAP(ifp, m); + } + if (x != 0) { + usbd_xfer_set_frames(xfer, x); + + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usbd_errstr(error)); + + /* free all previous TX buffers */ + ipheth_free_queue(sc->sc_tx_buf, IPHETH_TX_FRAMES_MAX); + + /* count output errors */ + ifp->if_oerrors++; + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +ipheth_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ipheth_softc *sc = usbd_xfer_softc(xfer); + struct mbuf *m; + uint8_t x; + int actlen; + int aframes; + int len; + + usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("received %u bytes in %u frames\n", actlen, aframes); + + for (x = 0; x != aframes; x++) { + + m = sc->sc_rx_buf[x]; + sc->sc_rx_buf[x] = NULL; + len = usbd_xfer_frame_len(xfer, x); + + if (len < (sizeof(struct ether_header) + + IPHETH_RX_ADJ)) { + m_freem(m); + continue; + } + + m_adj(m, IPHETH_RX_ADJ); + + /* queue up mbuf */ + uether_rxmbuf(&sc->sc_ue, m, len - IPHETH_RX_ADJ); + } + + /* FALLTHROUGH */ + case USB_ST_SETUP: + + for (x = 0; x != IPHETH_RX_FRAMES_MAX; x++) { + if (sc->sc_rx_buf[x] == NULL) { + m = uether_newbuf(); + if (m == NULL) + goto tr_stall; + + /* cancel alignment for ethernet */ + m_adj(m, ETHER_ALIGN); + + sc->sc_rx_buf[x] = m; + } else { + m = sc->sc_rx_buf[x]; + } + + usbd_xfer_set_frame_data(xfer, x, m->m_data, m->m_len); + } + /* set number of frames and start hardware */ + usbd_xfer_set_frames(xfer, x); + usbd_transfer_submit(xfer); + /* flush any received frames */ + uether_rxflush(&sc->sc_ue); + break; + + default: /* Error */ + DPRINTF("error = %s\n", usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + tr_stall: + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + usbd_xfer_set_frames(xfer, 0); + usbd_transfer_submit(xfer); + break; + } + /* need to free the RX-mbufs when we are cancelled */ + ipheth_free_queue(sc->sc_rx_buf, IPHETH_RX_FRAMES_MAX); + break; + } +} diff --git a/sys/bus/u4b/net/if_iphethvar.h b/sys/bus/u4b/net/if_iphethvar.h new file mode 100644 index 0000000000..65b0c940c6 --- /dev/null +++ b/sys/bus/u4b/net/if_iphethvar.h @@ -0,0 +1,84 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2010 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2009 Diego Giagio. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Thanks to Diego Giagio for figuring out the programming details for + * the Apple iPhone Ethernet driver. + */ + +#ifndef _IF_IPHETHVAR_H_ +#define _IF_IPHETHVAR_H_ + +#define IPHETH_USBINTF_CLASS 255 +#define IPHETH_USBINTF_SUBCLASS 253 +#define IPHETH_USBINTF_PROTO 1 + +#define IPHETH_BUF_SIZE 1516 +#define IPHETH_TX_TIMEOUT 5000 /* ms */ + +#define IPHETH_RX_FRAMES_MAX 1 +#define IPHETH_TX_FRAMES_MAX 8 + +#define IPHETH_RX_ADJ 2 + +#define IPHETH_CFG_INDEX 0 +#define IPHETH_IF_INDEX 2 +#define IPHETH_ALT_INTFNUM 1 + +#define IPHETH_CTRL_ENDP 0x00 +#define IPHETH_CTRL_BUF_SIZE 0x40 +#define IPHETH_CTRL_TIMEOUT 5000 /* ms */ + +#define IPHETH_CMD_GET_MACADDR 0x00 +#define IPHETH_CMD_CARRIER_CHECK 0x45 + +#define IPHETH_CARRIER_ON 0x04 + +enum { + IPHETH_BULK_TX, + IPHETH_BULK_RX, + IPHETH_N_TRANSFER, +}; + +struct ipheth_softc { + struct usb_ether sc_ue; + struct mtx sc_mtx; + + struct usb_xfer *sc_xfer[IPHETH_N_TRANSFER]; + struct mbuf *sc_rx_buf[IPHETH_RX_FRAMES_MAX]; + struct mbuf *sc_tx_buf[IPHETH_TX_FRAMES_MAX]; + + uint8_t sc_data[IPHETH_CTRL_BUF_SIZE]; + uint8_t sc_iface_no; + uint8_t sc_carrier_on; +}; + +#define IPHETH_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define IPHETH_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define IPHETH_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t) + +#endif /* _IF_IPHETHVAR_H_ */ diff --git a/sys/bus/u4b/net/if_kue.c b/sys/bus/u4b/net/if_kue.c new file mode 100644 index 0000000000..2d3fc422a9 --- /dev/null +++ b/sys/bus/u4b/net/if_kue.c @@ -0,0 +1,707 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Kawasaki LSI KL5KUSB101B USB to ethernet adapter driver. + * + * Written by Bill Paul + * Electrical Engineering Department + * Columbia University, New York City + */ + +/* + * The KLSI USB to ethernet adapter chip contains an USB serial interface, + * ethernet MAC and embedded microcontroller (called the QT Engine). + * The chip must have firmware loaded into it before it will operate. + * Packets are passed between the chip and host via bulk transfers. + * There is an interrupt endpoint mentioned in the software spec, however + * it's currently unused. This device is 10Mbps half-duplex only, hence + * there is no media selection logic. The MAC supports a 128 entry + * multicast filter, though the exact size of the filter can depend + * on the firmware. Curiously, while the software spec describes various + * ethernet statistics counters, my sample adapter and firmware combination + * claims not to support any statistics counters at all. + * + * Note that once we load the firmware in the device, we have to be + * careful not to load it again: if you restart your computer but + * leave the adapter attached to the USB controller, it may remain + * powered on and retain its firmware. In this case, we don't need + * to load the firmware a second time. + * + * Special thanks to Rob Furr for providing an ADS Technologies + * adapter for development and testing. No monkeys were harmed during + * the development of this driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR kue_debug +#include +#include + +#include +#include +#include + +/* + * Various supported device vendors/products. + */ +static const STRUCT_USB_HOST_ID kue_devs[] = { +#define KUE_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } + KUE_DEV(3COM, 3C19250), + KUE_DEV(3COM, 3C460), + KUE_DEV(ABOCOM, URE450), + KUE_DEV(ADS, UBS10BT), + KUE_DEV(ADS, UBS10BTX), + KUE_DEV(AOX, USB101), + KUE_DEV(ASANTE, EA), + KUE_DEV(ATEN, DSB650C), + KUE_DEV(ATEN, UC10T), + KUE_DEV(COREGA, ETHER_USB_T), + KUE_DEV(DLINK, DSB650C), + KUE_DEV(ENTREGA, E45), + KUE_DEV(ENTREGA, XX1), + KUE_DEV(ENTREGA, XX2), + KUE_DEV(IODATA, USBETT), + KUE_DEV(JATON, EDA), + KUE_DEV(KINGSTON, XX1), + KUE_DEV(KLSI, DUH3E10BT), + KUE_DEV(KLSI, DUH3E10BTN), + KUE_DEV(LINKSYS, USB10T), + KUE_DEV(MOBILITY, EA), + KUE_DEV(NETGEAR, EA101), + KUE_DEV(NETGEAR, EA101X), + KUE_DEV(PERACOM, ENET), + KUE_DEV(PERACOM, ENET2), + KUE_DEV(PERACOM, ENET3), + KUE_DEV(PORTGEAR, EA8), + KUE_DEV(PORTGEAR, EA9), + KUE_DEV(PORTSMITH, EEA), + KUE_DEV(SHARK, PA), + KUE_DEV(SILICOM, GPE), + KUE_DEV(SILICOM, U2E), + KUE_DEV(SMC, 2102USB), +#undef KUE_DEV +}; + +/* prototypes */ + +static device_probe_t kue_probe; +static device_attach_t kue_attach; +static device_detach_t kue_detach; + +static usb_callback_t kue_bulk_read_callback; +static usb_callback_t kue_bulk_write_callback; + +static uether_fn_t kue_attach_post; +static uether_fn_t kue_init; +static uether_fn_t kue_stop; +static uether_fn_t kue_start; +static uether_fn_t kue_setmulti; +static uether_fn_t kue_setpromisc; + +static int kue_do_request(struct kue_softc *, + struct usb_device_request *, void *); +static int kue_setword(struct kue_softc *, uint8_t, uint16_t); +static int kue_ctl(struct kue_softc *, uint8_t, uint8_t, uint16_t, + void *, int); +static int kue_load_fw(struct kue_softc *); +static void kue_reset(struct kue_softc *); + +#ifdef USB_DEBUG +static int kue_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, kue, CTLFLAG_RW, 0, "USB kue"); +SYSCTL_INT(_hw_usb_kue, OID_AUTO, debug, CTLFLAG_RW, &kue_debug, 0, + "Debug level"); +#endif + +static const struct usb_config kue_config[KUE_N_TRANSFER] = { + + [KUE_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = (MCLBYTES + 2 + 64), + .flags = {.pipe_bof = 1,}, + .callback = kue_bulk_write_callback, + .timeout = 10000, /* 10 seconds */ + }, + + [KUE_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = (MCLBYTES + 2), + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = kue_bulk_read_callback, + .timeout = 0, /* no timeout */ + }, +}; + +static device_method_t kue_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, kue_probe), + DEVMETHOD(device_attach, kue_attach), + DEVMETHOD(device_detach, kue_detach), + + {0, 0} +}; + +static driver_t kue_driver = { + .name = "kue", + .methods = kue_methods, + .size = sizeof(struct kue_softc), +}; + +static devclass_t kue_devclass; + +DRIVER_MODULE(kue, uhub, kue_driver, kue_devclass, NULL, 0); +MODULE_DEPEND(kue, uether, 1, 1, 1); +MODULE_DEPEND(kue, usb, 1, 1, 1); +MODULE_DEPEND(kue, ether, 1, 1, 1); +MODULE_VERSION(kue, 1); + +static const struct usb_ether_methods kue_ue_methods = { + .ue_attach_post = kue_attach_post, + .ue_start = kue_start, + .ue_init = kue_init, + .ue_stop = kue_stop, + .ue_setmulti = kue_setmulti, + .ue_setpromisc = kue_setpromisc, +}; + +/* + * We have a custom do_request function which is almost like the + * regular do_request function, except it has a much longer timeout. + * Why? Because we need to make requests over the control endpoint + * to download the firmware to the device, which can take longer + * than the default timeout. + */ +static int +kue_do_request(struct kue_softc *sc, struct usb_device_request *req, + void *data) +{ + usb_error_t err; + + err = uether_do_request(&sc->sc_ue, req, data, 60000); + + return (err); +} + +static int +kue_setword(struct kue_softc *sc, uint8_t breq, uint16_t word) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = breq; + USETW(req.wValue, word); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + return (kue_do_request(sc, &req, NULL)); +} + +static int +kue_ctl(struct kue_softc *sc, uint8_t rw, uint8_t breq, + uint16_t val, void *data, int len) +{ + struct usb_device_request req; + + if (rw == KUE_CTL_WRITE) + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + else + req.bmRequestType = UT_READ_VENDOR_DEVICE; + + + req.bRequest = breq; + USETW(req.wValue, val); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + + return (kue_do_request(sc, &req, data)); +} + +static int +kue_load_fw(struct kue_softc *sc) +{ + struct usb_device_descriptor *dd; + uint16_t hwrev; + usb_error_t err; + + dd = usbd_get_device_descriptor(sc->sc_ue.ue_udev); + hwrev = UGETW(dd->bcdDevice); + + /* + * First, check if we even need to load the firmware. + * If the device was still attached when the system was + * rebooted, it may already have firmware loaded in it. + * If this is the case, we don't need to do it again. + * And in fact, if we try to load it again, we'll hang, + * so we have to avoid this condition if we don't want + * to look stupid. + * + * We can test this quickly by checking the bcdRevision + * code. The NIC will return a different revision code if + * it's probed while the firmware is still loaded and + * running. + */ + if (hwrev == 0x0202) + return(0); + + /* Load code segment */ + err = kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SEND_SCAN, + 0, kue_code_seg, sizeof(kue_code_seg)); + if (err) { + device_printf(sc->sc_ue.ue_dev, "failed to load code segment: %s\n", + usbd_errstr(err)); + return(ENXIO); + } + + /* Load fixup segment */ + err = kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SEND_SCAN, + 0, kue_fix_seg, sizeof(kue_fix_seg)); + if (err) { + device_printf(sc->sc_ue.ue_dev, "failed to load fixup segment: %s\n", + usbd_errstr(err)); + return(ENXIO); + } + + /* Send trigger command. */ + err = kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SEND_SCAN, + 0, kue_trig_seg, sizeof(kue_trig_seg)); + if (err) { + device_printf(sc->sc_ue.ue_dev, "failed to load trigger segment: %s\n", + usbd_errstr(err)); + return(ENXIO); + } + + return (0); +} + +static void +kue_setpromisc(struct usb_ether *ue) +{ + struct kue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + KUE_LOCK_ASSERT(sc, MA_OWNED); + + if (ifp->if_flags & IFF_PROMISC) + sc->sc_rxfilt |= KUE_RXFILT_PROMISC; + else + sc->sc_rxfilt &= ~KUE_RXFILT_PROMISC; + + kue_setword(sc, KUE_CMD_SET_PKT_FILTER, sc->sc_rxfilt); +} + +static void +kue_setmulti(struct usb_ether *ue) +{ + struct kue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + struct ifmultiaddr *ifma; + int i = 0; + + KUE_LOCK_ASSERT(sc, MA_OWNED); + + if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { + sc->sc_rxfilt |= KUE_RXFILT_ALLMULTI; + sc->sc_rxfilt &= ~KUE_RXFILT_MULTICAST; + kue_setword(sc, KUE_CMD_SET_PKT_FILTER, sc->sc_rxfilt); + return; + } + + sc->sc_rxfilt &= ~KUE_RXFILT_ALLMULTI; + + if_maddr_rlock(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) + { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + /* + * If there are too many addresses for the + * internal filter, switch over to allmulti mode. + */ + if (i == KUE_MCFILTCNT(sc)) + break; + memcpy(KUE_MCFILT(sc, i), + LLADDR((struct sockaddr_dl *)ifma->ifma_addr), + ETHER_ADDR_LEN); + i++; + } + if_maddr_runlock(ifp); + + if (i == KUE_MCFILTCNT(sc)) + sc->sc_rxfilt |= KUE_RXFILT_ALLMULTI; + else { + sc->sc_rxfilt |= KUE_RXFILT_MULTICAST; + kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SET_MCAST_FILTERS, + i, sc->sc_mcfilters, i * ETHER_ADDR_LEN); + } + + kue_setword(sc, KUE_CMD_SET_PKT_FILTER, sc->sc_rxfilt); +} + +/* + * Issue a SET_CONFIGURATION command to reset the MAC. This should be + * done after the firmware is loaded into the adapter in order to + * bring it into proper operation. + */ +static void +kue_reset(struct kue_softc *sc) +{ + struct usb_config_descriptor *cd; + usb_error_t err; + + cd = usbd_get_config_descriptor(sc->sc_ue.ue_udev); + + err = usbd_req_set_config(sc->sc_ue.ue_udev, &sc->sc_mtx, + cd->bConfigurationValue); + if (err) + DPRINTF("reset failed (ignored)\n"); + + /* wait a little while for the chip to get its brains in order */ + uether_pause(&sc->sc_ue, hz / 100); +} + +static void +kue_attach_post(struct usb_ether *ue) +{ + struct kue_softc *sc = uether_getsc(ue); + int error; + + /* load the firmware into the NIC */ + error = kue_load_fw(sc); + if (error) { + device_printf(sc->sc_ue.ue_dev, "could not load firmware\n"); + /* ignore the error */ + } + + /* reset the adapter */ + kue_reset(sc); + + /* read ethernet descriptor */ + kue_ctl(sc, KUE_CTL_READ, KUE_CMD_GET_ETHER_DESCRIPTOR, + 0, &sc->sc_desc, sizeof(sc->sc_desc)); + + /* copy in ethernet address */ + memcpy(ue->ue_eaddr, sc->sc_desc.kue_macaddr, sizeof(ue->ue_eaddr)); +} + +/* + * Probe for a KLSI chip. + */ +static int +kue_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != KUE_CONFIG_IDX) + return (ENXIO); + if (uaa->info.bIfaceIndex != KUE_IFACE_IDX) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(kue_devs, sizeof(kue_devs), uaa)); +} + +/* + * Attach the interface. Allocate softc structures, do + * setup and ethernet/BPF attach. + */ +static int +kue_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct kue_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + uint8_t iface_index; + int error; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + iface_index = KUE_IFACE_IDX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, kue_config, KUE_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB transfers failed\n"); + goto detach; + } + + sc->sc_mcfilters = malloc(KUE_MCFILTCNT(sc) * ETHER_ADDR_LEN, + M_USBDEV, M_WAITOK); + if (sc->sc_mcfilters == NULL) { + device_printf(dev, "failed allocating USB memory\n"); + goto detach; + } + + ue->ue_sc = sc; + ue->ue_dev = dev; + ue->ue_udev = uaa->device; + ue->ue_mtx = &sc->sc_mtx; + ue->ue_methods = &kue_ue_methods; + + error = uether_ifattach(ue); + if (error) { + device_printf(dev, "could not attach interface\n"); + goto detach; + } + return (0); /* success */ + +detach: + kue_detach(dev); + return (ENXIO); /* failure */ +} + +static int +kue_detach(device_t dev) +{ + struct kue_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + + usbd_transfer_unsetup(sc->sc_xfer, KUE_N_TRANSFER); + uether_ifdetach(ue); + mtx_destroy(&sc->sc_mtx); + free(sc->sc_mcfilters, M_USBDEV); + + return (0); +} + +/* + * A frame has been uploaded: pass the resulting mbuf chain up to + * the higher level protocols. + */ +static void +kue_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct kue_softc *sc = usbd_xfer_softc(xfer); + struct usb_ether *ue = &sc->sc_ue; + struct ifnet *ifp = uether_getifp(ue); + struct usb_page_cache *pc; + uint8_t buf[2]; + int len; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen <= (2 + sizeof(struct ether_header))) { + ifp->if_ierrors++; + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, 2); + actlen -= 2; + len = buf[0] | (buf[1] << 8); + len = min(actlen, len); + + uether_rxbuf(ue, pc, 2, len); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + uether_rxflush(ue); + return; + + default: /* Error */ + DPRINTF("bulk read error, %s\n", + usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +kue_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct kue_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct usb_page_cache *pc; + struct mbuf *m; + int total_len; + int temp_len; + uint8_t buf[2]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + ifp->if_opackets++; + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) + return; + if (m->m_pkthdr.len > MCLBYTES) + m->m_pkthdr.len = MCLBYTES; + temp_len = (m->m_pkthdr.len + 2); + total_len = (temp_len + (64 - (temp_len % 64))); + + /* the first two bytes are the frame length */ + + buf[0] = (uint8_t)(m->m_pkthdr.len); + buf[1] = (uint8_t)(m->m_pkthdr.len >> 8); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, buf, 2); + usbd_m_copy_in(pc, 2, m, 0, m->m_pkthdr.len); + + usbd_frame_zero(pc, temp_len, total_len - temp_len); + usbd_xfer_set_frame_len(xfer, 0, total_len); + + /* + * if there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + + m_freem(m); + + usbd_transfer_submit(xfer); + + return; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usbd_errstr(error)); + + ifp->if_oerrors++; + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +kue_start(struct usb_ether *ue) +{ + struct kue_softc *sc = uether_getsc(ue); + + /* + * start the USB transfers, if not already started: + */ + usbd_transfer_start(sc->sc_xfer[KUE_BULK_DT_RD]); + usbd_transfer_start(sc->sc_xfer[KUE_BULK_DT_WR]); +} + +static void +kue_init(struct usb_ether *ue) +{ + struct kue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + KUE_LOCK_ASSERT(sc, MA_OWNED); + + /* set MAC address */ + kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SET_MAC, + 0, IF_LLADDR(ifp), ETHER_ADDR_LEN); + + /* I'm not sure how to tune these. */ +#if 0 + /* + * Leave this one alone for now; setting it + * wrong causes lockups on some machines/controllers. + */ + kue_setword(sc, KUE_CMD_SET_SOFS, 1); +#endif + kue_setword(sc, KUE_CMD_SET_URB_SIZE, 64); + + /* load the multicast filter */ + kue_setpromisc(ue); + + usbd_xfer_set_stall(sc->sc_xfer[KUE_BULK_DT_WR]); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + kue_start(ue); +} + +static void +kue_stop(struct usb_ether *ue) +{ + struct kue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + KUE_LOCK_ASSERT(sc, MA_OWNED); + + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + + /* + * stop all the transfers, if not already stopped: + */ + usbd_transfer_stop(sc->sc_xfer[KUE_BULK_DT_WR]); + usbd_transfer_stop(sc->sc_xfer[KUE_BULK_DT_RD]); +} diff --git a/sys/bus/u4b/net/if_kuefw.h b/sys/bus/u4b/net/if_kuefw.h new file mode 100644 index 0000000000..2b055a92ed --- /dev/null +++ b/sys/bus/u4b/net/if_kuefw.h @@ -0,0 +1,685 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * This file contains the firmware needed to make the KLSI chip work, + * along with a few constants related to the QT Engine microcontroller + * embedded in the KLSI part. + * + * Firmware is loaded using the vendor-specific 'send scan data' + * command (0xFF). The basic operation is that we must load the + * firmware, then issue some trigger commands to fix it up and start + * it running. There are three transfers: load the binary code, + * load the 'fixup' (data segment?), then issue a command to + * start the code firmware running. The data itself is prefixed by + * a 16-bit signature word, a 16-bit length value, a type byte + * and an interrupt (command) byte. The code segment is of type + * 0x02 (replacement interrupt vector data) and the fixup segment + * is of type 0x03 (replacement interrupt fixup data). The interrupt + * code is 0x64 (load new code). The length word is the total length + * of the segment minus 7. I precomputed the values and stuck them + * into the appropriate locations within the segments to save some + * work in the driver. + */ + +/* QT controller data block types. */ +/* Write data into specific memory location. */ +#define KUE_QTBTYPE_WRITE_DATA 0x00 +/* Write data into interrupt vector location */ +#define KUE_QTBTYPE_WRITE_INTVEC 0x01 +/* Replace interrupt vector with this data */ +#define KUE_QTBTYPE_REPL_INTVEC 0x02 +/* Fixup interrupt vector code with this data */ +#define KUE_QTBTYPE_FIXUP_INTVEC 0x03 +/* Force jump to location */ +#define KUE_QTBTYPE_JUMP 0x04 +/* Force call to location */ +#define KUE_QTBTYPE_CALL 0x05 +/* Force interrupt call */ +#define KUE_QTBTYPE_CALLINTR 0x06 +/* + * Cause data to be written using the specified QT engine + * interrupt, from starting location in memory for a specified + * number of bytes. + */ +#define KUE_QTBTYPE_WRITE_WITH_INTR 0x07 +/* Cause data from stream to be written using specified QT interrupt. */ +#define KUE_QTBTYPE_WRITE_STR_WITH_INTR 0x08 +/* Cause data to be written to config locations. */ +/* Addresses assume 0xc000 offset. */ +#define KUE_QTBTYPE_WRITE_CONFIG 0x09 + +#define KUE_QTINTR_LOAD_CODE 0x64 +#define KUE_QTINTR_TRIGGER_CODE 0x3B +#define KUE_QTINTR_LOAD_CODE_HIGH 0x9C + +/* Firmware code segment */ +static unsigned char kue_code_seg[] = +{ + /******************************************/ + /* NOTE: B6/C3 is data header signature */ + /* 0xAA/0xBB is data length = total */ + /* bytes - 7, 0xCC is type, 0xDD is */ + /* interrupt to use. */ + /******************************************/ + 0xB6, 0xC3, 0xf7, 0x0e, 0x02, 0x64, + 0x9f, 0xcf, 0xbc, 0x08, 0xe7, 0x57, 0x00, 0x00, + 0x9a, 0x08, 0x97, 0xc1, 0xe7, 0x67, 0xff, 0x1f, + 0x28, 0xc0, 0xe7, 0x87, 0x00, 0x04, 0x24, 0xc0, + 0xe7, 0x67, 0xff, 0xf9, 0x22, 0xc0, 0x97, 0xcf, + 0xe7, 0x09, 0xa2, 0xc0, 0x94, 0x08, 0xd7, 0x09, + 0x00, 0xc0, 0xe7, 0x59, 0xba, 0x08, 0x94, 0x08, + 0x03, 0xc1, 0xe7, 0x67, 0xff, 0xf7, 0x24, 0xc0, + 0xe7, 0x05, 0x00, 0xc0, 0xa7, 0xcf, 0x92, 0x08, + 0xe7, 0x57, 0x00, 0x00, 0x8e, 0x08, 0xa7, 0xa1, + 0x8e, 0x08, 0x97, 0xcf, 0xe7, 0x57, 0x00, 0x00, + 0xf2, 0x09, 0x0a, 0xc0, 0xe7, 0x57, 0x00, 0x00, + 0xa4, 0xc0, 0xa7, 0xc0, 0x56, 0x08, 0x9f, 0xaf, + 0x70, 0x09, 0xe7, 0x07, 0x00, 0x00, 0xf2, 0x09, + 0xe7, 0x57, 0xff, 0xff, 0x90, 0x08, 0x9f, 0xa0, + 0x40, 0x00, 0xe7, 0x59, 0x90, 0x08, 0x94, 0x08, + 0x9f, 0xa0, 0x40, 0x00, 0xc8, 0x09, 0xa2, 0x08, + 0x08, 0x62, 0x9f, 0xa1, 0x14, 0x0a, 0xe7, 0x57, + 0x00, 0x00, 0x52, 0x08, 0xa7, 0xc0, 0x56, 0x08, + 0x9f, 0xaf, 0x04, 0x00, 0xe7, 0x57, 0x00, 0x00, + 0x8e, 0x08, 0xa7, 0xc1, 0x56, 0x08, 0xc0, 0x09, + 0xa8, 0x08, 0x00, 0x60, 0x05, 0xc4, 0xc0, 0x59, + 0x94, 0x08, 0x02, 0xc0, 0x9f, 0xaf, 0xee, 0x00, + 0xe7, 0x59, 0xae, 0x08, 0x94, 0x08, 0x02, 0xc1, + 0x9f, 0xaf, 0xf6, 0x00, 0x9f, 0xaf, 0x9e, 0x03, + 0xef, 0x57, 0x00, 0x00, 0xf0, 0x09, 0x9f, 0xa1, + 0xde, 0x01, 0xe7, 0x57, 0x00, 0x00, 0x78, 0x08, + 0x9f, 0xa0, 0xe4, 0x03, 0x9f, 0xaf, 0x2c, 0x04, + 0xa7, 0xcf, 0x56, 0x08, 0x48, 0x02, 0xe7, 0x09, + 0x94, 0x08, 0xa8, 0x08, 0xc8, 0x37, 0x04, 0x00, + 0x9f, 0xaf, 0x68, 0x04, 0x97, 0xcf, 0xe7, 0x57, + 0x00, 0x00, 0xa6, 0x08, 0x97, 0xc0, 0xd7, 0x09, + 0x00, 0xc0, 0xc1, 0xdf, 0xc8, 0x09, 0x9c, 0x08, + 0x08, 0x62, 0x1d, 0xc0, 0x27, 0x04, 0x9c, 0x08, + 0x10, 0x94, 0xf0, 0x07, 0xee, 0x09, 0x02, 0x00, + 0xc1, 0x07, 0x01, 0x00, 0x70, 0x00, 0x04, 0x00, + 0xf0, 0x07, 0x44, 0x01, 0x06, 0x00, 0x50, 0xaf, + 0xe7, 0x09, 0x94, 0x08, 0xae, 0x08, 0xe7, 0x17, + 0x14, 0x00, 0xae, 0x08, 0xe7, 0x67, 0xff, 0x07, + 0xae, 0x08, 0xe7, 0x07, 0xff, 0xff, 0xa8, 0x08, + 0xe7, 0x07, 0x00, 0x00, 0xa6, 0x08, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0xd7, 0x09, 0x00, 0xc0, + 0xc1, 0xdf, 0x48, 0x02, 0xd0, 0x09, 0x9c, 0x08, + 0x27, 0x02, 0x9c, 0x08, 0xe7, 0x09, 0x20, 0xc0, + 0xee, 0x09, 0xe7, 0xd0, 0xee, 0x09, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0x48, 0x02, 0xc8, 0x37, + 0x04, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x60, + 0x21, 0xc0, 0xc0, 0x37, 0x3e, 0x00, 0x23, 0xc9, + 0xc0, 0x57, 0xb4, 0x05, 0x1b, 0xc8, 0xc0, 0x17, + 0x3f, 0x00, 0xc0, 0x67, 0xc0, 0xff, 0x30, 0x00, + 0x08, 0x00, 0xf0, 0x07, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x02, 0xc0, 0x17, 0x4c, 0x00, 0x30, 0x00, + 0x06, 0x00, 0xf0, 0x07, 0xbe, 0x01, 0x0a, 0x00, + 0x48, 0x02, 0xc1, 0x07, 0x02, 0x00, 0xd7, 0x09, + 0x00, 0xc0, 0xc1, 0xdf, 0x51, 0xaf, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0x9f, 0xaf, 0x68, 0x04, + 0x9f, 0xaf, 0xe4, 0x03, 0x97, 0xcf, 0x9f, 0xaf, + 0xe4, 0x03, 0xc9, 0x37, 0x04, 0x00, 0xc1, 0xdf, + 0xc8, 0x09, 0x70, 0x08, 0x50, 0x02, 0x67, 0x02, + 0x70, 0x08, 0xd1, 0x07, 0x00, 0x00, 0xc0, 0xdf, + 0x9f, 0xaf, 0xde, 0x01, 0x97, 0xcf, 0xe7, 0x57, + 0x00, 0x00, 0xaa, 0x08, 0x97, 0xc1, 0xe7, 0x57, + 0x01, 0x00, 0x7a, 0x08, 0x97, 0xc0, 0xc8, 0x09, + 0x6e, 0x08, 0x08, 0x62, 0x97, 0xc0, 0x00, 0x02, + 0xc0, 0x17, 0x0e, 0x00, 0x27, 0x00, 0x34, 0x01, + 0x27, 0x0c, 0x0c, 0x00, 0x36, 0x01, 0xef, 0x57, + 0x00, 0x00, 0xf0, 0x09, 0x9f, 0xc0, 0xbe, 0x02, + 0xe7, 0x57, 0x00, 0x00, 0xb0, 0x08, 0x97, 0xc1, + 0xe7, 0x07, 0x09, 0x00, 0x12, 0xc0, 0xe7, 0x77, + 0x00, 0x08, 0x20, 0xc0, 0x9f, 0xc1, 0xb6, 0x02, + 0xe7, 0x57, 0x09, 0x00, 0x12, 0xc0, 0x77, 0xc9, + 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, 0xe7, 0x77, + 0x00, 0x08, 0x20, 0xc0, 0x2f, 0xc1, 0xe7, 0x07, + 0x00, 0x00, 0x42, 0xc0, 0xe7, 0x07, 0x05, 0x00, + 0x90, 0xc0, 0xc8, 0x07, 0x0a, 0x00, 0xe7, 0x77, + 0x04, 0x00, 0x20, 0xc0, 0x09, 0xc1, 0x08, 0xda, + 0x7a, 0xc1, 0xe7, 0x07, 0x00, 0x01, 0x42, 0xc0, + 0xe7, 0x07, 0x04, 0x00, 0x90, 0xc0, 0x1a, 0xcf, + 0xe7, 0x07, 0x01, 0x00, 0x7a, 0x08, 0x00, 0xd8, + 0x27, 0x50, 0x34, 0x01, 0x17, 0xc1, 0xe7, 0x77, + 0x02, 0x00, 0x20, 0xc0, 0x79, 0xc1, 0x27, 0x50, + 0x34, 0x01, 0x10, 0xc1, 0xe7, 0x77, 0x02, 0x00, + 0x20, 0xc0, 0x79, 0xc0, 0x9f, 0xaf, 0xd8, 0x02, + 0xe7, 0x05, 0x00, 0xc0, 0x00, 0x60, 0x9f, 0xc0, + 0xde, 0x01, 0x97, 0xcf, 0xe7, 0x07, 0x01, 0x00, + 0xb8, 0x08, 0x06, 0xcf, 0xe7, 0x07, 0x30, 0x0e, + 0x02, 0x00, 0xe7, 0x07, 0x50, 0xc3, 0x12, 0xc0, + 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, 0xe7, 0x07, + 0x01, 0x00, 0xb8, 0x08, 0x97, 0xcf, 0xe7, 0x07, + 0x50, 0xc3, 0x12, 0xc0, 0xe7, 0x07, 0x30, 0x0e, + 0x02, 0x00, 0xe7, 0x07, 0x01, 0x00, 0x7a, 0x08, + 0xe7, 0x07, 0x05, 0x00, 0x90, 0xc0, 0x97, 0xcf, + 0xe7, 0x07, 0x00, 0x01, 0x42, 0xc0, 0xe7, 0x07, + 0x04, 0x00, 0x90, 0xc0, 0xe7, 0x07, 0x00, 0x00, + 0x7a, 0x08, 0xe7, 0x57, 0x0f, 0x00, 0xb2, 0x08, + 0x13, 0xc1, 0x9f, 0xaf, 0x2e, 0x08, 0xca, 0x09, + 0xac, 0x08, 0xf2, 0x17, 0x01, 0x00, 0x5c, 0x00, + 0xf2, 0x27, 0x00, 0x00, 0x5e, 0x00, 0xe7, 0x07, + 0x00, 0x00, 0xb2, 0x08, 0xe7, 0x07, 0x01, 0x00, + 0xb4, 0x08, 0xc0, 0x07, 0xff, 0xff, 0x97, 0xcf, + 0x9f, 0xaf, 0x4c, 0x03, 0xc0, 0x69, 0xb4, 0x08, + 0x57, 0x00, 0x9f, 0xde, 0x33, 0x00, 0xc1, 0x05, + 0x27, 0xd8, 0xb2, 0x08, 0x27, 0xd2, 0xb4, 0x08, + 0xe7, 0x87, 0x01, 0x00, 0xb4, 0x08, 0xe7, 0x67, + 0xff, 0x03, 0xb4, 0x08, 0x00, 0x60, 0x97, 0xc0, + 0xe7, 0x07, 0x01, 0x00, 0xb0, 0x08, 0x27, 0x00, + 0x12, 0xc0, 0x97, 0xcf, 0xc0, 0x09, 0xb6, 0x08, + 0x00, 0xd2, 0x02, 0xc3, 0xc0, 0x97, 0x05, 0x80, + 0x27, 0x00, 0xb6, 0x08, 0xc0, 0x99, 0x82, 0x08, + 0xc0, 0x99, 0xa2, 0xc0, 0x97, 0xcf, 0xe7, 0x07, + 0x00, 0x00, 0xb0, 0x08, 0xc0, 0xdf, 0x97, 0xcf, + 0xc8, 0x09, 0x72, 0x08, 0x08, 0x62, 0x02, 0xc0, + 0x10, 0x64, 0x07, 0xc1, 0xe7, 0x07, 0x00, 0x00, + 0x64, 0x08, 0xe7, 0x07, 0xc8, 0x05, 0x24, 0x00, + 0x97, 0xcf, 0x27, 0x04, 0x72, 0x08, 0xc8, 0x17, + 0x0e, 0x00, 0x27, 0x02, 0x64, 0x08, 0xe7, 0x07, + 0xd6, 0x05, 0x24, 0x00, 0x97, 0xcf, 0xd7, 0x09, + 0x00, 0xc0, 0xc1, 0xdf, 0xe7, 0x57, 0x00, 0x00, + 0x62, 0x08, 0x13, 0xc1, 0x9f, 0xaf, 0x70, 0x03, + 0xe7, 0x57, 0x00, 0x00, 0x64, 0x08, 0x13, 0xc0, + 0xe7, 0x09, 0x64, 0x08, 0x30, 0x01, 0xe7, 0x07, + 0xf2, 0x05, 0x32, 0x01, 0xe7, 0x07, 0x10, 0x00, + 0x96, 0xc0, 0xe7, 0x09, 0x64, 0x08, 0x62, 0x08, + 0x04, 0xcf, 0xe7, 0x57, 0x00, 0x00, 0x64, 0x08, + 0x02, 0xc1, 0x9f, 0xaf, 0x70, 0x03, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0xd7, 0x09, 0x00, 0xc0, + 0xc1, 0xdf, 0xc8, 0x09, 0x72, 0x08, 0x27, 0x02, + 0x78, 0x08, 0x08, 0x62, 0x03, 0xc1, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0x27, 0x04, 0x72, 0x08, + 0xe7, 0x05, 0x00, 0xc0, 0xf0, 0x07, 0x40, 0x00, + 0x08, 0x00, 0xf0, 0x07, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x02, 0xc0, 0x17, 0x0c, 0x00, 0x30, 0x00, + 0x06, 0x00, 0xf0, 0x07, 0x64, 0x01, 0x0a, 0x00, + 0xc8, 0x17, 0x04, 0x00, 0xc1, 0x07, 0x02, 0x00, + 0x51, 0xaf, 0x97, 0xcf, 0xe7, 0x57, 0x00, 0x00, + 0x6a, 0x08, 0x97, 0xc0, 0xc1, 0xdf, 0xc8, 0x09, + 0x6a, 0x08, 0x27, 0x04, 0x6a, 0x08, 0x27, 0x52, + 0x6c, 0x08, 0x03, 0xc1, 0xe7, 0x07, 0x6a, 0x08, + 0x6c, 0x08, 0xc0, 0xdf, 0x17, 0x02, 0xc8, 0x17, + 0x0e, 0x00, 0x9f, 0xaf, 0x16, 0x05, 0xc8, 0x05, + 0x00, 0x60, 0x03, 0xc0, 0x9f, 0xaf, 0x80, 0x04, + 0x97, 0xcf, 0x9f, 0xaf, 0x68, 0x04, 0x97, 0xcf, + 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, 0x08, 0x62, + 0x1c, 0xc0, 0xd0, 0x09, 0x72, 0x08, 0x27, 0x02, + 0x72, 0x08, 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, + 0x97, 0x02, 0xca, 0x09, 0xac, 0x08, 0xf2, 0x17, + 0x01, 0x00, 0x04, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x06, 0x00, 0xca, 0x17, 0x2c, 0x00, 0xf8, 0x77, + 0x01, 0x00, 0x0e, 0x00, 0x06, 0xc0, 0xca, 0xd9, + 0xf8, 0x57, 0xff, 0x00, 0x0e, 0x00, 0x01, 0xc1, + 0xca, 0xd9, 0x22, 0x1c, 0x0c, 0x00, 0xe2, 0x27, + 0x00, 0x00, 0xe2, 0x17, 0x01, 0x00, 0xe2, 0x27, + 0x00, 0x00, 0xca, 0x05, 0x00, 0x0c, 0x0c, 0x00, + 0xc0, 0x17, 0x41, 0x00, 0xc0, 0x67, 0xc0, 0xff, + 0x30, 0x00, 0x08, 0x00, 0x00, 0x02, 0xc0, 0x17, + 0x0c, 0x00, 0x30, 0x00, 0x06, 0x00, 0xf0, 0x07, + 0xdc, 0x00, 0x0a, 0x00, 0xf0, 0x07, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x0c, 0x08, 0x00, 0x40, 0xd1, + 0x01, 0x00, 0xc0, 0x19, 0xa6, 0x08, 0xc0, 0x59, + 0x98, 0x08, 0x04, 0xc9, 0x49, 0xaf, 0x9f, 0xaf, + 0xee, 0x00, 0x4a, 0xaf, 0x67, 0x10, 0xa6, 0x08, + 0xc8, 0x17, 0x04, 0x00, 0xc1, 0x07, 0x01, 0x00, + 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, 0x50, 0xaf, + 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, 0xc0, 0x07, + 0x01, 0x00, 0xc1, 0x09, 0x7c, 0x08, 0xc1, 0x77, + 0x01, 0x00, 0x97, 0xc1, 0xd8, 0x77, 0x01, 0x00, + 0x12, 0xc0, 0xc9, 0x07, 0x4c, 0x08, 0x9f, 0xaf, + 0x64, 0x05, 0x04, 0xc1, 0xc1, 0x77, 0x08, 0x00, + 0x13, 0xc0, 0x97, 0xcf, 0xc1, 0x77, 0x02, 0x00, + 0x97, 0xc1, 0xc1, 0x77, 0x10, 0x00, 0x0c, 0xc0, + 0x9f, 0xaf, 0x86, 0x05, 0x97, 0xcf, 0xc1, 0x77, + 0x04, 0x00, 0x06, 0xc0, 0xc9, 0x07, 0x7e, 0x08, + 0x9f, 0xaf, 0x64, 0x05, 0x97, 0xc0, 0x00, 0xcf, + 0x00, 0x90, 0x97, 0xcf, 0x50, 0x54, 0x97, 0xc1, + 0x70, 0x5c, 0x02, 0x00, 0x02, 0x00, 0x97, 0xc1, + 0x70, 0x5c, 0x04, 0x00, 0x04, 0x00, 0x97, 0xcf, + 0xc0, 0x00, 0x60, 0x00, 0x30, 0x00, 0x18, 0x00, + 0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0xcb, 0x09, + 0x88, 0x08, 0xcc, 0x09, 0x8a, 0x08, 0x0b, 0x53, + 0x11, 0xc0, 0xc9, 0x02, 0xca, 0x07, 0x78, 0x05, + 0x9f, 0xaf, 0x64, 0x05, 0x97, 0xc0, 0x0a, 0xc8, + 0x82, 0x08, 0x0a, 0xcf, 0x82, 0x08, 0x9f, 0xaf, + 0x64, 0x05, 0x97, 0xc0, 0x05, 0xc2, 0x89, 0x30, + 0x82, 0x60, 0x78, 0xc1, 0x00, 0x90, 0x97, 0xcf, + 0x89, 0x10, 0x09, 0x53, 0x79, 0xc2, 0x89, 0x30, + 0x82, 0x08, 0x7a, 0xcf, 0xc0, 0xdf, 0x97, 0xcf, + 0xe7, 0x09, 0x96, 0xc0, 0x66, 0x08, 0xe7, 0x09, + 0x98, 0xc0, 0x68, 0x08, 0x0f, 0xcf, 0xe7, 0x09, + 0x96, 0xc0, 0x66, 0x08, 0xe7, 0x09, 0x98, 0xc0, + 0x68, 0x08, 0xe7, 0x09, 0x64, 0x08, 0x30, 0x01, + 0xe7, 0x07, 0xf2, 0x05, 0x32, 0x01, 0xe7, 0x07, + 0x10, 0x00, 0x96, 0xc0, 0xd7, 0x09, 0x00, 0xc0, + 0x17, 0x02, 0xc8, 0x09, 0x62, 0x08, 0xc8, 0x37, + 0x0e, 0x00, 0xe7, 0x57, 0x04, 0x00, 0x68, 0x08, + 0x3d, 0xc0, 0xe7, 0x87, 0x00, 0x08, 0x24, 0xc0, + 0xe7, 0x09, 0x94, 0x08, 0xba, 0x08, 0xe7, 0x17, + 0x64, 0x00, 0xba, 0x08, 0xe7, 0x67, 0xff, 0x07, + 0xba, 0x08, 0xe7, 0x77, 0x2a, 0x00, 0x66, 0x08, + 0x30, 0xc0, 0x97, 0x02, 0xca, 0x09, 0xac, 0x08, + 0xe7, 0x77, 0x20, 0x00, 0x66, 0x08, 0x0e, 0xc0, + 0xf2, 0x17, 0x01, 0x00, 0x10, 0x00, 0xf2, 0x27, + 0x00, 0x00, 0x12, 0x00, 0xe7, 0x77, 0x0a, 0x00, + 0x66, 0x08, 0xca, 0x05, 0x1e, 0xc0, 0x97, 0x02, + 0xca, 0x09, 0xac, 0x08, 0xf2, 0x17, 0x01, 0x00, + 0x0c, 0x00, 0xf2, 0x27, 0x00, 0x00, 0x0e, 0x00, + 0xe7, 0x77, 0x02, 0x00, 0x66, 0x08, 0x07, 0xc0, + 0xf2, 0x17, 0x01, 0x00, 0x44, 0x00, 0xf2, 0x27, + 0x00, 0x00, 0x46, 0x00, 0x06, 0xcf, 0xf2, 0x17, + 0x01, 0x00, 0x60, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x62, 0x00, 0xca, 0x05, 0x9f, 0xaf, 0x68, 0x04, + 0x0f, 0xcf, 0x57, 0x02, 0x09, 0x02, 0xf1, 0x09, + 0x68, 0x08, 0x0c, 0x00, 0xf1, 0xda, 0x0c, 0x00, + 0xc8, 0x09, 0x6c, 0x08, 0x50, 0x02, 0x67, 0x02, + 0x6c, 0x08, 0xd1, 0x07, 0x00, 0x00, 0xc9, 0x05, + 0xe7, 0x09, 0x64, 0x08, 0x62, 0x08, 0xe7, 0x57, + 0x00, 0x00, 0x62, 0x08, 0x02, 0xc0, 0x9f, 0xaf, + 0x70, 0x03, 0xc8, 0x05, 0xe7, 0x05, 0x00, 0xc0, + 0xc0, 0xdf, 0x97, 0xcf, 0xd7, 0x09, 0x00, 0xc0, + 0x17, 0x00, 0x17, 0x02, 0x97, 0x02, 0xc0, 0x09, + 0x92, 0xc0, 0xe7, 0x87, 0x00, 0x08, 0x24, 0xc0, + 0xe7, 0x09, 0x94, 0x08, 0xba, 0x08, 0xe7, 0x17, + 0x64, 0x00, 0xba, 0x08, 0xe7, 0x67, 0xff, 0x07, + 0xba, 0x08, 0xe7, 0x07, 0x04, 0x00, 0x90, 0xc0, + 0xca, 0x09, 0xac, 0x08, 0xe7, 0x07, 0x00, 0x00, + 0x7a, 0x08, 0xe7, 0x07, 0x66, 0x03, 0x02, 0x00, + 0xc0, 0x77, 0x02, 0x00, 0x10, 0xc0, 0xef, 0x57, + 0x00, 0x00, 0xf0, 0x09, 0x04, 0xc0, 0x9f, 0xaf, + 0xd8, 0x02, 0x9f, 0xcf, 0x12, 0x08, 0xf2, 0x17, + 0x01, 0x00, 0x50, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x52, 0x00, 0x9f, 0xcf, 0x12, 0x08, 0xef, 0x57, + 0x00, 0x00, 0xf0, 0x09, 0x08, 0xc0, 0xe7, 0x57, + 0x00, 0x00, 0xb8, 0x08, 0xe7, 0x07, 0x00, 0x00, + 0xb8, 0x08, 0x0a, 0xc0, 0x03, 0xcf, 0xc0, 0x77, + 0x10, 0x00, 0x06, 0xc0, 0xf2, 0x17, 0x01, 0x00, + 0x58, 0x00, 0xf2, 0x27, 0x00, 0x00, 0x5a, 0x00, + 0xc0, 0x77, 0x80, 0x00, 0x06, 0xc0, 0xf2, 0x17, + 0x01, 0x00, 0x70, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x72, 0x00, 0xc0, 0x77, 0x08, 0x00, 0x1d, 0xc1, + 0xf2, 0x17, 0x01, 0x00, 0x08, 0x00, 0xf2, 0x27, + 0x00, 0x00, 0x0a, 0x00, 0xc0, 0x77, 0x00, 0x02, + 0x06, 0xc0, 0xf2, 0x17, 0x01, 0x00, 0x64, 0x00, + 0xf2, 0x27, 0x00, 0x00, 0x66, 0x00, 0xc0, 0x77, + 0x40, 0x00, 0x06, 0xc0, 0xf2, 0x17, 0x01, 0x00, + 0x5c, 0x00, 0xf2, 0x27, 0x00, 0x00, 0x5e, 0x00, + 0xc0, 0x77, 0x01, 0x00, 0x01, 0xc0, 0x37, 0xcf, + 0x36, 0xcf, 0xf2, 0x17, 0x01, 0x00, 0x00, 0x00, + 0xf2, 0x27, 0x00, 0x00, 0x02, 0x00, 0xef, 0x57, + 0x00, 0x00, 0xf0, 0x09, 0x18, 0xc0, 0xe7, 0x57, + 0x01, 0x00, 0xb2, 0x08, 0x0e, 0xc2, 0x07, 0xc8, + 0xf2, 0x17, 0x01, 0x00, 0x50, 0x00, 0xf2, 0x27, + 0x00, 0x00, 0x52, 0x00, 0x06, 0xcf, 0xf2, 0x17, + 0x01, 0x00, 0x54, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x56, 0x00, 0xe7, 0x07, 0x00, 0x00, 0xb2, 0x08, + 0xe7, 0x07, 0x01, 0x00, 0xb4, 0x08, 0xc8, 0x09, + 0x34, 0x01, 0xca, 0x17, 0x14, 0x00, 0xd8, 0x77, + 0x01, 0x00, 0x05, 0xc0, 0xca, 0xd9, 0xd8, 0x57, + 0xff, 0x00, 0x01, 0xc0, 0xca, 0xd9, 0xe2, 0x19, + 0x94, 0xc0, 0xe2, 0x27, 0x00, 0x00, 0xe2, 0x17, + 0x01, 0x00, 0xe2, 0x27, 0x00, 0x00, 0x9f, 0xaf, + 0x2e, 0x08, 0x9f, 0xaf, 0xde, 0x01, 0xe7, 0x57, + 0x00, 0x00, 0xaa, 0x08, 0x9f, 0xa1, 0xf0, 0x0b, + 0xca, 0x05, 0xc8, 0x05, 0xc0, 0x05, 0xe7, 0x05, + 0x00, 0xc0, 0xc0, 0xdf, 0x97, 0xcf, 0xc8, 0x09, + 0x6e, 0x08, 0x08, 0x62, 0x97, 0xc0, 0x27, 0x04, + 0x6e, 0x08, 0x27, 0x52, 0x70, 0x08, 0x03, 0xc1, + 0xe7, 0x07, 0x6e, 0x08, 0x70, 0x08, 0x9f, 0xaf, + 0x68, 0x04, 0x97, 0xcf, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x33, 0xcc, + 0x00, 0x00, 0x00, 0x00, 0xe7, 0x57, 0x00, 0x80, + 0xb2, 0x00, 0x06, 0xc2, 0xe7, 0x07, 0x52, 0x0e, + 0x12, 0x00, 0xe7, 0x07, 0x98, 0x0e, 0xb2, 0x00, + 0xe7, 0x07, 0xa4, 0x09, 0xf2, 0x02, 0xc8, 0x09, + 0xb4, 0x00, 0xf8, 0x07, 0x02, 0x00, 0x0d, 0x00, + 0xd7, 0x09, 0x0e, 0xc0, 0xe7, 0x07, 0x00, 0x00, + 0x0e, 0xc0, 0xc8, 0x09, 0xdc, 0x00, 0xf0, 0x07, + 0xff, 0xff, 0x09, 0x00, 0xf0, 0x07, 0xfb, 0x13, + 0x0b, 0x00, 0xe7, 0x09, 0xc0, 0x00, 0x58, 0x08, + 0xe7, 0x09, 0xbe, 0x00, 0x54, 0x08, 0xe7, 0x09, + 0x10, 0x00, 0x92, 0x08, 0xc8, 0x07, 0xb4, 0x09, + 0x9f, 0xaf, 0x8c, 0x09, 0x9f, 0xaf, 0xe2, 0x0b, + 0xc0, 0x07, 0x80, 0x01, 0x44, 0xaf, 0x27, 0x00, + 0x88, 0x08, 0x27, 0x00, 0x8a, 0x08, 0x27, 0x00, + 0x8c, 0x08, 0xc0, 0x07, 0x74, 0x00, 0x44, 0xaf, + 0x27, 0x00, 0xac, 0x08, 0x08, 0x00, 0x00, 0x90, + 0xc1, 0x07, 0x1d, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x01, 0xda, 0x7c, 0xc1, 0x9f, 0xaf, 0x8a, 0x0b, + 0xc0, 0x07, 0x4c, 0x00, 0x48, 0xaf, 0x27, 0x00, + 0x56, 0x08, 0x9f, 0xaf, 0x72, 0x0c, 0xe7, 0x07, + 0x00, 0x80, 0x96, 0x08, 0xef, 0x57, 0x00, 0x00, + 0xf0, 0x09, 0x03, 0xc0, 0xe7, 0x07, 0x01, 0x00, + 0x1c, 0xc0, 0xe7, 0x05, 0x0e, 0xc0, 0x97, 0xcf, + 0x49, 0xaf, 0xe7, 0x87, 0x43, 0x00, 0x0e, 0xc0, + 0xe7, 0x07, 0xff, 0xff, 0x94, 0x08, 0x9f, 0xaf, + 0x8a, 0x0c, 0xc0, 0x07, 0x01, 0x00, 0x60, 0xaf, + 0x4a, 0xaf, 0x97, 0xcf, 0x00, 0x08, 0x09, 0x08, + 0x11, 0x08, 0x00, 0xda, 0x7c, 0xc1, 0x97, 0xcf, + 0x67, 0x04, 0xcc, 0x02, 0xc0, 0xdf, 0x51, 0x94, + 0xb1, 0xaf, 0x06, 0x00, 0xc1, 0xdf, 0xc9, 0x09, + 0xcc, 0x02, 0x49, 0x62, 0x75, 0xc1, 0xc0, 0xdf, + 0xa7, 0xcf, 0xd6, 0x02, 0x0e, 0x00, 0x24, 0x00, + 0xd6, 0x05, 0x22, 0x00, 0xc4, 0x06, 0xd0, 0x00, + 0xf0, 0x0b, 0xaa, 0x00, 0x0e, 0x0a, 0xbe, 0x00, + 0x2c, 0x0c, 0x10, 0x00, 0x20, 0x00, 0x04, 0x00, + 0xc4, 0x05, 0x02, 0x00, 0x66, 0x03, 0x06, 0x00, + 0x00, 0x00, 0x24, 0xc0, 0x04, 0x04, 0x28, 0xc0, + 0xfe, 0xfb, 0x1e, 0xc0, 0x00, 0x04, 0x22, 0xc0, + 0xff, 0xf0, 0xc0, 0x00, 0x60, 0x0b, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x34, 0x0a, 0x3e, 0x0a, + 0x9e, 0x0a, 0xa8, 0x0a, 0xce, 0x0a, 0xd2, 0x0a, + 0xd6, 0x0a, 0x00, 0x0b, 0x10, 0x0b, 0x1e, 0x0b, + 0x20, 0x0b, 0x28, 0x0b, 0x28, 0x0b, 0x27, 0x02, + 0xa2, 0x08, 0x97, 0xcf, 0xe7, 0x07, 0x00, 0x00, + 0xa2, 0x08, 0x0a, 0x0e, 0x01, 0x00, 0xca, 0x57, + 0x0e, 0x00, 0x9f, 0xc3, 0x2a, 0x0b, 0xca, 0x37, + 0x00, 0x00, 0x9f, 0xc2, 0x2a, 0x0b, 0x0a, 0xd2, + 0xb2, 0xcf, 0xf4, 0x09, 0xc8, 0x09, 0xde, 0x00, + 0x07, 0x06, 0x9f, 0xcf, 0x3c, 0x0b, 0xf0, 0x57, + 0x80, 0x01, 0x06, 0x00, 0x9f, 0xc8, 0x2a, 0x0b, + 0x27, 0x0c, 0x02, 0x00, 0x86, 0x08, 0xc0, 0x09, + 0x88, 0x08, 0x27, 0x00, 0x8a, 0x08, 0xe7, 0x07, + 0x00, 0x00, 0x84, 0x08, 0x27, 0x00, 0x5c, 0x08, + 0x00, 0x1c, 0x06, 0x00, 0x27, 0x00, 0x8c, 0x08, + 0x41, 0x90, 0x67, 0x50, 0x86, 0x08, 0x0d, 0xc0, + 0x67, 0x00, 0x5a, 0x08, 0x27, 0x0c, 0x06, 0x00, + 0x5e, 0x08, 0xe7, 0x07, 0x8a, 0x0a, 0x60, 0x08, + 0xc8, 0x07, 0x5a, 0x08, 0x41, 0x90, 0x51, 0xaf, + 0x97, 0xcf, 0x9f, 0xaf, 0xac, 0x0e, 0xe7, 0x09, + 0x8c, 0x08, 0x8a, 0x08, 0xe7, 0x09, 0x86, 0x08, + 0x84, 0x08, 0x59, 0xaf, 0x97, 0xcf, 0x27, 0x0c, + 0x02, 0x00, 0x7c, 0x08, 0x59, 0xaf, 0x97, 0xcf, + 0x09, 0x0c, 0x02, 0x00, 0x09, 0xda, 0x49, 0xd2, + 0xc9, 0x19, 0xac, 0x08, 0xc8, 0x07, 0x5a, 0x08, + 0xe0, 0x07, 0x00, 0x00, 0x60, 0x02, 0xe0, 0x07, + 0x04, 0x00, 0xd0, 0x07, 0x9a, 0x0a, 0x48, 0xdb, + 0x41, 0x90, 0x50, 0xaf, 0x97, 0xcf, 0x59, 0xaf, + 0x97, 0xcf, 0x59, 0xaf, 0x97, 0xcf, 0xf0, 0x57, + 0x06, 0x00, 0x06, 0x00, 0x26, 0xc1, 0xe7, 0x07, + 0x7e, 0x08, 0x5c, 0x08, 0x41, 0x90, 0x67, 0x00, + 0x5a, 0x08, 0x27, 0x0c, 0x06, 0x00, 0x5e, 0x08, + 0xe7, 0x07, 0x5c, 0x0b, 0x60, 0x08, 0xc8, 0x07, + 0x5a, 0x08, 0x41, 0x90, 0x51, 0xaf, 0x97, 0xcf, + 0x07, 0x0c, 0x06, 0x00, 0xc7, 0x57, 0x06, 0x00, + 0x10, 0xc1, 0xc8, 0x07, 0x7e, 0x08, 0x16, 0xcf, + 0x00, 0x0c, 0x02, 0x00, 0x00, 0xda, 0x40, 0xd1, + 0x27, 0x00, 0x98, 0x08, 0x1f, 0xcf, 0x1e, 0xcf, + 0x27, 0x0c, 0x02, 0x00, 0xa4, 0x08, 0x1a, 0xcf, + 0x00, 0xcf, 0x27, 0x02, 0x20, 0x01, 0xe7, 0x07, + 0x08, 0x00, 0x22, 0x01, 0xe7, 0x07, 0x13, 0x00, + 0xb0, 0xc0, 0x97, 0xcf, 0x41, 0x90, 0x67, 0x00, + 0x5a, 0x08, 0xe7, 0x01, 0x5e, 0x08, 0x27, 0x02, + 0x5c, 0x08, 0xe7, 0x07, 0x5c, 0x0b, 0x60, 0x08, + 0xc8, 0x07, 0x5a, 0x08, 0xc1, 0x07, 0x00, 0x80, + 0x50, 0xaf, 0x97, 0xcf, 0x59, 0xaf, 0x97, 0xcf, + 0x00, 0x60, 0x05, 0xc0, 0xe7, 0x07, 0x00, 0x00, + 0x9a, 0x08, 0xa7, 0xcf, 0x58, 0x08, 0x9f, 0xaf, + 0xe2, 0x0b, 0xe7, 0x07, 0x01, 0x00, 0x9a, 0x08, + 0x49, 0xaf, 0xd7, 0x09, 0x00, 0xc0, 0x07, 0xaf, + 0xe7, 0x05, 0x00, 0xc0, 0x4a, 0xaf, 0xa7, 0xcf, + 0x58, 0x08, 0xc0, 0x07, 0x40, 0x00, 0x44, 0xaf, + 0x27, 0x00, 0xa0, 0x08, 0x08, 0x00, 0xc0, 0x07, + 0x20, 0x00, 0x20, 0x94, 0x00, 0xda, 0x7d, 0xc1, + 0xc0, 0x07, 0xfe, 0x7f, 0x44, 0xaf, 0x40, 0x00, + 0x41, 0x90, 0xc0, 0x37, 0x08, 0x00, 0xdf, 0xde, + 0x50, 0x06, 0xc0, 0x57, 0x10, 0x00, 0x02, 0xc2, + 0xc0, 0x07, 0x10, 0x00, 0x27, 0x00, 0x76, 0x08, + 0x41, 0x90, 0x9f, 0xde, 0x40, 0x06, 0x44, 0xaf, + 0x27, 0x00, 0x74, 0x08, 0xc0, 0x09, 0x76, 0x08, + 0x41, 0x90, 0x00, 0xd2, 0x00, 0xd8, 0x9f, 0xde, + 0x08, 0x00, 0x44, 0xaf, 0x27, 0x00, 0x9e, 0x08, + 0x97, 0xcf, 0xe7, 0x87, 0x00, 0x84, 0x28, 0xc0, + 0xe7, 0x67, 0xff, 0xf3, 0x24, 0xc0, 0x97, 0xcf, + 0xe7, 0x87, 0x01, 0x00, 0xaa, 0x08, 0xe7, 0x57, + 0x00, 0x00, 0x7a, 0x08, 0x97, 0xc1, 0x9f, 0xaf, + 0xe2, 0x0b, 0xe7, 0x87, 0x00, 0x06, 0x22, 0xc0, + 0xe7, 0x07, 0x00, 0x00, 0x90, 0xc0, 0xe7, 0x67, + 0xfe, 0xff, 0x3e, 0xc0, 0xe7, 0x07, 0x2e, 0x00, + 0x0a, 0xc0, 0xe7, 0x87, 0x01, 0x00, 0x3e, 0xc0, + 0xe7, 0x07, 0xff, 0xff, 0x94, 0x08, 0x9f, 0xaf, + 0xf0, 0x0c, 0x97, 0xcf, 0x17, 0x00, 0xa7, 0xaf, + 0x54, 0x08, 0xc0, 0x05, 0x27, 0x00, 0x52, 0x08, + 0xe7, 0x87, 0x01, 0x00, 0xaa, 0x08, 0x9f, 0xaf, + 0xe2, 0x0b, 0xe7, 0x07, 0x0c, 0x00, 0x40, 0xc0, + 0x9f, 0xaf, 0xf0, 0x0c, 0xe7, 0x07, 0x00, 0x00, + 0x78, 0x08, 0x00, 0x90, 0xe7, 0x09, 0x88, 0x08, + 0x8a, 0x08, 0x27, 0x00, 0x84, 0x08, 0x27, 0x00, + 0x7c, 0x08, 0x9f, 0xaf, 0x8a, 0x0c, 0xe7, 0x07, + 0x00, 0x00, 0xb2, 0x02, 0xe7, 0x07, 0x00, 0x00, + 0xb4, 0x02, 0xc0, 0x07, 0x06, 0x00, 0xc8, 0x09, + 0xde, 0x00, 0xc8, 0x17, 0x03, 0x00, 0xc9, 0x07, + 0x7e, 0x08, 0x29, 0x0a, 0x00, 0xda, 0x7d, 0xc1, + 0x97, 0xcf, 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, + 0x00, 0x90, 0x27, 0x00, 0x6a, 0x08, 0xe7, 0x07, + 0x6a, 0x08, 0x6c, 0x08, 0x27, 0x00, 0x6e, 0x08, + 0xe7, 0x07, 0x6e, 0x08, 0x70, 0x08, 0x27, 0x00, + 0x78, 0x08, 0x27, 0x00, 0x62, 0x08, 0x27, 0x00, + 0x64, 0x08, 0xc8, 0x09, 0x74, 0x08, 0xc1, 0x09, + 0x76, 0x08, 0xc9, 0x07, 0x72, 0x08, 0x11, 0x02, + 0x09, 0x02, 0xc8, 0x17, 0x40, 0x06, 0x01, 0xda, + 0x7a, 0xc1, 0x51, 0x94, 0xc8, 0x09, 0x9e, 0x08, + 0xc9, 0x07, 0x9c, 0x08, 0xc1, 0x09, 0x76, 0x08, + 0x01, 0xd2, 0x01, 0xd8, 0x11, 0x02, 0x09, 0x02, + 0xc8, 0x17, 0x08, 0x00, 0x01, 0xda, 0x7a, 0xc1, + 0x51, 0x94, 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, + 0xe7, 0x57, 0x00, 0x00, 0x52, 0x08, 0x97, 0xc0, + 0x9f, 0xaf, 0x04, 0x00, 0xe7, 0x09, 0x94, 0x08, + 0x90, 0x08, 0xe7, 0x57, 0xff, 0xff, 0x90, 0x08, + 0x04, 0xc1, 0xe7, 0x07, 0xf0, 0x0c, 0x8e, 0x08, + 0x97, 0xcf, 0xe7, 0x17, 0x32, 0x00, 0x90, 0x08, + 0xe7, 0x67, 0xff, 0x07, 0x90, 0x08, 0xe7, 0x07, + 0x26, 0x0d, 0x8e, 0x08, 0x97, 0xcf, 0xd7, 0x09, + 0x00, 0xc0, 0xc1, 0xdf, 0xe7, 0x57, 0x00, 0x00, + 0x96, 0x08, 0x23, 0xc0, 0xe7, 0x07, 0x00, 0x80, + 0x80, 0xc0, 0xe7, 0x07, 0x04, 0x00, 0x90, 0xc0, + 0xe7, 0x07, 0x00, 0x00, 0x80, 0xc0, 0xe7, 0x07, + 0x00, 0x80, 0x80, 0xc0, 0xc0, 0x07, 0x00, 0x00, + 0xc0, 0x07, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00, + 0xe7, 0x07, 0x00, 0x00, 0x80, 0xc0, 0xe7, 0x07, + 0x00, 0x80, 0x80, 0xc0, 0xe7, 0x07, 0x00, 0x80, + 0x40, 0xc0, 0xc0, 0x07, 0x00, 0x00, 0xe7, 0x07, + 0x00, 0x00, 0x40, 0xc0, 0xe7, 0x07, 0x00, 0x00, + 0x80, 0xc0, 0xef, 0x57, 0x00, 0x00, 0xf1, 0x09, + 0x9f, 0xa0, 0xc0, 0x0d, 0xe7, 0x07, 0x04, 0x00, + 0x90, 0xc0, 0xe7, 0x07, 0x00, 0x02, 0x40, 0xc0, + 0xe7, 0x07, 0x0c, 0x02, 0x40, 0xc0, 0xe7, 0x07, + 0x00, 0x00, 0x96, 0x08, 0xe7, 0x07, 0x00, 0x00, + 0x8e, 0x08, 0xe7, 0x07, 0x00, 0x00, 0xaa, 0x08, + 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, 0x9f, 0xaf, + 0x9e, 0x03, 0xe7, 0x05, 0x00, 0xc0, 0x9f, 0xaf, + 0xde, 0x01, 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, + 0x9f, 0xaf, 0xde, 0x0d, 0xef, 0x77, 0x00, 0x00, + 0xf1, 0x09, 0x97, 0xc1, 0x9f, 0xaf, 0xde, 0x0d, + 0xef, 0x77, 0x00, 0x00, 0xf1, 0x09, 0x97, 0xc1, + 0xef, 0x07, 0x01, 0x00, 0xf1, 0x09, 0xe7, 0x87, + 0x00, 0x08, 0x1e, 0xc0, 0xe7, 0x87, 0x00, 0x08, + 0x22, 0xc0, 0xe7, 0x67, 0xff, 0xf7, 0x22, 0xc0, + 0xe7, 0x77, 0x00, 0x08, 0x20, 0xc0, 0x11, 0xc0, + 0xe7, 0x67, 0xff, 0xf7, 0x1e, 0xc0, 0xe7, 0x87, + 0x00, 0x08, 0x22, 0xc0, 0xe7, 0x67, 0xff, 0xf7, + 0x22, 0xc0, 0xe7, 0x77, 0x00, 0x08, 0x20, 0xc0, + 0x04, 0xc1, 0xe7, 0x87, 0x00, 0x08, 0x22, 0xc0, + 0x97, 0xcf, 0xe7, 0x07, 0x01, 0x01, 0xf0, 0x09, + 0xef, 0x57, 0x18, 0x00, 0xfe, 0xff, 0x97, 0xc2, + 0xef, 0x07, 0x00, 0x00, 0xf0, 0x09, 0x97, 0xcf, + 0xd7, 0x09, 0x00, 0xc0, 0x17, 0x00, 0x17, 0x02, + 0x97, 0x02, 0xe7, 0x57, 0x00, 0x00, 0x7a, 0x08, + 0x06, 0xc0, 0xc0, 0x09, 0x92, 0xc0, 0xc0, 0x77, + 0x09, 0x02, 0x9f, 0xc1, 0xea, 0x06, 0x9f, 0xcf, + 0x20, 0x08, 0xd7, 0x09, 0x0e, 0xc0, 0xe7, 0x07, + 0x00, 0x00, 0x0e, 0xc0, 0x9f, 0xaf, 0x66, 0x0e, + 0xe7, 0x05, 0x0e, 0xc0, 0x97, 0xcf, 0xd7, 0x09, + 0x00, 0xc0, 0x17, 0x02, 0xc8, 0x09, 0xb0, 0xc0, + 0xe7, 0x67, 0xfe, 0x7f, 0xb0, 0xc0, 0xc8, 0x77, + 0x00, 0x20, 0x9f, 0xc1, 0x64, 0xeb, 0xe7, 0x57, + 0x00, 0x00, 0xc8, 0x02, 0x9f, 0xc1, 0x80, 0xeb, + 0xc8, 0x99, 0xca, 0x02, 0xc8, 0x67, 0x04, 0x00, + 0x9f, 0xc1, 0x96, 0xeb, 0x9f, 0xcf, 0x4c, 0xeb, + 0xe7, 0x07, 0x00, 0x00, 0xa6, 0xc0, 0xe7, 0x09, + 0xb0, 0xc0, 0xc8, 0x02, 0xe7, 0x07, 0x03, 0x00, + 0xb0, 0xc0, 0x97, 0xcf, 0xc0, 0x09, 0x86, 0x08, + 0xc0, 0x37, 0x01, 0x00, 0x97, 0xc9, 0xc9, 0x09, + 0x88, 0x08, 0x02, 0x00, 0x41, 0x90, 0x48, 0x02, + 0xc9, 0x17, 0x06, 0x00, 0x9f, 0xaf, 0x64, 0x05, + 0x9f, 0xa2, 0xd6, 0x0e, 0x02, 0xda, 0x77, 0xc1, + 0x41, 0x60, 0x71, 0xc1, 0x97, 0xcf, 0x17, 0x02, + 0x57, 0x02, 0x43, 0x04, 0x21, 0x04, 0xe0, 0x00, + 0x43, 0x04, 0x21, 0x04, 0xe0, 0x00, 0x43, 0x04, + 0x21, 0x04, 0xe0, 0x00, 0xc1, 0x07, 0x01, 0x00, + 0xc9, 0x05, 0xc8, 0x05, 0x97, 0xcf, + 0, 0 +}; + +/* Firmware fixup (data?) segment */ +static unsigned char kue_fix_seg[] = +{ + /******************************************/ + /* NOTE: B6/C3 is data header signature */ + /* 0xAA/0xBB is data length = total */ + /* bytes - 7, 0xCC is type, 0xDD is */ + /* interrupt to use. */ + /******************************************/ + 0xB6, 0xC3, 0xc9, 0x02, 0x03, 0x64, + 0x02, 0x00, 0x08, 0x00, 0x24, 0x00, 0x2e, 0x00, + 0x2c, 0x00, 0x3e, 0x00, 0x44, 0x00, 0x48, 0x00, + 0x50, 0x00, 0x5c, 0x00, 0x60, 0x00, 0x66, 0x00, + 0x6c, 0x00, 0x70, 0x00, 0x76, 0x00, 0x74, 0x00, + 0x7a, 0x00, 0x7e, 0x00, 0x84, 0x00, 0x8a, 0x00, + 0x8e, 0x00, 0x92, 0x00, 0x98, 0x00, 0x9c, 0x00, + 0xa0, 0x00, 0xa8, 0x00, 0xae, 0x00, 0xb4, 0x00, + 0xb2, 0x00, 0xba, 0x00, 0xbe, 0x00, 0xc4, 0x00, + 0xc8, 0x00, 0xce, 0x00, 0xd2, 0x00, 0xd6, 0x00, + 0xda, 0x00, 0xe2, 0x00, 0xe0, 0x00, 0xea, 0x00, + 0xf2, 0x00, 0xfe, 0x00, 0x06, 0x01, 0x0c, 0x01, + 0x1a, 0x01, 0x24, 0x01, 0x22, 0x01, 0x2a, 0x01, + 0x30, 0x01, 0x36, 0x01, 0x3c, 0x01, 0x4e, 0x01, + 0x52, 0x01, 0x58, 0x01, 0x5c, 0x01, 0x9c, 0x01, + 0xb6, 0x01, 0xba, 0x01, 0xc0, 0x01, 0xca, 0x01, + 0xd0, 0x01, 0xda, 0x01, 0xe2, 0x01, 0xea, 0x01, + 0xf0, 0x01, 0x0a, 0x02, 0x0e, 0x02, 0x14, 0x02, + 0x26, 0x02, 0x6c, 0x02, 0x8e, 0x02, 0x98, 0x02, + 0xa0, 0x02, 0xa6, 0x02, 0xba, 0x02, 0xc6, 0x02, + 0xce, 0x02, 0xe8, 0x02, 0xee, 0x02, 0xf4, 0x02, + 0xf8, 0x02, 0x0a, 0x03, 0x10, 0x03, 0x1a, 0x03, + 0x1e, 0x03, 0x2a, 0x03, 0x2e, 0x03, 0x34, 0x03, + 0x3a, 0x03, 0x44, 0x03, 0x4e, 0x03, 0x5a, 0x03, + 0x5e, 0x03, 0x6a, 0x03, 0x72, 0x03, 0x80, 0x03, + 0x84, 0x03, 0x8c, 0x03, 0x94, 0x03, 0x98, 0x03, + 0xa8, 0x03, 0xae, 0x03, 0xb4, 0x03, 0xba, 0x03, + 0xce, 0x03, 0xcc, 0x03, 0xd6, 0x03, 0xdc, 0x03, + 0xec, 0x03, 0xf0, 0x03, 0xfe, 0x03, 0x1c, 0x04, + 0x30, 0x04, 0x38, 0x04, 0x3c, 0x04, 0x40, 0x04, + 0x48, 0x04, 0x46, 0x04, 0x54, 0x04, 0x5e, 0x04, + 0x64, 0x04, 0x74, 0x04, 0x78, 0x04, 0x84, 0x04, + 0xd8, 0x04, 0xec, 0x04, 0xf0, 0x04, 0xf8, 0x04, + 0xfe, 0x04, 0x1c, 0x05, 0x2c, 0x05, 0x30, 0x05, + 0x4a, 0x05, 0x56, 0x05, 0x5a, 0x05, 0x88, 0x05, + 0x8c, 0x05, 0x96, 0x05, 0x9a, 0x05, 0xa8, 0x05, + 0xcc, 0x05, 0xd2, 0x05, 0xda, 0x05, 0xe0, 0x05, + 0xe4, 0x05, 0xfc, 0x05, 0x06, 0x06, 0x14, 0x06, + 0x12, 0x06, 0x1a, 0x06, 0x20, 0x06, 0x26, 0x06, + 0x2e, 0x06, 0x34, 0x06, 0x48, 0x06, 0x52, 0x06, + 0x64, 0x06, 0x86, 0x06, 0x90, 0x06, 0x9a, 0x06, + 0xa0, 0x06, 0xac, 0x06, 0xaa, 0x06, 0xb2, 0x06, + 0xb8, 0x06, 0xdc, 0x06, 0xda, 0x06, 0xe2, 0x06, + 0xe8, 0x06, 0xf2, 0x06, 0xf8, 0x06, 0xfc, 0x06, + 0x0a, 0x07, 0x10, 0x07, 0x14, 0x07, 0x24, 0x07, + 0x2a, 0x07, 0x32, 0x07, 0x38, 0x07, 0xb2, 0x07, + 0xba, 0x07, 0xde, 0x07, 0xe4, 0x07, 0x10, 0x08, + 0x14, 0x08, 0x1a, 0x08, 0x1e, 0x08, 0x30, 0x08, + 0x38, 0x08, 0x3c, 0x08, 0x44, 0x08, 0x42, 0x08, + 0x48, 0x08, 0xc6, 0x08, 0xcc, 0x08, 0xd2, 0x08, + 0xfe, 0x08, 0x04, 0x09, 0x0a, 0x09, 0x0e, 0x09, + 0x12, 0x09, 0x16, 0x09, 0x20, 0x09, 0x24, 0x09, + 0x28, 0x09, 0x32, 0x09, 0x46, 0x09, 0x4a, 0x09, + 0x50, 0x09, 0x54, 0x09, 0x5a, 0x09, 0x60, 0x09, + 0x7c, 0x09, 0x80, 0x09, 0xb8, 0x09, 0xbc, 0x09, + 0xc0, 0x09, 0xc4, 0x09, 0xc8, 0x09, 0xcc, 0x09, + 0xd0, 0x09, 0xd4, 0x09, 0xec, 0x09, 0xf4, 0x09, + 0xf6, 0x09, 0xf8, 0x09, 0xfa, 0x09, 0xfc, 0x09, + 0xfe, 0x09, 0x00, 0x0a, 0x02, 0x0a, 0x04, 0x0a, + 0x06, 0x0a, 0x08, 0x0a, 0x0a, 0x0a, 0x0c, 0x0a, + 0x10, 0x0a, 0x18, 0x0a, 0x24, 0x0a, 0x2c, 0x0a, + 0x32, 0x0a, 0x3c, 0x0a, 0x46, 0x0a, 0x4c, 0x0a, + 0x50, 0x0a, 0x54, 0x0a, 0x5a, 0x0a, 0x5e, 0x0a, + 0x66, 0x0a, 0x6c, 0x0a, 0x72, 0x0a, 0x78, 0x0a, + 0x7e, 0x0a, 0x7c, 0x0a, 0x82, 0x0a, 0x8c, 0x0a, + 0x92, 0x0a, 0x90, 0x0a, 0x98, 0x0a, 0x96, 0x0a, + 0xa2, 0x0a, 0xb2, 0x0a, 0xb6, 0x0a, 0xc4, 0x0a, + 0xe2, 0x0a, 0xe0, 0x0a, 0xe8, 0x0a, 0xee, 0x0a, + 0xf4, 0x0a, 0xf2, 0x0a, 0xf8, 0x0a, 0x0c, 0x0b, + 0x1a, 0x0b, 0x24, 0x0b, 0x40, 0x0b, 0x44, 0x0b, + 0x48, 0x0b, 0x4e, 0x0b, 0x4c, 0x0b, 0x52, 0x0b, + 0x68, 0x0b, 0x6c, 0x0b, 0x70, 0x0b, 0x76, 0x0b, + 0x88, 0x0b, 0x92, 0x0b, 0xbe, 0x0b, 0xca, 0x0b, + 0xce, 0x0b, 0xde, 0x0b, 0xf4, 0x0b, 0xfa, 0x0b, + 0x00, 0x0c, 0x24, 0x0c, 0x28, 0x0c, 0x30, 0x0c, + 0x36, 0x0c, 0x3c, 0x0c, 0x40, 0x0c, 0x4a, 0x0c, + 0x50, 0x0c, 0x58, 0x0c, 0x56, 0x0c, 0x5c, 0x0c, + 0x60, 0x0c, 0x64, 0x0c, 0x80, 0x0c, 0x94, 0x0c, + 0x9a, 0x0c, 0x98, 0x0c, 0x9e, 0x0c, 0xa4, 0x0c, + 0xa2, 0x0c, 0xa8, 0x0c, 0xac, 0x0c, 0xb0, 0x0c, + 0xb4, 0x0c, 0xb8, 0x0c, 0xbc, 0x0c, 0xce, 0x0c, + 0xd2, 0x0c, 0xd6, 0x0c, 0xf4, 0x0c, 0xfa, 0x0c, + 0x00, 0x0d, 0xfe, 0x0c, 0x06, 0x0d, 0x0e, 0x0d, + 0x0c, 0x0d, 0x16, 0x0d, 0x1c, 0x0d, 0x22, 0x0d, + 0x20, 0x0d, 0x30, 0x0d, 0x7e, 0x0d, 0x82, 0x0d, + 0x9a, 0x0d, 0xa0, 0x0d, 0xa6, 0x0d, 0xb0, 0x0d, + 0xb8, 0x0d, 0xc2, 0x0d, 0xc8, 0x0d, 0xce, 0x0d, + 0xd4, 0x0d, 0xdc, 0x0d, 0x1e, 0x0e, 0x2c, 0x0e, + 0x3e, 0x0e, 0x4c, 0x0e, 0x50, 0x0e, 0x5e, 0x0e, + 0xae, 0x0e, 0xb8, 0x0e, 0xc6, 0x0e, 0xca, 0x0e, + 0, 0 +}; + +/* Fixup command. */ +#define KUE_TRIGCMD_OFFSET 5 +static unsigned char kue_trig_seg[] = { + 0xb6, 0xc3, 0x01, 0x00, 0x06, 0x64, 0x00, 0x00 +}; diff --git a/sys/bus/u4b/net/if_kuereg.h b/sys/bus/u4b/net/if_kuereg.h new file mode 100644 index 0000000000..16ad044d07 --- /dev/null +++ b/sys/bus/u4b/net/if_kuereg.h @@ -0,0 +1,141 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Definitions for the KLSI KL5KUSB101B USB to ethernet controller. + * The KLSI part is controlled via vendor control requests, the structure + * of which depend a bit on the firmware running on the internal + * microcontroller. The one exception is the 'send scan data' command, + * which is used to load the firmware. + */ + +#define KUE_CMD_GET_ETHER_DESCRIPTOR 0x00 +#define KUE_CMD_SET_MCAST_FILTERS 0x01 +#define KUE_CMD_SET_PKT_FILTER 0x02 +#define KUE_CMD_GET_ETHERSTATS 0x03 +#define KUE_CMD_GET_GPIO 0x04 +#define KUE_CMD_SET_GPIO 0x05 +#define KUE_CMD_SET_MAC 0x06 +#define KUE_CMD_GET_MAC 0x07 +#define KUE_CMD_SET_URB_SIZE 0x08 +#define KUE_CMD_SET_SOFS 0x09 +#define KUE_CMD_SET_EVEN_PKTS 0x0A +#define KUE_CMD_SEND_SCAN 0xFF + +struct kue_ether_desc { + uint8_t kue_len; + uint8_t kue_rsvd0; + uint8_t kue_rsvd1; + uint8_t kue_macaddr[ETHER_ADDR_LEN]; + uint8_t kue_etherstats[4]; + uint8_t kue_maxseg[2]; + uint8_t kue_mcastfilt[2]; + uint8_t kue_rsvd2; +} __packed; + +#define KUE_ETHERSTATS(x) UGETDW((x)->sc_desc.kue_etherstats) +#define KUE_MAXSEG(x) UGETW((x)->sc_desc.kue_maxseg) +#define KUE_MCFILTCNT(x) (UGETW((x)->sc_desc.kue_mcastfilt) & 0x7FFF) +#define KUE_MCFILT(x, y) \ + (char *)&(sc->sc_mcfilters[y * ETHER_ADDR_LEN]) + +#define KUE_STAT_TX_OK 0x00000001 +#define KUE_STAT_RX_OK 0x00000002 +#define KUE_STAT_TX_ERR 0x00000004 +#define KUE_STAT_RX_ERR 0x00000008 +#define KUE_STAT_RX_NOBUF 0x00000010 +#define KUE_STAT_TX_UCAST_BYTES 0x00000020 +#define KUE_STAT_TX_UCAST_FRAMES 0x00000040 +#define KUE_STAT_TX_MCAST_BYTES 0x00000080 +#define KUE_STAT_TX_MCAST_FRAMES 0x00000100 +#define KUE_STAT_TX_BCAST_BYTES 0x00000200 +#define KUE_STAT_TX_BCAST_FRAMES 0x00000400 +#define KUE_STAT_RX_UCAST_BYTES 0x00000800 +#define KUE_STAT_RX_UCAST_FRAMES 0x00001000 +#define KUE_STAT_RX_MCAST_BYTES 0x00002000 +#define KUE_STAT_RX_MCAST_FRAMES 0x00004000 +#define KUE_STAT_RX_BCAST_BYTES 0x00008000 +#define KUE_STAT_RX_BCAST_FRAMES 0x00010000 +#define KUE_STAT_RX_CRCERR 0x00020000 +#define KUE_STAT_TX_QUEUE_LENGTH 0x00040000 +#define KUE_STAT_RX_ALIGNERR 0x00080000 +#define KUE_STAT_TX_SINGLECOLL 0x00100000 +#define KUE_STAT_TX_MULTICOLL 0x00200000 +#define KUE_STAT_TX_DEFERRED 0x00400000 +#define KUE_STAT_TX_MAXCOLLS 0x00800000 +#define KUE_STAT_RX_OVERRUN 0x01000000 +#define KUE_STAT_TX_UNDERRUN 0x02000000 +#define KUE_STAT_TX_SQE_ERR 0x04000000 +#define KUE_STAT_TX_CARRLOSS 0x08000000 +#define KUE_STAT_RX_LATECOLL 0x10000000 + +#define KUE_RXFILT_PROMISC 0x0001 +#define KUE_RXFILT_ALLMULTI 0x0002 +#define KUE_RXFILT_UNICAST 0x0004 +#define KUE_RXFILT_BROADCAST 0x0008 +#define KUE_RXFILT_MULTICAST 0x0010 + +#define KUE_TIMEOUT 1000 +#define KUE_MIN_FRAMELEN 60 + +#define KUE_CTL_READ 0x01 +#define KUE_CTL_WRITE 0x02 + +#define KUE_CONFIG_IDX 0 /* config number 1 */ +#define KUE_IFACE_IDX 0 + +/* The interrupt endpoint is currently unused by the KLSI part. */ +#define KUE_ENDPT_MAX 4 +enum { + KUE_BULK_DT_WR, + KUE_BULK_DT_RD, + KUE_N_TRANSFER, +}; + +struct kue_softc { + struct usb_ether sc_ue; + struct mtx sc_mtx; + struct kue_ether_desc sc_desc; + struct usb_xfer *sc_xfer[KUE_N_TRANSFER]; + uint8_t *sc_mcfilters; + + int sc_flags; +#define KUE_FLAG_LINK 0x0001 + + uint16_t sc_rxfilt; +}; + +#define KUE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define KUE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define KUE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t) diff --git a/sys/bus/u4b/net/if_mos.c b/sys/bus/u4b/net/if_mos.c new file mode 100644 index 0000000000..7dd889efda --- /dev/null +++ b/sys/bus/u4b/net/if_mos.c @@ -0,0 +1,1023 @@ +/*- + * Copyright (c) 2011 Rick van der Zwet + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*- + * Copyright (c) 2008 Johann Christian Rode + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*- + * Copyright (c) 2005, 2006, 2007 Jonathan Gray + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*- + * Copyright (c) 1997, 1998, 1999, 2000-2003 + * Bill Paul . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Moschip MCS7730/MCS7830 USB to Ethernet controller + * The datasheet is available at the following URL: + * http://www.moschip.com/data/products/MCS7830/Data%20Sheet_7830.pdf + */ + +/* + * The FreeBSD if_mos.c driver is based on various different sources: + * The vendor provided driver at the following URL: + * http://www.moschip.com/data/products/MCS7830/Driver_FreeBSD_7830.tar.gz + * + * Mixed together with the OpenBSD if_mos.c driver for validation and checking + * and the FreeBSD if_reu.c as reference for the USB Ethernet framework. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR mos_debug +#include +#include + +#include + +//#include +#include "if_mosreg.h" + +#ifdef USB_DEBUG +static int mos_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, mos, CTLFLAG_RW, 0, "USB mos"); +SYSCTL_INT(_hw_usb_mos, OID_AUTO, debug, CTLFLAG_RW, &mos_debug, 0, + "Debug level"); +#endif + +#define MOS_DPRINTFN(fmt,...) \ + DPRINTF("mos: %s: " fmt "\n",__FUNCTION__,## __VA_ARGS__) + +#define USB_PRODUCT_MOSCHIP_MCS7730 0x7730 +#define USB_PRODUCT_SITECOMEU_LN030 0x0021 + + + +/* Various supported device vendors/products. */ +static const STRUCT_USB_HOST_ID mos_devs[] = { + {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7730, MCS7730)}, + {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7830, MCS7830)}, + {USB_VPI(USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_LN030, MCS7830)}, +}; + +static int mos_probe(device_t dev); +static int mos_attach(device_t dev); +static void mos_attach_post(struct usb_ether *ue); +static int mos_detach(device_t dev); + +static void mos_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error); +static void mos_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error); +static void mos_intr_callback(struct usb_xfer *xfer, usb_error_t error); +static void mos_tick(struct usb_ether *); +static void mos_start(struct usb_ether *); +static void mos_init(struct usb_ether *); +static void mos_chip_init(struct mos_softc *); +static void mos_stop(struct usb_ether *); +static int mos_miibus_readreg(device_t, int, int); +static int mos_miibus_writereg(device_t, int, int, int); +static void mos_miibus_statchg(device_t); +static int mos_ifmedia_upd(struct ifnet *); +static void mos_ifmedia_sts(struct ifnet *, struct ifmediareq *); +static void mos_reset(struct mos_softc *sc); + +static int mos_reg_read_1(struct mos_softc *, int); +static int mos_reg_read_2(struct mos_softc *, int); +static int mos_reg_write_1(struct mos_softc *, int, int); +static int mos_reg_write_2(struct mos_softc *, int, int); +static int mos_readmac(struct mos_softc *, uint8_t *); +static int mos_writemac(struct mos_softc *, uint8_t *); +static int mos_write_mcast(struct mos_softc *, u_char *); + +static void mos_setmulti(struct usb_ether *); +static void mos_setpromisc(struct usb_ether *); + +static const struct usb_config mos_config[MOS_ENDPT_MAX] = { + + [MOS_ENDPT_TX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = (MCLBYTES + 2), + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = mos_bulk_write_callback, + .timeout = 10000, + }, + + [MOS_ENDPT_RX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = (MCLBYTES + 4 + ETHER_CRC_LEN), + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = mos_bulk_read_callback, + }, + + [MOS_ENDPT_INTR] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, + .callback = mos_intr_callback, + }, +}; + +static device_method_t mos_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, mos_probe), + DEVMETHOD(device_attach, mos_attach), + DEVMETHOD(device_detach, mos_detach), + + /* MII interface */ + DEVMETHOD(miibus_readreg, mos_miibus_readreg), + DEVMETHOD(miibus_writereg, mos_miibus_writereg), + DEVMETHOD(miibus_statchg, mos_miibus_statchg), + + DEVMETHOD_END +}; + +static driver_t mos_driver = { + .name = "mos", + .methods = mos_methods, + .size = sizeof(struct mos_softc) +}; + +static devclass_t mos_devclass; + +DRIVER_MODULE(mos, uhub, mos_driver, mos_devclass, NULL, 0); +DRIVER_MODULE(miibus, mos, miibus_driver, miibus_devclass, 0, 0); +MODULE_DEPEND(mos, uether, 1, 1, 1); +MODULE_DEPEND(mos, usb, 1, 1, 1); +MODULE_DEPEND(mos, ether, 1, 1, 1); +MODULE_DEPEND(mos, miibus, 1, 1, 1); + +static const struct usb_ether_methods mos_ue_methods = { + .ue_attach_post = mos_attach_post, + .ue_start = mos_start, + .ue_init = mos_init, + .ue_stop = mos_stop, + .ue_tick = mos_tick, + .ue_setmulti = mos_setmulti, + .ue_setpromisc = mos_setpromisc, + .ue_mii_upd = mos_ifmedia_upd, + .ue_mii_sts = mos_ifmedia_sts, +}; + + +static int +mos_reg_read_1(struct mos_softc *sc, int reg) +{ + struct usb_device_request req; + usb_error_t err; + uByte val = 0; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = MOS_UR_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + err = uether_do_request(&sc->sc_ue, &req, &val, 1000); + + if (err) { + MOS_DPRINTFN("mos_reg_read_1 error, reg: %d\n", reg); + return (-1); + } + return (val); +} + +static int +mos_reg_read_2(struct mos_softc *sc, int reg) +{ + struct usb_device_request req; + usb_error_t err; + uWord val; + + USETW(val, 0); + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = MOS_UR_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 2); + + err = uether_do_request(&sc->sc_ue, &req, &val, 1000); + + if (err) { + MOS_DPRINTFN("mos_reg_read_2 error, reg: %d", reg); + return (-1); + } + return (UGETW(val)); +} + +static int +mos_reg_write_1(struct mos_softc *sc, int reg, int aval) +{ + struct usb_device_request req; + usb_error_t err; + uByte val; + val = aval; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = MOS_UR_WRITEREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + err = uether_do_request(&sc->sc_ue, &req, &val, 1000); + + if (err) { + MOS_DPRINTFN("mos_reg_write_1 error, reg: %d", reg); + return (-1); + } + return (0); +} + +static int +mos_reg_write_2(struct mos_softc *sc, int reg, int aval) +{ + struct usb_device_request req; + usb_error_t err; + uWord val; + + USETW(val, aval); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = MOS_UR_WRITEREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 2); + + err = uether_do_request(&sc->sc_ue, &req, &val, 1000); + + if (err) { + MOS_DPRINTFN("mos_reg_write_2 error, reg: %d", reg); + return (-1); + } + return (0); +} + +static int +mos_readmac(struct mos_softc *sc, u_char *mac) +{ + struct usb_device_request req; + usb_error_t err; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = MOS_UR_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, MOS_MAC); + USETW(req.wLength, ETHER_ADDR_LEN); + + err = uether_do_request(&sc->sc_ue, &req, mac, 1000); + + if (err) { + return (-1); + } + return (0); +} + +static int +mos_writemac(struct mos_softc *sc, uint8_t *mac) +{ + struct usb_device_request req; + usb_error_t err; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = MOS_UR_WRITEREG; + USETW(req.wValue, 0); + USETW(req.wIndex, MOS_MAC); + USETW(req.wLength, ETHER_ADDR_LEN); + + err = uether_do_request(&sc->sc_ue, &req, mac, 1000); + + if (err) { + MOS_DPRINTFN("mos_writemac error"); + return (-1); + } + return (0); +} + +static int +mos_write_mcast(struct mos_softc *sc, u_char *hashtbl) +{ + struct usb_device_request req; + usb_error_t err; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = MOS_UR_WRITEREG; + USETW(req.wValue, 0); + USETW(req.wIndex, MOS_MCAST_TABLE); + USETW(req.wLength, 8); + + err = uether_do_request(&sc->sc_ue, &req, hashtbl, 1000); + + if (err) { + MOS_DPRINTFN("mos_reg_mcast error"); + return (-1); + } + return (0); +} + +static int +mos_miibus_readreg(struct device *dev, int phy, int reg) +{ + struct mos_softc *sc = device_get_softc(dev); + uWord val; + int i, res, locked; + + USETW(val, 0); + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + MOS_LOCK(sc); + + mos_reg_write_2(sc, MOS_PHY_DATA, 0); + mos_reg_write_1(sc, MOS_PHY_CTL, (phy & MOS_PHYCTL_PHYADDR) | + MOS_PHYCTL_READ); + mos_reg_write_1(sc, MOS_PHY_STS, (reg & MOS_PHYSTS_PHYREG) | + MOS_PHYSTS_PENDING); + + for (i = 0; i < MOS_TIMEOUT; i++) { + if (mos_reg_read_1(sc, MOS_PHY_STS) & MOS_PHYSTS_READY) + break; + } + if (i == MOS_TIMEOUT) { + MOS_DPRINTFN("MII read timeout"); + } + res = mos_reg_read_2(sc, MOS_PHY_DATA); + + if (!locked) + MOS_UNLOCK(sc); + return (res); +} + +static int +mos_miibus_writereg(device_t dev, int phy, int reg, int val) +{ + struct mos_softc *sc = device_get_softc(dev); + int i, locked; + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + MOS_LOCK(sc); + + mos_reg_write_2(sc, MOS_PHY_DATA, val); + mos_reg_write_1(sc, MOS_PHY_CTL, (phy & MOS_PHYCTL_PHYADDR) | + MOS_PHYCTL_WRITE); + mos_reg_write_1(sc, MOS_PHY_STS, (reg & MOS_PHYSTS_PHYREG) | + MOS_PHYSTS_PENDING); + + for (i = 0; i < MOS_TIMEOUT; i++) { + if (mos_reg_read_1(sc, MOS_PHY_STS) & MOS_PHYSTS_READY) + break; + } + if (i == MOS_TIMEOUT) + MOS_DPRINTFN("MII write timeout"); + + if (!locked) + MOS_UNLOCK(sc); + return 0; +} + +static void +mos_miibus_statchg(device_t dev) +{ + struct mos_softc *sc = device_get_softc(dev); + struct mii_data *mii = GET_MII(sc); + int val, err, locked; + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + MOS_LOCK(sc); + + /* disable RX, TX prior to changing FDX, SPEEDSEL */ + val = mos_reg_read_1(sc, MOS_CTL); + val &= ~(MOS_CTL_TX_ENB | MOS_CTL_RX_ENB); + mos_reg_write_1(sc, MOS_CTL, val); + + /* reset register which counts dropped frames */ + mos_reg_write_1(sc, MOS_FRAME_DROP_CNT, 0); + + if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) + val |= MOS_CTL_FDX_ENB; + else + val &= ~(MOS_CTL_FDX_ENB); + + switch (IFM_SUBTYPE(mii->mii_media_active)) { + case IFM_100_TX: + val |= MOS_CTL_SPEEDSEL; + break; + case IFM_10_T: + val &= ~(MOS_CTL_SPEEDSEL); + break; + } + + /* re-enable TX, RX */ + val |= (MOS_CTL_TX_ENB | MOS_CTL_RX_ENB); + err = mos_reg_write_1(sc, MOS_CTL, val); + + if (err) + MOS_DPRINTFN("media change failed"); + + if (!locked) + MOS_UNLOCK(sc); +} + +/* + * Set media options. + */ +static int +mos_ifmedia_upd(struct ifnet *ifp) +{ + struct mos_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + struct mii_softc *miisc; + + MOS_LOCK_ASSERT(sc, MA_OWNED); + + sc->mos_link = 0; + if (mii->mii_instance) { + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) + mii_phy_reset(miisc); + } + mii_mediachg(mii); + return (0); +} + +/* + * Report current media status. + */ +static void +mos_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct mos_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + + MOS_LOCK(sc); + mii_pollstat(mii); + + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; + MOS_UNLOCK(sc); +} + +static void +mos_setpromisc(struct usb_ether *ue) +{ + struct mos_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + uint8_t rxmode; + + MOS_LOCK_ASSERT(sc, MA_OWNED); + + rxmode = mos_reg_read_1(sc, MOS_CTL); + + /* If we want promiscuous mode, set the allframes bit. */ + if (ifp->if_flags & IFF_PROMISC) { + rxmode |= MOS_CTL_RX_PROMISC; + } else { + rxmode &= ~MOS_CTL_RX_PROMISC; + } + + mos_reg_write_1(sc, MOS_CTL, rxmode); +} + + + +static void +mos_setmulti(struct usb_ether *ue) +{ + struct mos_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + struct ifmultiaddr *ifma; + + uint32_t h = 0; + uint8_t rxmode; + uint8_t hashtbl[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + int allmulti = 0; + + MOS_LOCK_ASSERT(sc, MA_OWNED); + + rxmode = mos_reg_read_1(sc, MOS_CTL); + + if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) + allmulti = 1; + + /* get all new ones */ + if_maddr_rlock(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + if (ifma->ifma_addr->sa_family != AF_LINK) { + allmulti = 1; + continue; + }; + h = ether_crc32_be(LLADDR((struct sockaddr_dl *) + ifma->ifma_addr), ETHER_ADDR_LEN) >> 26; + hashtbl[h / 8] |= 1 << (h % 8); + } + if_maddr_runlock(ifp); + + /* now program new ones */ + if (allmulti == 1) { + rxmode |= MOS_CTL_ALLMULTI; + mos_reg_write_1(sc, MOS_CTL, rxmode); + } else { + rxmode &= ~MOS_CTL_ALLMULTI; + mos_write_mcast(sc, (void *)&hashtbl); + mos_reg_write_1(sc, MOS_CTL, rxmode); + } +} + +static void +mos_reset(struct mos_softc *sc) +{ + uint8_t ctl; + + ctl = mos_reg_read_1(sc, MOS_CTL); + ctl &= ~(MOS_CTL_RX_PROMISC | MOS_CTL_ALLMULTI | MOS_CTL_TX_ENB | + MOS_CTL_RX_ENB); + /* Disable RX, TX, promiscuous and allmulticast mode */ + mos_reg_write_1(sc, MOS_CTL, ctl); + + /* Reset frame drop counter register to zero */ + mos_reg_write_1(sc, MOS_FRAME_DROP_CNT, 0); + + /* Wait a little while for the chip to get its brains in order. */ + usb_pause_mtx(&sc->sc_mtx, hz / 128); + return; +} + +static void +mos_chip_init(struct mos_softc *sc) +{ + int i; + + /* + * Rev.C devices have a pause threshold register which needs to be set + * at startup. + */ + if (mos_reg_read_1(sc, MOS_PAUSE_TRHD) != -1) { + for (i = 0; i < MOS_PAUSE_REWRITES; i++) + mos_reg_write_1(sc, MOS_PAUSE_TRHD, 0); + } + sc->mos_phyaddrs[0] = 1; + sc->mos_phyaddrs[1] = 0xFF; +} + +/* + * Probe for a MCS7x30 chip. + */ +static int +mos_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + int retval; + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != MOS_CONFIG_IDX) + return (ENXIO); + if (uaa->info.bIfaceIndex != MOS_IFACE_IDX) + return (ENXIO); + + retval = usbd_lookup_id_by_uaa(mos_devs, sizeof(mos_devs), uaa); + return (retval); +} + +/* + * Attach the interface. Allocate softc structures, do ifmedia + * setup and ethernet/BPF attach. + */ +static int +mos_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct mos_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + uint8_t iface_index; + int error; + + sc->mos_flags = USB_GET_DRIVER_INFO(uaa); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + iface_index = MOS_IFACE_IDX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, mos_config, MOS_ENDPT_MAX, + sc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating USB transfers failed\n"); + goto detach; + } + ue->ue_sc = sc; + ue->ue_dev = dev; + ue->ue_udev = uaa->device; + ue->ue_mtx = &sc->sc_mtx; + ue->ue_methods = &mos_ue_methods; + + + if (sc->mos_flags & MCS7730) { + MOS_DPRINTFN("model: MCS7730"); + } else if (sc->mos_flags & MCS7830) { + MOS_DPRINTFN("model: MCS7830"); + } + error = uether_ifattach(ue); + if (error) { + device_printf(dev, "could not attach interface\n"); + goto detach; + } + return (0); + + +detach: + mos_detach(dev); + return (ENXIO); +} + + +static void +mos_attach_post(struct usb_ether *ue) +{ + struct mos_softc *sc = uether_getsc(ue); + int err; + + /* Read MAC address, inform the world. */ + err = mos_readmac(sc, ue->ue_eaddr); + + if (err) + MOS_DPRINTFN("couldn't get MAC address"); + + MOS_DPRINTFN("address: %s", ether_sprintf(ue->ue_eaddr)); + + mos_chip_init(sc); +} + +static int +mos_detach(device_t dev) +{ + struct mos_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + + usbd_transfer_unsetup(sc->sc_xfer, MOS_ENDPT_MAX); + uether_ifdetach(ue); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + + + + +/* + * A frame has been uploaded: pass the resulting mbuf chain up to + * the higher level protocols. + */ +static void +mos_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct mos_softc *sc = usbd_xfer_softc(xfer); + struct usb_ether *ue = &sc->sc_ue; + struct ifnet *ifp = uether_getifp(ue); + + uint8_t rxstat = 0; + uint32_t actlen; + uint16_t pktlen = 0; + struct usb_page_cache *pc; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + pc = usbd_xfer_get_frame(xfer, 0); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + MOS_DPRINTFN("actlen : %d", actlen); + if (actlen <= 1) { + ifp->if_ierrors++; + goto tr_setup; + } + /* evaluate status byte at the end */ + usbd_copy_out(pc, actlen - sizeof(rxstat), &rxstat, + sizeof(rxstat)); + + if (rxstat != MOS_RXSTS_VALID) { + MOS_DPRINTFN("erroneous frame received"); + if (rxstat & MOS_RXSTS_SHORT_FRAME) + MOS_DPRINTFN("frame size less than 64 bytes"); + if (rxstat & MOS_RXSTS_LARGE_FRAME) { + MOS_DPRINTFN("frame size larger than " + "1532 bytes"); + } + if (rxstat & MOS_RXSTS_CRC_ERROR) + MOS_DPRINTFN("CRC error"); + if (rxstat & MOS_RXSTS_ALIGN_ERROR) + MOS_DPRINTFN("alignment error"); + ifp->if_ierrors++; + goto tr_setup; + } + /* Remember the last byte was used for the status fields */ + pktlen = actlen - 1; + if (pktlen < sizeof(struct ether_header)) { + MOS_DPRINTFN("error: pktlen %d is smaller " + "than ether_header %zd", pktlen, + sizeof(struct ether_header)); + ifp->if_ierrors++; + goto tr_setup; + } + uether_rxbuf(ue, pc, 0, actlen); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + uether_rxflush(ue); + return; + default: + MOS_DPRINTFN("bulk read error, %s", usbd_errstr(error)); + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + MOS_DPRINTFN("start rx %i", usbd_xfer_max_len(xfer)); + return; + } +} + +/* + * A frame was downloaded to the chip. It's safe for us to clean up + * the list buffers. + */ +static void +mos_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct mos_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct usb_page_cache *pc; + struct mbuf *m; + + + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + MOS_DPRINTFN("transfer of complete"); + ifp->if_opackets++; + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + /* + * XXX: don't send anything if there is no link? + */ + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + return; + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); + + usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len); + + + /* + * if there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + + m_freem(m); + + usbd_transfer_submit(xfer); + + ifp->if_opackets++; + return; + default: + MOS_DPRINTFN("usb error on tx: %s\n", usbd_errstr(error)); + ifp->if_oerrors++; + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +mos_tick(struct usb_ether *ue) +{ + struct mos_softc *sc = uether_getsc(ue); + struct mii_data *mii = GET_MII(sc); + + MOS_LOCK_ASSERT(sc, MA_OWNED); + + mii_tick(mii); + if (!sc->mos_link && mii->mii_media_status & IFM_ACTIVE && + IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { + MOS_DPRINTFN("got link"); + sc->mos_link++; + mos_start(ue); + } +} + + +static void +mos_start(struct usb_ether *ue) +{ + struct mos_softc *sc = uether_getsc(ue); + + /* + * start the USB transfers, if not already started: + */ + usbd_transfer_start(sc->sc_xfer[MOS_ENDPT_TX]); + usbd_transfer_start(sc->sc_xfer[MOS_ENDPT_RX]); + usbd_transfer_start(sc->sc_xfer[MOS_ENDPT_INTR]); +} + +static void +mos_init(struct usb_ether *ue) +{ + struct mos_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + uint8_t rxmode; + + MOS_LOCK_ASSERT(sc, MA_OWNED); + + /* Cancel pending I/O and free all RX/TX buffers. */ + mos_reset(sc); + + /* Write MAC address */ + mos_writemac(sc, IF_LLADDR(ifp)); + + /* Read and set transmitter IPG values */ + sc->mos_ipgs[0] = mos_reg_read_1(sc, MOS_IPG0); + sc->mos_ipgs[1] = mos_reg_read_1(sc, MOS_IPG1); + mos_reg_write_1(sc, MOS_IPG0, sc->mos_ipgs[0]); + mos_reg_write_1(sc, MOS_IPG1, sc->mos_ipgs[1]); + + /* + * Enable receiver and transmitter, bridge controls speed/duplex + * mode + */ + rxmode = mos_reg_read_1(sc, MOS_CTL); + rxmode |= MOS_CTL_RX_ENB | MOS_CTL_TX_ENB | MOS_CTL_BS_ENB; + rxmode &= ~(MOS_CTL_SLEEP); + + mos_setpromisc(ue); + + /* XXX: broadcast mode? */ + mos_reg_write_1(sc, MOS_CTL, rxmode); + + /* Load the multicast filter. */ + mos_setmulti(ue); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + mos_start(ue); +} + + +static void +mos_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct mos_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct usb_page_cache *pc; + uint32_t pkt; + int actlen; + + ifp->if_oerrors++; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + MOS_DPRINTFN("actlen %i", actlen); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &pkt, sizeof(pkt)); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + return; + default: + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + + +/* + * Stop the adapter and free any mbufs allocated to the + * RX and TX lists. + */ +static void +mos_stop(struct usb_ether *ue) +{ + struct mos_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + mos_reset(sc); + + MOS_LOCK_ASSERT(sc, MA_OWNED); + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + + /* stop all the transfers, if not already stopped */ + usbd_transfer_stop(sc->sc_xfer[MOS_ENDPT_TX]); + usbd_transfer_stop(sc->sc_xfer[MOS_ENDPT_RX]); + usbd_transfer_stop(sc->sc_xfer[MOS_ENDPT_INTR]); + + sc->mos_link = 0; +} diff --git a/sys/bus/u4b/net/if_mosreg.h b/sys/bus/u4b/net/if_mosreg.h new file mode 100644 index 0000000000..bab66f6a55 --- /dev/null +++ b/sys/bus/u4b/net/if_mosreg.h @@ -0,0 +1,175 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2010, 2011 Rick van der Zwet + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*- + * Copyright (c) 2008 Johann Christian Rode + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*- + * Copyright (c) 1997, 1998, 1999, 2000-2003 + * Bill Paul . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Ravikanth. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul, THE VOICES IN HIS HEAD OR + * THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* + * Register definitions for the Moschip MCS7x30 ethernet controller. + */ +#define MOS_MCAST_TABLE 0x00 +#define MOS_IPG0 0x08 +#define MOS_IPG1 0x09 +#define MOS_PHY_DATA0 0x0a +#define MOS_PHY_DATA1 0x0b +#define MOS_PHY_CTL 0x0c +#define MOS_PHY_STS 0x0d +#define MOS_PHY_DATA MOS_PHY_DATA0 +#define MOS_CTL 0x0e +#define MOS_MAC0 0x0f +#define MOS_MAC1 0x10 +#define MOS_MAC2 0x11 +#define MOS_MAC3 0x12 +#define MOS_MAC4 0x13 +#define MOS_MAC5 0x14 +#define MOS_MAC MOS_MAC0 +/* apparently only available on hardware rev. C */ +#define MOS_FRAME_DROP_CNT 0x15 +#define MOS_PAUSE_TRHD 0x16 + +#define MOS_PHYCTL_PHYADDR 0x1f +#define MOS_PHYCTL_WRITE 0x20 +#define MOS_PHYCTL_READ 0x40 + +#define MOS_PHYSTS_PHYREG 0x1f +#define MOS_PHYSTS_READY 0x40 +#define MOS_PHYSTS_PENDING 0x80 + +#define MOS_CTL_RX_PROMISC 0x01 +#define MOS_CTL_ALLMULTI 0x02 +#define MOS_CTL_SLEEP 0x04 +#define MOS_CTL_TX_ENB 0x08 +/* + * The documentation calls this bit 'reserved', but in the FreeBSD driver + * provided by the vendor, this enables the receiver. + */ +#define MOS_CTL_RX_ENB 0x10 +#define MOS_CTL_FDX_ENB 0x20 +/* 0 = 10 Mbps, 1 = 100 Mbps */ +#define MOS_CTL_SPEEDSEL 0x40 +/* 0 = PHY controls speed/duplex mode, 1 = bridge controls speed/duplex mode */ +#define MOS_CTL_BS_ENB 0x80 + +#define MOS_RXSTS_SHORT_FRAME 0x01 +#define MOS_RXSTS_LENGTH_ERROR 0x02 +#define MOS_RXSTS_ALIGN_ERROR 0x04 +#define MOS_RXSTS_CRC_ERROR 0x08 +#define MOS_RXSTS_LARGE_FRAME 0x10 +#define MOS_RXSTS_VALID 0x20 +/* + * The EtherType field of an Ethernet frame can contain values other than + * the frame length, hence length errors are ignored. + */ +#define MOS_RXSTS_MASK 0x3d + +#define MOS_PAUSE_TRHD_DEFAULT 0 +#define MOS_PAUSE_REWRITES 3 + +#define MOS_TIMEOUT 1000 + +#define MOS_RX_LIST_CNT 1 +#define MOS_TX_LIST_CNT 1 + +/* Maximum size of a fast ethernet frame plus one byte for the status */ +#define MOS_BUFSZ (ETHER_MAX_LEN+1) + +/* + * USB endpoints. + */ +#define MOS_ENDPT_RX 0 +#define MOS_ENDPT_TX 1 +#define MOS_ENDPT_INTR 2 +#define MOS_ENDPT_MAX 3 + +/* + * USB vendor requests. + */ +#define MOS_UR_READREG 0x0e +#define MOS_UR_WRITEREG 0x0d + +#define MOS_CONFIG_IDX 0 +#define MOS_IFACE_IDX 0 + +#define MCS7730 0x0001 +#define MCS7830 0x0002 + +#define MOS_INC(x, y) (x) = (x + 1) % y + +struct mos_softc { + struct usb_ether sc_ue; + struct ifnet ifp; + + struct mtx sc_mtx; + struct usb_xfer *sc_xfer[MOS_ENDPT_MAX]; + + uint16_t mos_flags; + + int mos_link; + unsigned char mos_ipgs[2]; + unsigned char mos_phyaddrs[2]; +}; + +#define GET_MII(sc) uether_getmii(&(sc)->sc_ue) +#define MOS_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define MOS_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define MOS_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t) diff --git a/sys/bus/u4b/net/if_rue.c b/sys/bus/u4b/net/if_rue.c new file mode 100644 index 0000000000..a870676503 --- /dev/null +++ b/sys/bus/u4b/net/if_rue.c @@ -0,0 +1,915 @@ +/*- + * Copyright (c) 2001-2003, Shunsuke Akiyama . + * Copyright (c) 1997, 1998, 1999, 2000 Bill Paul . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * RealTek RTL8150 USB to fast ethernet controller driver. + * Datasheet is available from + * ftp://ftp.realtek.com.tw/lancard/data_sheet/8150/. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR rue_debug +#include +#include + +#include +#include + +#ifdef USB_DEBUG +static int rue_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, rue, CTLFLAG_RW, 0, "USB rue"); +SYSCTL_INT(_hw_usb_rue, OID_AUTO, debug, CTLFLAG_RW, + &rue_debug, 0, "Debug level"); +#endif + +/* + * Various supported device vendors/products. + */ + +static const STRUCT_USB_HOST_ID rue_devs[] = { + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUAKTX, 0)}, + {USB_VPI(USB_VENDOR_REALTEK, USB_PRODUCT_REALTEK_USBKR100, 0)}, + {USB_VPI(USB_VENDOR_OQO, USB_PRODUCT_OQO_ETHER01, 0)}, +}; + +/* prototypes */ + +static device_probe_t rue_probe; +static device_attach_t rue_attach; +static device_detach_t rue_detach; + +static miibus_readreg_t rue_miibus_readreg; +static miibus_writereg_t rue_miibus_writereg; +static miibus_statchg_t rue_miibus_statchg; + +static usb_callback_t rue_intr_callback; +static usb_callback_t rue_bulk_read_callback; +static usb_callback_t rue_bulk_write_callback; + +static uether_fn_t rue_attach_post; +static uether_fn_t rue_init; +static uether_fn_t rue_stop; +static uether_fn_t rue_start; +static uether_fn_t rue_tick; +static uether_fn_t rue_setmulti; +static uether_fn_t rue_setpromisc; + +static int rue_read_mem(struct rue_softc *, uint16_t, void *, int); +static int rue_write_mem(struct rue_softc *, uint16_t, void *, int); +static uint8_t rue_csr_read_1(struct rue_softc *, uint16_t); +static uint16_t rue_csr_read_2(struct rue_softc *, uint16_t); +static int rue_csr_write_1(struct rue_softc *, uint16_t, uint8_t); +static int rue_csr_write_2(struct rue_softc *, uint16_t, uint16_t); +static int rue_csr_write_4(struct rue_softc *, int, uint32_t); + +static void rue_reset(struct rue_softc *); +static int rue_ifmedia_upd(struct ifnet *); +static void rue_ifmedia_sts(struct ifnet *, struct ifmediareq *); + +static const struct usb_config rue_config[RUE_N_TRANSFER] = { + + [RUE_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = MCLBYTES, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = rue_bulk_write_callback, + .timeout = 10000, /* 10 seconds */ + }, + + [RUE_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = (MCLBYTES + 4), + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = rue_bulk_read_callback, + .timeout = 0, /* no timeout */ + }, + + [RUE_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = rue_intr_callback, + }, +}; + +static device_method_t rue_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, rue_probe), + DEVMETHOD(device_attach, rue_attach), + DEVMETHOD(device_detach, rue_detach), + + /* MII interface */ + DEVMETHOD(miibus_readreg, rue_miibus_readreg), + DEVMETHOD(miibus_writereg, rue_miibus_writereg), + DEVMETHOD(miibus_statchg, rue_miibus_statchg), + + DEVMETHOD_END +}; + +static driver_t rue_driver = { + .name = "rue", + .methods = rue_methods, + .size = sizeof(struct rue_softc), +}; + +static devclass_t rue_devclass; + +DRIVER_MODULE(rue, uhub, rue_driver, rue_devclass, NULL, 0); +DRIVER_MODULE(miibus, rue, miibus_driver, miibus_devclass, 0, 0); +MODULE_DEPEND(rue, uether, 1, 1, 1); +MODULE_DEPEND(rue, usb, 1, 1, 1); +MODULE_DEPEND(rue, ether, 1, 1, 1); +MODULE_DEPEND(rue, miibus, 1, 1, 1); +MODULE_VERSION(rue, 1); + +static const struct usb_ether_methods rue_ue_methods = { + .ue_attach_post = rue_attach_post, + .ue_start = rue_start, + .ue_init = rue_init, + .ue_stop = rue_stop, + .ue_tick = rue_tick, + .ue_setmulti = rue_setmulti, + .ue_setpromisc = rue_setpromisc, + .ue_mii_upd = rue_ifmedia_upd, + .ue_mii_sts = rue_ifmedia_sts, +}; + +#define RUE_SETBIT(sc, reg, x) \ + rue_csr_write_1(sc, reg, rue_csr_read_1(sc, reg) | (x)) + +#define RUE_CLRBIT(sc, reg, x) \ + rue_csr_write_1(sc, reg, rue_csr_read_1(sc, reg) & ~(x)) + +static int +rue_read_mem(struct rue_softc *sc, uint16_t addr, void *buf, int len) +{ + struct usb_device_request req; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UR_SET_ADDRESS; + USETW(req.wValue, addr); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + + return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); +} + +static int +rue_write_mem(struct rue_softc *sc, uint16_t addr, void *buf, int len) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UR_SET_ADDRESS; + USETW(req.wValue, addr); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + + return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); +} + +static uint8_t +rue_csr_read_1(struct rue_softc *sc, uint16_t reg) +{ + uint8_t val; + + rue_read_mem(sc, reg, &val, 1); + return (val); +} + +static uint16_t +rue_csr_read_2(struct rue_softc *sc, uint16_t reg) +{ + uint8_t val[2]; + + rue_read_mem(sc, reg, &val, 2); + return (UGETW(val)); +} + +static int +rue_csr_write_1(struct rue_softc *sc, uint16_t reg, uint8_t val) +{ + return (rue_write_mem(sc, reg, &val, 1)); +} + +static int +rue_csr_write_2(struct rue_softc *sc, uint16_t reg, uint16_t val) +{ + uint8_t temp[2]; + + USETW(temp, val); + return (rue_write_mem(sc, reg, &temp, 2)); +} + +static int +rue_csr_write_4(struct rue_softc *sc, int reg, uint32_t val) +{ + uint8_t temp[4]; + + USETDW(temp, val); + return (rue_write_mem(sc, reg, &temp, 4)); +} + +static int +rue_miibus_readreg(device_t dev, int phy, int reg) +{ + struct rue_softc *sc = device_get_softc(dev); + uint16_t rval; + uint16_t ruereg; + int locked; + + if (phy != 0) /* RTL8150 supports PHY == 0, only */ + return (0); + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + RUE_LOCK(sc); + + switch (reg) { + case MII_BMCR: + ruereg = RUE_BMCR; + break; + case MII_BMSR: + ruereg = RUE_BMSR; + break; + case MII_ANAR: + ruereg = RUE_ANAR; + break; + case MII_ANER: + ruereg = RUE_AER; + break; + case MII_ANLPAR: + ruereg = RUE_ANLP; + break; + case MII_PHYIDR1: + case MII_PHYIDR2: + rval = 0; + goto done; + default: + if (RUE_REG_MIN <= reg && reg <= RUE_REG_MAX) { + rval = rue_csr_read_1(sc, reg); + goto done; + } + device_printf(sc->sc_ue.ue_dev, "bad phy register\n"); + rval = 0; + goto done; + } + + rval = rue_csr_read_2(sc, ruereg); +done: + if (!locked) + RUE_UNLOCK(sc); + return (rval); +} + +static int +rue_miibus_writereg(device_t dev, int phy, int reg, int data) +{ + struct rue_softc *sc = device_get_softc(dev); + uint16_t ruereg; + int locked; + + if (phy != 0) /* RTL8150 supports PHY == 0, only */ + return (0); + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + RUE_LOCK(sc); + + switch (reg) { + case MII_BMCR: + ruereg = RUE_BMCR; + break; + case MII_BMSR: + ruereg = RUE_BMSR; + break; + case MII_ANAR: + ruereg = RUE_ANAR; + break; + case MII_ANER: + ruereg = RUE_AER; + break; + case MII_ANLPAR: + ruereg = RUE_ANLP; + break; + case MII_PHYIDR1: + case MII_PHYIDR2: + goto done; + default: + if (RUE_REG_MIN <= reg && reg <= RUE_REG_MAX) { + rue_csr_write_1(sc, reg, data); + goto done; + } + device_printf(sc->sc_ue.ue_dev, " bad phy register\n"); + goto done; + } + rue_csr_write_2(sc, ruereg, data); +done: + if (!locked) + RUE_UNLOCK(sc); + return (0); +} + +static void +rue_miibus_statchg(device_t dev) +{ + /* + * When the code below is enabled the card starts doing weird + * things after link going from UP to DOWN and back UP. + * + * Looks like some of register writes below messes up PHY + * interface. + * + * No visible regressions were found after commenting this code + * out, so that disable it for good. + */ +#if 0 + struct rue_softc *sc = device_get_softc(dev); + struct mii_data *mii = GET_MII(sc); + uint16_t bmcr; + int locked; + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + RUE_LOCK(sc); + + RUE_CLRBIT(sc, RUE_CR, (RUE_CR_RE | RUE_CR_TE)); + + bmcr = rue_csr_read_2(sc, RUE_BMCR); + + if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX) + bmcr |= RUE_BMCR_SPD_SET; + else + bmcr &= ~RUE_BMCR_SPD_SET; + + if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) + bmcr |= RUE_BMCR_DUPLEX; + else + bmcr &= ~RUE_BMCR_DUPLEX; + + rue_csr_write_2(sc, RUE_BMCR, bmcr); + + RUE_SETBIT(sc, RUE_CR, (RUE_CR_RE | RUE_CR_TE)); + + if (!locked) + RUE_UNLOCK(sc); +#endif +} + +static void +rue_setpromisc(struct usb_ether *ue) +{ + struct rue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + RUE_LOCK_ASSERT(sc, MA_OWNED); + + /* If we want promiscuous mode, set the allframes bit. */ + if (ifp->if_flags & IFF_PROMISC) + RUE_SETBIT(sc, RUE_RCR, RUE_RCR_AAP); + else + RUE_CLRBIT(sc, RUE_RCR, RUE_RCR_AAP); +} + +/* + * Program the 64-bit multicast hash filter. + */ +static void +rue_setmulti(struct usb_ether *ue) +{ + struct rue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + uint16_t rxcfg; + int h = 0; + uint32_t hashes[2] = { 0, 0 }; + struct ifmultiaddr *ifma; + int mcnt = 0; + + RUE_LOCK_ASSERT(sc, MA_OWNED); + + rxcfg = rue_csr_read_2(sc, RUE_RCR); + + if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { + rxcfg |= (RUE_RCR_AAM | RUE_RCR_AAP); + rxcfg &= ~RUE_RCR_AM; + rue_csr_write_2(sc, RUE_RCR, rxcfg); + rue_csr_write_4(sc, RUE_MAR0, 0xFFFFFFFF); + rue_csr_write_4(sc, RUE_MAR4, 0xFFFFFFFF); + return; + } + + /* first, zot all the existing hash bits */ + rue_csr_write_4(sc, RUE_MAR0, 0); + rue_csr_write_4(sc, RUE_MAR4, 0); + + /* now program new ones */ + if_maddr_rlock(ifp); + TAILQ_FOREACH (ifma, &ifp->if_multiaddrs, ifma_link) + { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + h = ether_crc32_be(LLADDR((struct sockaddr_dl *) + ifma->ifma_addr), ETHER_ADDR_LEN) >> 26; + if (h < 32) + hashes[0] |= (1 << h); + else + hashes[1] |= (1 << (h - 32)); + mcnt++; + } + if_maddr_runlock(ifp); + + if (mcnt) + rxcfg |= RUE_RCR_AM; + else + rxcfg &= ~RUE_RCR_AM; + + rxcfg &= ~(RUE_RCR_AAM | RUE_RCR_AAP); + + rue_csr_write_2(sc, RUE_RCR, rxcfg); + rue_csr_write_4(sc, RUE_MAR0, hashes[0]); + rue_csr_write_4(sc, RUE_MAR4, hashes[1]); +} + +static void +rue_reset(struct rue_softc *sc) +{ + int i; + + rue_csr_write_1(sc, RUE_CR, RUE_CR_SOFT_RST); + + for (i = 0; i != RUE_TIMEOUT; i++) { + if (uether_pause(&sc->sc_ue, hz / 1000)) + break; + if (!(rue_csr_read_1(sc, RUE_CR) & RUE_CR_SOFT_RST)) + break; + } + if (i == RUE_TIMEOUT) + device_printf(sc->sc_ue.ue_dev, "reset never completed\n"); + + uether_pause(&sc->sc_ue, hz / 100); +} + +static void +rue_attach_post(struct usb_ether *ue) +{ + struct rue_softc *sc = uether_getsc(ue); + + /* reset the adapter */ + rue_reset(sc); + + /* get station address from the EEPROM */ + rue_read_mem(sc, RUE_EEPROM_IDR0, ue->ue_eaddr, ETHER_ADDR_LEN); +} + +/* + * Probe for a RTL8150 chip. + */ +static int +rue_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != RUE_CONFIG_IDX) + return (ENXIO); + if (uaa->info.bIfaceIndex != RUE_IFACE_IDX) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(rue_devs, sizeof(rue_devs), uaa)); +} + +/* + * Attach the interface. Allocate softc structures, do ifmedia + * setup and ethernet/BPF attach. + */ +static int +rue_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct rue_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + uint8_t iface_index; + int error; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + iface_index = RUE_IFACE_IDX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, rue_config, RUE_N_TRANSFER, + sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB transfers failed\n"); + goto detach; + } + + ue->ue_sc = sc; + ue->ue_dev = dev; + ue->ue_udev = uaa->device; + ue->ue_mtx = &sc->sc_mtx; + ue->ue_methods = &rue_ue_methods; + + error = uether_ifattach(ue); + if (error) { + device_printf(dev, "could not attach interface\n"); + goto detach; + } + return (0); /* success */ + +detach: + rue_detach(dev); + return (ENXIO); /* failure */ +} + +static int +rue_detach(device_t dev) +{ + struct rue_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + + usbd_transfer_unsetup(sc->sc_xfer, RUE_N_TRANSFER); + uether_ifdetach(ue); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +rue_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct rue_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct rue_intrpkt pkt; + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (ifp && (ifp->if_drv_flags & IFF_DRV_RUNNING) && + actlen >= sizeof(pkt)) { + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &pkt, sizeof(pkt)); + + ifp->if_ierrors += pkt.rue_rxlost_cnt; + ifp->if_ierrors += pkt.rue_crcerr_cnt; + ifp->if_collisions += pkt.rue_col_cnt; + } + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +rue_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct rue_softc *sc = usbd_xfer_softc(xfer); + struct usb_ether *ue = &sc->sc_ue; + struct ifnet *ifp = uether_getifp(ue); + struct usb_page_cache *pc; + uint16_t status; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen < 4) { + ifp->if_ierrors++; + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, actlen - 4, &status, sizeof(status)); + actlen -= 4; + + /* check recieve packet was valid or not */ + status = le16toh(status); + if ((status & RUE_RXSTAT_VALID) == 0) { + ifp->if_ierrors++; + goto tr_setup; + } + uether_rxbuf(ue, pc, 0, actlen); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + uether_rxflush(ue); + return; + + default: /* Error */ + DPRINTF("bulk read error, %s\n", + usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +rue_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct rue_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct usb_page_cache *pc; + struct mbuf *m; + int temp_len; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + ifp->if_opackets++; + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + if ((sc->sc_flags & RUE_FLAG_LINK) == 0) { + /* + * don't send anything if there is no link ! + */ + return; + } + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) + return; + if (m->m_pkthdr.len > MCLBYTES) + m->m_pkthdr.len = MCLBYTES; + temp_len = m->m_pkthdr.len; + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); + + /* + * This is an undocumented behavior. + * RTL8150 chip doesn't send frame length smaller than + * RUE_MIN_FRAMELEN (60) byte packet. + */ + if (temp_len < RUE_MIN_FRAMELEN) { + usbd_frame_zero(pc, temp_len, + RUE_MIN_FRAMELEN - temp_len); + temp_len = RUE_MIN_FRAMELEN; + } + usbd_xfer_set_frame_len(xfer, 0, temp_len); + + /* + * if there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + + m_freem(m); + + usbd_transfer_submit(xfer); + + return; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usbd_errstr(error)); + + ifp->if_oerrors++; + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +rue_tick(struct usb_ether *ue) +{ + struct rue_softc *sc = uether_getsc(ue); + struct mii_data *mii = GET_MII(sc); + + RUE_LOCK_ASSERT(sc, MA_OWNED); + + mii_tick(mii); + if ((sc->sc_flags & RUE_FLAG_LINK) == 0 + && mii->mii_media_status & IFM_ACTIVE && + IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { + sc->sc_flags |= RUE_FLAG_LINK; + rue_start(ue); + } +} + +static void +rue_start(struct usb_ether *ue) +{ + struct rue_softc *sc = uether_getsc(ue); + + /* + * start the USB transfers, if not already started: + */ + usbd_transfer_start(sc->sc_xfer[RUE_INTR_DT_RD]); + usbd_transfer_start(sc->sc_xfer[RUE_BULK_DT_RD]); + usbd_transfer_start(sc->sc_xfer[RUE_BULK_DT_WR]); +} + +static void +rue_init(struct usb_ether *ue) +{ + struct rue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + RUE_LOCK_ASSERT(sc, MA_OWNED); + + /* + * Cancel pending I/O + */ + rue_reset(sc); + + /* Set MAC address */ + rue_write_mem(sc, RUE_IDR0, IF_LLADDR(ifp), ETHER_ADDR_LEN); + + rue_stop(ue); + + /* + * Set the initial TX and RX configuration. + */ + rue_csr_write_1(sc, RUE_TCR, RUE_TCR_CONFIG); + rue_csr_write_2(sc, RUE_RCR, RUE_RCR_CONFIG|RUE_RCR_AB); + + /* Load the multicast filter */ + rue_setpromisc(ue); + /* Load the multicast filter. */ + rue_setmulti(ue); + + /* Enable RX and TX */ + rue_csr_write_1(sc, RUE_CR, (RUE_CR_TE | RUE_CR_RE | RUE_CR_EP3CLREN)); + + usbd_xfer_set_stall(sc->sc_xfer[RUE_BULK_DT_WR]); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + rue_start(ue); +} + +/* + * Set media options. + */ +static int +rue_ifmedia_upd(struct ifnet *ifp) +{ + struct rue_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + struct mii_softc *miisc; + + RUE_LOCK_ASSERT(sc, MA_OWNED); + + sc->sc_flags &= ~RUE_FLAG_LINK; + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) + PHY_RESET(miisc); + mii_mediachg(mii); + return (0); +} + +/* + * Report current media status. + */ +static void +rue_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct rue_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + + RUE_LOCK(sc); + mii_pollstat(mii); + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; + RUE_UNLOCK(sc); +} + +static void +rue_stop(struct usb_ether *ue) +{ + struct rue_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + RUE_LOCK_ASSERT(sc, MA_OWNED); + + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + sc->sc_flags &= ~RUE_FLAG_LINK; + + /* + * stop all the transfers, if not already stopped: + */ + usbd_transfer_stop(sc->sc_xfer[RUE_BULK_DT_WR]); + usbd_transfer_stop(sc->sc_xfer[RUE_BULK_DT_RD]); + usbd_transfer_stop(sc->sc_xfer[RUE_INTR_DT_RD]); + + rue_csr_write_1(sc, RUE_CR, 0x00); + + rue_reset(sc); +} diff --git a/sys/bus/u4b/net/if_ruereg.h b/sys/bus/u4b/net/if_ruereg.h new file mode 100644 index 0000000000..c90a969215 --- /dev/null +++ b/sys/bus/u4b/net/if_ruereg.h @@ -0,0 +1,178 @@ +/*- + * Copyright (c) 2001-2003, Shunsuke Akiyama . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#define RUE_CONFIG_IDX 0 /* config number 1 */ +#define RUE_IFACE_IDX 0 + +#define RUE_INTR_PKTLEN 0x8 + +#define RUE_TIMEOUT 50 +#define RUE_MIN_FRAMELEN 60 + +/* Registers. */ +#define RUE_IDR0 0x0120 +#define RUE_IDR1 0x0121 +#define RUE_IDR2 0x0122 +#define RUE_IDR3 0x0123 +#define RUE_IDR4 0x0124 +#define RUE_IDR5 0x0125 + +#define RUE_MAR0 0x0126 +#define RUE_MAR1 0x0127 +#define RUE_MAR2 0x0128 +#define RUE_MAR3 0x0129 +#define RUE_MAR4 0x012A +#define RUE_MAR5 0x012B +#define RUE_MAR6 0x012C +#define RUE_MAR7 0x012D + +#define RUE_CR 0x012E /* B, R/W */ +#define RUE_CR_SOFT_RST 0x10 +#define RUE_CR_RE 0x08 +#define RUE_CR_TE 0x04 +#define RUE_CR_EP3CLREN 0x02 + +#define RUE_TCR 0x012F /* B, R/W */ +#define RUE_TCR_TXRR1 0x80 +#define RUE_TCR_TXRR0 0x40 +#define RUE_TCR_IFG1 0x10 +#define RUE_TCR_IFG0 0x08 +#define RUE_TCR_NOCRC 0x01 +#define RUE_TCR_CONFIG (RUE_TCR_TXRR1 | RUE_TCR_TXRR0 | \ + RUE_TCR_IFG1 | RUE_TCR_IFG0) + +#define RUE_RCR 0x0130 /* W, R/W */ +#define RUE_RCR_TAIL 0x80 +#define RUE_RCR_AER 0x40 +#define RUE_RCR_AR 0x20 +#define RUE_RCR_AM 0x10 +#define RUE_RCR_AB 0x08 +#define RUE_RCR_AD 0x04 +#define RUE_RCR_AAM 0x02 +#define RUE_RCR_AAP 0x01 +#define RUE_RCR_CONFIG (RUE_RCR_TAIL | RUE_RCR_AD) + +#define RUE_TSR 0x0132 +#define RUE_RSR 0x0133 +#define RUE_CON0 0x0135 +#define RUE_CON1 0x0136 +#define RUE_MSR 0x0137 +#define RUE_PHYADD 0x0138 +#define RUE_PHYDAT 0x0139 + +#define RUE_PHYCNT 0x013B /* B, R/W */ +#define RUE_PHYCNT_PHYOWN 0x40 +#define RUE_PHYCNT_RWCR 0x20 + +#define RUE_GPPC 0x013D +#define RUE_WAKECNT 0x013E + +#define RUE_BMCR 0x0140 +#define RUE_BMCR_SPD_SET 0x2000 +#define RUE_BMCR_DUPLEX 0x0100 + +#define RUE_BMSR 0x0142 + +#define RUE_ANAR 0x0144 /* W, R/W */ +#define RUE_ANAR_PAUSE 0x0400 + +#define RUE_ANLP 0x0146 /* W, R/O */ +#define RUE_ANLP_PAUSE 0x0400 + +#define RUE_AER 0x0148 + +#define RUE_NWAYT 0x014A +#define RUE_CSCR 0x014C + +#define RUE_CRC0 0x014E +#define RUE_CRC1 0x0150 +#define RUE_CRC2 0x0152 +#define RUE_CRC3 0x0154 +#define RUE_CRC4 0x0156 + +#define RUE_BYTEMASK0 0x0158 +#define RUE_BYTEMASK1 0x0160 +#define RUE_BYTEMASK2 0x0168 +#define RUE_BYTEMASK3 0x0170 +#define RUE_BYTEMASK4 0x0178 + +#define RUE_PHY1 0x0180 +#define RUE_PHY2 0x0184 + +#define RUE_TW1 0x0186 + +#define RUE_REG_MIN 0x0120 +#define RUE_REG_MAX 0x0189 + +/* EEPROM address declarations. */ +#define RUE_EEPROM_BASE 0x1200 +#define RUE_EEPROM_IDR0 (RUE_EEPROM_BASE + 0x02) +#define RUE_EEPROM_IDR1 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_IDR2 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_IDR3 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_IDR4 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_IDR5 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_INTERVAL (RUE_EEPROM_BASE + 0x17) + +#define RUE_RXSTAT_VALID (0x01 << 12) +#define RUE_RXSTAT_RUNT (0x02 << 12) +#define RUE_RXSTAT_PMATCH (0x04 << 12) +#define RUE_RXSTAT_MCAST (0x08 << 12) + +#define GET_MII(sc) uether_getmii(&(sc)->sc_ue) + +struct rue_intrpkt { + uint8_t rue_tsr; + uint8_t rue_rsr; + uint8_t rue_gep_msr; + uint8_t rue_waksr; + uint8_t rue_txok_cnt; + uint8_t rue_rxlost_cnt; + uint8_t rue_crcerr_cnt; + uint8_t rue_col_cnt; +} __packed; + +enum { + RUE_BULK_DT_WR, + RUE_BULK_DT_RD, + RUE_INTR_DT_RD, + RUE_N_TRANSFER, +}; + +struct rue_softc { + struct usb_ether sc_ue; + struct mtx sc_mtx; + struct usb_xfer *sc_xfer[RUE_N_TRANSFER]; + + int sc_flags; +#define RUE_FLAG_LINK 0x0001 +}; + +#define RUE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define RUE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define RUE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t) diff --git a/sys/bus/u4b/net/if_udav.c b/sys/bus/u4b/net/if_udav.c new file mode 100644 index 0000000000..8160a3b5c3 --- /dev/null +++ b/sys/bus/u4b/net/if_udav.c @@ -0,0 +1,855 @@ +/* $NetBSD: if_udav.c,v 1.2 2003/09/04 15:17:38 tsutsui Exp $ */ +/* $nabe: if_udav.c,v 1.3 2003/08/21 16:57:19 nabe Exp $ */ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2003 + * Shingo WATANABE . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/* + * DM9601(DAVICOM USB to Ethernet MAC Controller with Integrated 10/100 PHY) + * The spec can be found at the following url. + * http://www.davicom.com.tw/big5/download/Data%20Sheet/DM9601-DS-P01-930914.pdf + */ + +/* + * TODO: + * Interrupt Endpoint support + * External PHYs + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR udav_debug +#include +#include + +#include +#include + +/* prototypes */ + +static device_probe_t udav_probe; +static device_attach_t udav_attach; +static device_detach_t udav_detach; + +static usb_callback_t udav_bulk_write_callback; +static usb_callback_t udav_bulk_read_callback; +static usb_callback_t udav_intr_callback; + +static uether_fn_t udav_attach_post; +static uether_fn_t udav_init; +static uether_fn_t udav_stop; +static uether_fn_t udav_start; +static uether_fn_t udav_tick; +static uether_fn_t udav_setmulti; +static uether_fn_t udav_setpromisc; + +static int udav_csr_read(struct udav_softc *, uint16_t, void *, int); +static int udav_csr_write(struct udav_softc *, uint16_t, void *, int); +static uint8_t udav_csr_read1(struct udav_softc *, uint16_t); +static int udav_csr_write1(struct udav_softc *, uint16_t, uint8_t); +static void udav_reset(struct udav_softc *); +static int udav_ifmedia_upd(struct ifnet *); +static void udav_ifmedia_status(struct ifnet *, struct ifmediareq *); + +static miibus_readreg_t udav_miibus_readreg; +static miibus_writereg_t udav_miibus_writereg; +static miibus_statchg_t udav_miibus_statchg; + +static const struct usb_config udav_config[UDAV_N_TRANSFER] = { + + [UDAV_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = (MCLBYTES + 2), + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = udav_bulk_write_callback, + .timeout = 10000, /* 10 seconds */ + }, + + [UDAV_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = (MCLBYTES + 3), + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = udav_bulk_read_callback, + .timeout = 0, /* no timeout */ + }, + + [UDAV_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = udav_intr_callback, + }, +}; + +static device_method_t udav_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, udav_probe), + DEVMETHOD(device_attach, udav_attach), + DEVMETHOD(device_detach, udav_detach), + + /* MII interface */ + DEVMETHOD(miibus_readreg, udav_miibus_readreg), + DEVMETHOD(miibus_writereg, udav_miibus_writereg), + DEVMETHOD(miibus_statchg, udav_miibus_statchg), + + DEVMETHOD_END +}; + +static driver_t udav_driver = { + .name = "udav", + .methods = udav_methods, + .size = sizeof(struct udav_softc), +}; + +static devclass_t udav_devclass; + +DRIVER_MODULE(udav, uhub, udav_driver, udav_devclass, NULL, 0); +DRIVER_MODULE(miibus, udav, miibus_driver, miibus_devclass, 0, 0); +MODULE_DEPEND(udav, uether, 1, 1, 1); +MODULE_DEPEND(udav, usb, 1, 1, 1); +MODULE_DEPEND(udav, ether, 1, 1, 1); +MODULE_DEPEND(udav, miibus, 1, 1, 1); +MODULE_VERSION(udav, 1); + +static const struct usb_ether_methods udav_ue_methods = { + .ue_attach_post = udav_attach_post, + .ue_start = udav_start, + .ue_init = udav_init, + .ue_stop = udav_stop, + .ue_tick = udav_tick, + .ue_setmulti = udav_setmulti, + .ue_setpromisc = udav_setpromisc, + .ue_mii_upd = udav_ifmedia_upd, + .ue_mii_sts = udav_ifmedia_status, +}; + +#ifdef USB_DEBUG +static int udav_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, udav, CTLFLAG_RW, 0, "USB udav"); +SYSCTL_INT(_hw_usb_udav, OID_AUTO, debug, CTLFLAG_RW, &udav_debug, 0, + "Debug level"); +#endif + +#define UDAV_SETBIT(sc, reg, x) \ + udav_csr_write1(sc, reg, udav_csr_read1(sc, reg) | (x)) + +#define UDAV_CLRBIT(sc, reg, x) \ + udav_csr_write1(sc, reg, udav_csr_read1(sc, reg) & ~(x)) + +static const STRUCT_USB_HOST_ID udav_devs[] = { + /* ShanTou DM9601 USB NIC */ + {USB_VPI(USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_DM9601, 0)}, + /* ShanTou ST268 USB NIC */ + {USB_VPI(USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_ST268, 0)}, + /* Corega USB-TXC */ + {USB_VPI(USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB_TXC, 0)}, + /* ShanTou AMD8515 USB NIC */ + {USB_VPI(USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_ADM8515, 0)}, + /* Kontron AG USB Ethernet */ + {USB_VPI(USB_VENDOR_KONTRON, USB_PRODUCT_KONTRON_DM9601, 0)}, + {USB_VPI(USB_VENDOR_KONTRON, USB_PRODUCT_KONTRON_JP1082, 0)}, +}; + +static void +udav_attach_post(struct usb_ether *ue) +{ + struct udav_softc *sc = uether_getsc(ue); + + /* reset the adapter */ + udav_reset(sc); + + /* Get Ethernet Address */ + udav_csr_read(sc, UDAV_PAR, ue->ue_eaddr, ETHER_ADDR_LEN); +} + +static int +udav_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != UDAV_CONFIG_INDEX) + return (ENXIO); + if (uaa->info.bIfaceIndex != UDAV_IFACE_INDEX) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(udav_devs, sizeof(udav_devs), uaa)); +} + +static int +udav_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct udav_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + uint8_t iface_index; + int error; + + sc->sc_flags = USB_GET_DRIVER_INFO(uaa); + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + iface_index = UDAV_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, udav_config, UDAV_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB transfers failed\n"); + goto detach; + } + + ue->ue_sc = sc; + ue->ue_dev = dev; + ue->ue_udev = uaa->device; + ue->ue_mtx = &sc->sc_mtx; + ue->ue_methods = &udav_ue_methods; + + error = uether_ifattach(ue); + if (error) { + device_printf(dev, "could not attach interface\n"); + goto detach; + } + + return (0); /* success */ + +detach: + udav_detach(dev); + return (ENXIO); /* failure */ +} + +static int +udav_detach(device_t dev) +{ + struct udav_softc *sc = device_get_softc(dev); + struct usb_ether *ue = &sc->sc_ue; + + usbd_transfer_unsetup(sc->sc_xfer, UDAV_N_TRANSFER); + uether_ifdetach(ue); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +#if 0 +static int +udav_mem_read(struct udav_softc *sc, uint16_t offset, void *buf, + int len) +{ + struct usb_device_request req; + + len &= 0xff; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_MEM_READ; + USETW(req.wValue, 0x0000); + USETW(req.wIndex, offset); + USETW(req.wLength, len); + + return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); +} + +static int +udav_mem_write(struct udav_softc *sc, uint16_t offset, void *buf, + int len) +{ + struct usb_device_request req; + + len &= 0xff; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_MEM_WRITE; + USETW(req.wValue, 0x0000); + USETW(req.wIndex, offset); + USETW(req.wLength, len); + + return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); +} + +static int +udav_mem_write1(struct udav_softc *sc, uint16_t offset, + uint8_t ch) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_MEM_WRITE1; + USETW(req.wValue, ch); + USETW(req.wIndex, offset); + USETW(req.wLength, 0x0000); + + return (uether_do_request(&sc->sc_ue, &req, NULL, 1000)); +} +#endif + +static int +udav_csr_read(struct udav_softc *sc, uint16_t offset, void *buf, int len) +{ + struct usb_device_request req; + + len &= 0xff; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_REG_READ; + USETW(req.wValue, 0x0000); + USETW(req.wIndex, offset); + USETW(req.wLength, len); + + return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); +} + +static int +udav_csr_write(struct udav_softc *sc, uint16_t offset, void *buf, int len) +{ + struct usb_device_request req; + + offset &= 0xff; + len &= 0xff; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_REG_WRITE; + USETW(req.wValue, 0x0000); + USETW(req.wIndex, offset); + USETW(req.wLength, len); + + return (uether_do_request(&sc->sc_ue, &req, buf, 1000)); +} + +static uint8_t +udav_csr_read1(struct udav_softc *sc, uint16_t offset) +{ + uint8_t val; + + udav_csr_read(sc, offset, &val, 1); + return (val); +} + +static int +udav_csr_write1(struct udav_softc *sc, uint16_t offset, + uint8_t ch) +{ + struct usb_device_request req; + + offset &= 0xff; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_REG_WRITE1; + USETW(req.wValue, ch); + USETW(req.wIndex, offset); + USETW(req.wLength, 0x0000); + + return (uether_do_request(&sc->sc_ue, &req, NULL, 1000)); +} + +static void +udav_init(struct usb_ether *ue) +{ + struct udav_softc *sc = ue->ue_sc; + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + + UDAV_LOCK_ASSERT(sc, MA_OWNED); + + /* + * Cancel pending I/O + */ + udav_stop(ue); + + /* set MAC address */ + udav_csr_write(sc, UDAV_PAR, IF_LLADDR(ifp), ETHER_ADDR_LEN); + + /* initialize network control register */ + + /* disable loopback */ + UDAV_CLRBIT(sc, UDAV_NCR, UDAV_NCR_LBK0 | UDAV_NCR_LBK1); + + /* Initialize RX control register */ + UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_DIS_LONG | UDAV_RCR_DIS_CRC); + + /* load multicast filter and update promiscious mode bit */ + udav_setpromisc(ue); + + /* enable RX */ + UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_RXEN); + + /* clear POWER_DOWN state of internal PHY */ + UDAV_SETBIT(sc, UDAV_GPCR, UDAV_GPCR_GEP_CNTL0); + UDAV_CLRBIT(sc, UDAV_GPR, UDAV_GPR_GEPIO0); + + usbd_xfer_set_stall(sc->sc_xfer[UDAV_BULK_DT_WR]); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + udav_start(ue); +} + +static void +udav_reset(struct udav_softc *sc) +{ + int i; + + /* Select PHY */ +#if 1 + /* + * XXX: force select internal phy. + * external phy routines are not tested. + */ + UDAV_CLRBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); +#else + if (sc->sc_flags & UDAV_EXT_PHY) + UDAV_SETBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); + else + UDAV_CLRBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); +#endif + + UDAV_SETBIT(sc, UDAV_NCR, UDAV_NCR_RST); + + for (i = 0; i < UDAV_TX_TIMEOUT; i++) { + if (!(udav_csr_read1(sc, UDAV_NCR) & UDAV_NCR_RST)) + break; + if (uether_pause(&sc->sc_ue, hz / 100)) + break; + } + + uether_pause(&sc->sc_ue, hz / 100); +} + +#define UDAV_BITS 6 +static void +udav_setmulti(struct usb_ether *ue) +{ + struct udav_softc *sc = ue->ue_sc; + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct ifmultiaddr *ifma; + uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + int h = 0; + + UDAV_LOCK_ASSERT(sc, MA_OWNED); + + if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { + UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_ALL|UDAV_RCR_PRMSC); + return; + } + + /* first, zot all the existing hash bits */ + memset(hashtbl, 0x00, sizeof(hashtbl)); + hashtbl[7] |= 0x80; /* broadcast address */ + udav_csr_write(sc, UDAV_MAR, hashtbl, sizeof(hashtbl)); + + /* now program new ones */ + if_maddr_rlock(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) + { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + h = ether_crc32_be(LLADDR((struct sockaddr_dl *) + ifma->ifma_addr), ETHER_ADDR_LEN) >> 26; + hashtbl[h / 8] |= 1 << (h % 8); + } + if_maddr_runlock(ifp); + + /* disable all multicast */ + UDAV_CLRBIT(sc, UDAV_RCR, UDAV_RCR_ALL); + + /* write hash value to the register */ + udav_csr_write(sc, UDAV_MAR, hashtbl, sizeof(hashtbl)); +} + +static void +udav_setpromisc(struct usb_ether *ue) +{ + struct udav_softc *sc = ue->ue_sc; + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + uint8_t rxmode; + + rxmode = udav_csr_read1(sc, UDAV_RCR); + rxmode &= ~(UDAV_RCR_ALL | UDAV_RCR_PRMSC); + + if (ifp->if_flags & IFF_PROMISC) + rxmode |= UDAV_RCR_ALL | UDAV_RCR_PRMSC; + else if (ifp->if_flags & IFF_ALLMULTI) + rxmode |= UDAV_RCR_ALL; + + /* write new mode bits */ + udav_csr_write1(sc, UDAV_RCR, rxmode); +} + +static void +udav_start(struct usb_ether *ue) +{ + struct udav_softc *sc = ue->ue_sc; + + /* + * start the USB transfers, if not already started: + */ + usbd_transfer_start(sc->sc_xfer[UDAV_INTR_DT_RD]); + usbd_transfer_start(sc->sc_xfer[UDAV_BULK_DT_RD]); + usbd_transfer_start(sc->sc_xfer[UDAV_BULK_DT_WR]); +} + +static void +udav_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct udav_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct usb_page_cache *pc; + struct mbuf *m; + int extra_len; + int temp_len; + uint8_t buf[2]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + ifp->if_opackets++; + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + if ((sc->sc_flags & UDAV_FLAG_LINK) == 0) { + /* + * don't send anything if there is no link ! + */ + return; + } + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + + if (m == NULL) + return; + if (m->m_pkthdr.len > MCLBYTES) + m->m_pkthdr.len = MCLBYTES; + if (m->m_pkthdr.len < UDAV_MIN_FRAME_LEN) { + extra_len = UDAV_MIN_FRAME_LEN - m->m_pkthdr.len; + } else { + extra_len = 0; + } + + temp_len = (m->m_pkthdr.len + extra_len); + + /* + * the frame length is specified in the first 2 bytes of the + * buffer + */ + buf[0] = (uint8_t)(temp_len); + buf[1] = (uint8_t)(temp_len >> 8); + + temp_len += 2; + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, buf, 2); + usbd_m_copy_in(pc, 2, m, 0, m->m_pkthdr.len); + + if (extra_len) + usbd_frame_zero(pc, temp_len - extra_len, extra_len); + /* + * if there's a BPF listener, bounce a copy + * of this frame to him: + */ + BPF_MTAP(ifp, m); + + m_freem(m); + + usbd_xfer_set_frame_len(xfer, 0, temp_len); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usbd_errstr(error)); + + ifp->if_oerrors++; + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +udav_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct udav_softc *sc = usbd_xfer_softc(xfer); + struct usb_ether *ue = &sc->sc_ue; + struct ifnet *ifp = uether_getifp(ue); + struct usb_page_cache *pc; + struct udav_rxpkt stat; + int len; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen < sizeof(stat) + ETHER_CRC_LEN) { + ifp->if_ierrors++; + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &stat, sizeof(stat)); + actlen -= sizeof(stat); + len = min(actlen, le16toh(stat.pktlen)); + len -= ETHER_CRC_LEN; + + if (stat.rxstat & UDAV_RSR_LCS) { + ifp->if_collisions++; + goto tr_setup; + } + if (stat.rxstat & UDAV_RSR_ERR) { + ifp->if_ierrors++; + goto tr_setup; + } + uether_rxbuf(ue, pc, sizeof(stat), len); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + uether_rxflush(ue); + return; + + default: /* Error */ + DPRINTF("bulk read error, %s\n", + usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +udav_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +udav_stop(struct usb_ether *ue) +{ + struct udav_softc *sc = ue->ue_sc; + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + + UDAV_LOCK_ASSERT(sc, MA_OWNED); + + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + sc->sc_flags &= ~UDAV_FLAG_LINK; + + /* + * stop all the transfers, if not already stopped: + */ + usbd_transfer_stop(sc->sc_xfer[UDAV_BULK_DT_WR]); + usbd_transfer_stop(sc->sc_xfer[UDAV_BULK_DT_RD]); + usbd_transfer_stop(sc->sc_xfer[UDAV_INTR_DT_RD]); + + udav_reset(sc); +} + +static int +udav_ifmedia_upd(struct ifnet *ifp) +{ + struct udav_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + struct mii_softc *miisc; + + UDAV_LOCK_ASSERT(sc, MA_OWNED); + + sc->sc_flags &= ~UDAV_FLAG_LINK; + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) + PHY_RESET(miisc); + mii_mediachg(mii); + return (0); +} + +static void +udav_ifmedia_status(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct udav_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + + UDAV_LOCK(sc); + mii_pollstat(mii); + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; + UDAV_UNLOCK(sc); +} + +static void +udav_tick(struct usb_ether *ue) +{ + struct udav_softc *sc = ue->ue_sc; + struct mii_data *mii = GET_MII(sc); + + UDAV_LOCK_ASSERT(sc, MA_OWNED); + + mii_tick(mii); + if ((sc->sc_flags & UDAV_FLAG_LINK) == 0 + && mii->mii_media_status & IFM_ACTIVE && + IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { + sc->sc_flags |= UDAV_FLAG_LINK; + udav_start(ue); + } +} + +static int +udav_miibus_readreg(device_t dev, int phy, int reg) +{ + struct udav_softc *sc = device_get_softc(dev); + uint16_t data16; + uint8_t val[2]; + int locked; + + /* XXX: one PHY only for the internal PHY */ + if (phy != 0) + return (0); + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + UDAV_LOCK(sc); + + /* select internal PHY and set PHY register address */ + udav_csr_write1(sc, UDAV_EPAR, + UDAV_EPAR_PHY_ADR0 | (reg & UDAV_EPAR_EROA_MASK)); + + /* select PHY operation and start read command */ + udav_csr_write1(sc, UDAV_EPCR, UDAV_EPCR_EPOS | UDAV_EPCR_ERPRR); + + /* XXX: should we wait? */ + + /* end read command */ + UDAV_CLRBIT(sc, UDAV_EPCR, UDAV_EPCR_ERPRR); + + /* retrieve the result from data registers */ + udav_csr_read(sc, UDAV_EPDRL, val, 2); + + data16 = (val[0] | (val[1] << 8)); + + DPRINTFN(11, "phy=%d reg=0x%04x => 0x%04x\n", + phy, reg, data16); + + if (!locked) + UDAV_UNLOCK(sc); + return (data16); +} + +static int +udav_miibus_writereg(device_t dev, int phy, int reg, int data) +{ + struct udav_softc *sc = device_get_softc(dev); + uint8_t val[2]; + int locked; + + /* XXX: one PHY only for the internal PHY */ + if (phy != 0) + return (0); + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + UDAV_LOCK(sc); + + /* select internal PHY and set PHY register address */ + udav_csr_write1(sc, UDAV_EPAR, + UDAV_EPAR_PHY_ADR0 | (reg & UDAV_EPAR_EROA_MASK)); + + /* put the value to the data registers */ + val[0] = (data & 0xff); + val[1] = (data >> 8) & 0xff; + udav_csr_write(sc, UDAV_EPDRL, val, 2); + + /* select PHY operation and start write command */ + udav_csr_write1(sc, UDAV_EPCR, UDAV_EPCR_EPOS | UDAV_EPCR_ERPRW); + + /* XXX: should we wait? */ + + /* end write command */ + UDAV_CLRBIT(sc, UDAV_EPCR, UDAV_EPCR_ERPRW); + + if (!locked) + UDAV_UNLOCK(sc); + return (0); +} + +static void +udav_miibus_statchg(device_t dev) +{ + /* nothing to do */ +} diff --git a/sys/bus/u4b/net/if_udavreg.h b/sys/bus/u4b/net/if_udavreg.h new file mode 100644 index 0000000000..82715e8bf3 --- /dev/null +++ b/sys/bus/u4b/net/if_udavreg.h @@ -0,0 +1,166 @@ +/* $NetBSD: if_udavreg.h,v 1.2 2003/09/04 15:17:39 tsutsui Exp $ */ +/* $nabe: if_udavreg.h,v 1.2 2003/08/21 16:26:40 nabe Exp $ */ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2003 + * Shingo WATANABE . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#define UDAV_IFACE_INDEX 0 +#define UDAV_CONFIG_INDEX 0 /* config number 1 */ + +#define UDAV_TX_TIMEOUT 1000 +#define UDAV_TIMEOUT 10000 + +#define UDAV_TX_TIMEOUT 1000 +#define UDAV_TIMEOUT 10000 + +/* Packet length */ +#define UDAV_MIN_FRAME_LEN 60 + +/* Request */ +#define UDAV_REQ_REG_READ 0x00 /* Read from register(s) */ +#define UDAV_REQ_REG_WRITE 0x01 /* Write to register(s) */ +#define UDAV_REQ_REG_WRITE1 0x03 /* Write to a register */ + +#define UDAV_REQ_MEM_READ 0x02 /* Read from memory */ +#define UDAV_REQ_MEM_WRITE 0x05 /* Write to memory */ +#define UDAV_REQ_MEM_WRITE1 0x07 /* Write a byte to memory */ + +/* Registers */ +#define UDAV_NCR 0x00 /* Network Control Register */ +#define UDAV_NCR_EXT_PHY (1<<7) /* Select External PHY */ +#define UDAV_NCR_WAKEEN (1<<6) /* Wakeup Event Enable */ +#define UDAV_NCR_FCOL (1<<4) /* Force Collision Mode */ +#define UDAV_NCR_FDX (1<<3) /* Full-Duplex Mode (RO on Int. PHY) */ +#define UDAV_NCR_LBK1 (1<<2) /* Lookback Mode */ +#define UDAV_NCR_LBK0 (1<<1) /* Lookback Mode */ +#define UDAV_NCR_RST (1<<0) /* Software reset */ + +#define UDAV_RCR 0x05 /* RX Control Register */ +#define UDAV_RCR_WTDIS (1<<6) /* Watchdog Timer Disable */ +#define UDAV_RCR_DIS_LONG (1<<5) /* Discard Long Packet(over 1522Byte) */ +#define UDAV_RCR_DIS_CRC (1<<4) /* Discard CRC Error Packet */ +#define UDAV_RCR_ALL (1<<3) /* Pass All Multicast */ +#define UDAV_RCR_RUNT (1<<2) /* Pass Runt Packet */ +#define UDAV_RCR_PRMSC (1<<1) /* Promiscuous Mode */ +#define UDAV_RCR_RXEN (1<<0) /* RX Enable */ + +#define UDAV_RSR 0x06 /* RX Status Register */ +#define UDAV_RSR_RF (1<<7) /* Runt Frame */ +#define UDAV_RSR_MF (1<<6) /* Multicast Frame */ +#define UDAV_RSR_LCS (1<<5) /* Late Collision Seen */ +#define UDAV_RSR_RWTO (1<<4) /* Receive Watchdog Time-Out */ +#define UDAV_RSR_PLE (1<<3) /* Physical Layer Error */ +#define UDAV_RSR_AE (1<<2) /* Alignment Error */ +#define UDAV_RSR_CE (1<<1) /* CRC Error */ +#define UDAV_RSR_FOE (1<<0) /* FIFO Overflow Error */ +#define UDAV_RSR_ERR (UDAV_RSR_RF | UDAV_RSR_LCS | \ + UDAV_RSR_RWTO | UDAV_RSR_PLE | \ + UDAV_RSR_AE | UDAV_RSR_CE | UDAV_RSR_FOE) + +#define UDAV_EPCR 0x0b /* EEPROM & PHY Control Register */ +#define UDAV_EPCR_REEP (1<<5) /* Reload EEPROM */ +#define UDAV_EPCR_WEP (1<<4) /* Write EEPROM enable */ +#define UDAV_EPCR_EPOS (1<<3) /* EEPROM or PHY Operation Select */ +#define UDAV_EPCR_ERPRR (1<<2) /* EEPROM/PHY Register Read Command */ +#define UDAV_EPCR_ERPRW (1<<1) /* EEPROM/PHY Register Write Command */ +#define UDAV_EPCR_ERRE (1<<0) /* EEPROM/PHY Access Status */ + +#define UDAV_EPAR 0x0c /* EEPROM & PHY Control Register */ +#define UDAV_EPAR_PHY_ADR1 (1<<7) /* PHY Address bit 1 */ +#define UDAV_EPAR_PHY_ADR0 (1<<6) /* PHY Address bit 0 */ +#define UDAV_EPAR_EROA (1<<0) /* EEPROM Word/PHY Register Address */ +#define UDAV_EPAR_EROA_MASK (0x1f) /* [5:0] */ + +#define UDAV_EPDRL 0x0d /* EEPROM & PHY Data Register */ +#define UDAV_EPDRH 0x0e /* EEPROM & PHY Data Register */ + +#define UDAV_PAR0 0x10 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR1 0x11 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR2 0x12 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR3 0x13 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR4 0x14 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR5 0x15 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR UDAV_PAR0 + +#define UDAV_MAR0 0x16 /* Multicast Register */ +#define UDAV_MAR1 0x17 /* Multicast Register */ +#define UDAV_MAR2 0x18 /* Multicast Register */ +#define UDAV_MAR3 0x19 /* Multicast Register */ +#define UDAV_MAR4 0x1a /* Multicast Register */ +#define UDAV_MAR5 0x1b /* Multicast Register */ +#define UDAV_MAR6 0x1c /* Multicast Register */ +#define UDAV_MAR7 0x1d /* Multicast Register */ +#define UDAV_MAR UDAV_MAR0 + +#define UDAV_GPCR 0x1e /* General purpose control register */ +#define UDAV_GPCR_GEP_CNTL6 (1<<6) /* General purpose control 6 */ +#define UDAV_GPCR_GEP_CNTL5 (1<<5) /* General purpose control 5 */ +#define UDAV_GPCR_GEP_CNTL4 (1<<4) /* General purpose control 4 */ +#define UDAV_GPCR_GEP_CNTL3 (1<<3) /* General purpose control 3 */ +#define UDAV_GPCR_GEP_CNTL2 (1<<2) /* General purpose control 2 */ +#define UDAV_GPCR_GEP_CNTL1 (1<<1) /* General purpose control 1 */ +#define UDAV_GPCR_GEP_CNTL0 (1<<0) /* General purpose control 0 */ + +#define UDAV_GPR 0x1f /* General purpose register */ +#define UDAV_GPR_GEPIO6 (1<<6) /* General purpose 6 */ +#define UDAV_GPR_GEPIO5 (1<<5) /* General purpose 5 */ +#define UDAV_GPR_GEPIO4 (1<<4) /* General purpose 4 */ +#define UDAV_GPR_GEPIO3 (1<<3) /* General purpose 3 */ +#define UDAV_GPR_GEPIO2 (1<<2) /* General purpose 2 */ +#define UDAV_GPR_GEPIO1 (1<<1) /* General purpose 1 */ +#define UDAV_GPR_GEPIO0 (1<<0) /* General purpose 0 */ + +#define GET_MII(sc) uether_getmii(&(sc)->sc_ue) + +struct udav_rxpkt { + uint8_t rxstat; + uint16_t pktlen; +} __packed; + +enum { + UDAV_BULK_DT_WR, + UDAV_BULK_DT_RD, + UDAV_INTR_DT_RD, + UDAV_N_TRANSFER, +}; + +struct udav_softc { + struct usb_ether sc_ue; + struct mtx sc_mtx; + struct usb_xfer *sc_xfer[UDAV_N_TRANSFER]; + + int sc_flags; +#define UDAV_FLAG_LINK 0x0001 +#define UDAV_FLAG_EXT_PHY 0x0040 +}; + +#define UDAV_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define UDAV_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define UDAV_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t) diff --git a/sys/bus/u4b/net/if_usie.c b/sys/bus/u4b/net/if_usie.c new file mode 100644 index 0000000000..ed9054fc1f --- /dev/null +++ b/sys/bus/u4b/net/if_usie.c @@ -0,0 +1,1586 @@ +/*- + * Copyright (c) 2011 Anybots Inc + * written by Akinori Furukoshi + * - ucom part is based on u3g.c + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR usie_debug +#include +#include +#include + +#include + +#include + +#ifdef USB_DEBUG +static int usie_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, usie, CTLFLAG_RW, 0, "sierra USB modem"); +SYSCTL_INT(_hw_usb_usie, OID_AUTO, debug, CTLFLAG_RW, &usie_debug, 0, + "usie debug level"); +#endif + +/* Sierra Wireless Direct IP modems */ +static const STRUCT_USB_HOST_ID usie_devs[] = { +#define USIE_DEV(v, d) { \ + USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##d) } + USIE_DEV(SIERRA, MC8700), + USIE_DEV(SIERRA, TRUINSTALL), + USIE_DEV(AIRPRIME, USB308), +#undef USIE_DEV +}; + +static device_probe_t usie_probe; +static device_attach_t usie_attach; +static device_detach_t usie_detach; + +static void usie_uc_update_line_state(struct ucom_softc *, uint8_t); +static void usie_uc_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); +static void usie_uc_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void usie_uc_cfg_set_rts(struct ucom_softc *, uint8_t); +static void usie_uc_cfg_open(struct ucom_softc *); +static void usie_uc_cfg_close(struct ucom_softc *); +static void usie_uc_start_read(struct ucom_softc *); +static void usie_uc_stop_read(struct ucom_softc *); +static void usie_uc_start_write(struct ucom_softc *); +static void usie_uc_stop_write(struct ucom_softc *); + +static usb_callback_t usie_uc_tx_callback; +static usb_callback_t usie_uc_rx_callback; +static usb_callback_t usie_uc_status_callback; +static usb_callback_t usie_if_tx_callback; +static usb_callback_t usie_if_rx_callback; +static usb_callback_t usie_if_status_callback; + +static void usie_if_sync_to(void *); +static void usie_if_sync_cb(void *, int); +static void usie_if_status_cb(void *, int); + +static void usie_if_start(struct ifnet *); +static int usie_if_output(struct ifnet *, struct mbuf *, struct sockaddr *, struct route *); +static void usie_if_init(void *); +static void usie_if_stop(struct usie_softc *); +static int usie_if_ioctl(struct ifnet *, u_long, caddr_t); + +static int usie_do_request(struct usie_softc *, struct usb_device_request *, void *); +static int usie_if_cmd(struct usie_softc *, uint8_t); +static void usie_cns_req(struct usie_softc *, uint32_t, uint16_t); +static void usie_cns_rsp(struct usie_softc *, struct usie_cns *); +static void usie_hip_rsp(struct usie_softc *, uint8_t *, uint32_t); +static int usie_driver_loaded(struct module *, int, void *); + +static const struct usb_config usie_uc_config[USIE_UC_N_XFER] = { + [USIE_UC_STATUS] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = 0, /* use wMaxPacketSize */ + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &usie_uc_status_callback, + }, + [USIE_UC_RX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = USIE_BUFSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1,}, + .callback = &usie_uc_rx_callback, + }, + [USIE_UC_TX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = USIE_BUFSIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &usie_uc_tx_callback, + } +}; + +static const struct usb_config usie_if_config[USIE_IF_N_XFER] = { + [USIE_IF_STATUS] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = 0, /* use wMaxPacketSize */ + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &usie_if_status_callback, + }, + [USIE_IF_RX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = USIE_BUFSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &usie_if_rx_callback, + }, + [USIE_IF_TX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = MAX(USIE_BUFSIZE, MCLBYTES), + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &usie_if_tx_callback, + } +}; + +static device_method_t usie_methods[] = { + DEVMETHOD(device_probe, usie_probe), + DEVMETHOD(device_attach, usie_attach), + DEVMETHOD(device_detach, usie_detach), + {0, 0} +}; + +static driver_t usie_driver = { + .name = "usie", + .methods = usie_methods, + .size = sizeof(struct usie_softc), +}; + +static devclass_t usie_devclass; +static eventhandler_tag usie_etag; + +DRIVER_MODULE(usie, uhub, usie_driver, usie_devclass, usie_driver_loaded, 0); +MODULE_DEPEND(usie, ucom, 1, 1, 1); +MODULE_DEPEND(usie, usb, 1, 1, 1); +MODULE_VERSION(usie, 1); + +static const struct ucom_callback usie_uc_callback = { + .ucom_cfg_get_status = &usie_uc_cfg_get_status, + .ucom_cfg_set_dtr = &usie_uc_cfg_set_dtr, + .ucom_cfg_set_rts = &usie_uc_cfg_set_rts, + .ucom_cfg_open = &usie_uc_cfg_open, + .ucom_cfg_close = &usie_uc_cfg_close, + .ucom_start_read = &usie_uc_start_read, + .ucom_stop_read = &usie_uc_stop_read, + .ucom_start_write = &usie_uc_start_write, + .ucom_stop_write = &usie_uc_stop_write, +}; + +static void +usie_autoinst(void *arg, struct usb_device *udev, + struct usb_attach_arg *uaa) +{ + struct usb_interface *iface; + struct usb_interface_descriptor *id; + struct usb_device_request req; + int err; + + if (uaa->dev_state != UAA_DEV_READY) + return; + + iface = usbd_get_iface(udev, 0); + if (iface == NULL) + return; + + id = iface->idesc; + if (id == NULL || id->bInterfaceClass != UICLASS_MASS) + return; + + if (usbd_lookup_id_by_uaa(usie_devs, sizeof(usie_devs), uaa) != 0) + return; /* no device match */ + + if (bootverbose) { + DPRINTF("Ejecting %s %s\n", + usb_get_manufacturer(udev), + usb_get_product(udev)); + } + req.bmRequestType = UT_VENDOR; + req.bRequest = UR_SET_INTERFACE; + USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP); + USETW(req.wIndex, UHF_PORT_CONNECTION); + USETW(req.wLength, 0); + + /* at this moment there is no mutex */ + err = usbd_do_request_flags(udev, NULL, &req, + NULL, 0, NULL, 250 /* ms */ ); + + /* success, mark the udev as disappearing */ + if (err == 0) + uaa->dev_state = UAA_DEV_EJECTING; +} + +static int +usie_probe(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != USIE_CNFG_INDEX) + return (ENXIO); + if (uaa->info.bIfaceIndex != USIE_IFACE_INDEX) + return (ENXIO); + if (uaa->info.bInterfaceClass != UICLASS_VENDOR) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(usie_devs, sizeof(usie_devs), uaa)); +} + +static int +usie_attach(device_t self) +{ + struct usie_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + struct ifnet *ifp; + struct usb_interface *iface; + struct usb_interface_descriptor *id; + struct usb_device_request req; + int err; + uint16_t fwattr; + uint8_t iface_index; + uint8_t ifidx; + uint8_t start; + + device_set_usb_desc(self); + sc->sc_udev = uaa->device; + sc->sc_dev = self; + + mtx_init(&sc->sc_mtx, "usie", MTX_NETWORK_LOCK, MTX_DEF); + + TASK_INIT(&sc->sc_if_status_task, 0, usie_if_status_cb, sc); + TASK_INIT(&sc->sc_if_sync_task, 0, usie_if_sync_cb, sc); + + usb_callout_init_mtx(&sc->sc_if_sync_ch, &sc->sc_mtx, 0); + + mtx_lock(&sc->sc_mtx); + + /* set power mode to D0 */ + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = USIE_POWER; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + if (usie_do_request(sc, &req, NULL)) { + mtx_unlock(&sc->sc_mtx); + goto detach; + } + /* read fw attr */ + fwattr = 0; + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = USIE_FW_ATTR; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(fwattr)); + if (usie_do_request(sc, &req, &fwattr)) { + mtx_unlock(&sc->sc_mtx); + goto detach; + } + mtx_unlock(&sc->sc_mtx); + + /* check DHCP supports */ + DPRINTF("fwattr=%x\n", fwattr); + if (!(fwattr & USIE_FW_DHCP)) { + device_printf(self, "DHCP is not supported. A firmware upgrade might be needed.\n"); + } + + /* find available interfaces */ + sc->sc_nucom = 0; + for (ifidx = 0; ifidx < USIE_IFACE_MAX; ifidx++) { + iface = usbd_get_iface(uaa->device, ifidx); + if (iface == NULL) + break; + + id = usbd_get_interface_descriptor(iface); + if ((id == NULL) || (id->bInterfaceClass != UICLASS_VENDOR)) + continue; + + /* setup Direct IP transfer */ + if (id->bInterfaceNumber >= 7 && id->bNumEndpoints == 3) { + sc->sc_if_ifnum = id->bInterfaceNumber; + iface_index = ifidx; + + DPRINTF("ifnum=%d, ifidx=%d\n", + sc->sc_if_ifnum, ifidx); + + err = usbd_transfer_setup(uaa->device, + &iface_index, sc->sc_if_xfer, usie_if_config, + USIE_IF_N_XFER, sc, &sc->sc_mtx); + + if (err == 0) + continue; + + device_printf(self, + "could not allocate USB transfers on " + "iface_index=%d, err=%s\n", + iface_index, usbd_errstr(err)); + goto detach; + } + + /* setup ucom */ + if (sc->sc_nucom >= USIE_UCOM_MAX) + continue; + + usbd_set_parent_iface(uaa->device, ifidx, + uaa->info.bIfaceIndex); + + DPRINTF("NumEndpoints=%d bInterfaceNumber=%d\n", + id->bNumEndpoints, id->bInterfaceNumber); + + if (id->bNumEndpoints == 2) { + sc->sc_uc_xfer[sc->sc_nucom][0] = NULL; + start = 1; + } else + start = 0; + + err = usbd_transfer_setup(uaa->device, &ifidx, + sc->sc_uc_xfer[sc->sc_nucom] + start, + usie_uc_config + start, USIE_UC_N_XFER - start, + &sc->sc_ucom[sc->sc_nucom], &sc->sc_mtx); + + if (err != 0) { + DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(err)); + continue; + } + + mtx_lock(&sc->sc_mtx); + for (; start < USIE_UC_N_XFER; start++) + usbd_xfer_set_stall(sc->sc_uc_xfer[sc->sc_nucom][start]); + mtx_unlock(&sc->sc_mtx); + + sc->sc_uc_ifnum[sc->sc_nucom] = id->bInterfaceNumber; + + sc->sc_nucom++; /* found a port */ + } + + if (sc->sc_nucom == 0) { + device_printf(self, "no comports found\n"); + goto detach; + } + + err = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, + sc->sc_nucom, sc, &usie_uc_callback, &sc->sc_mtx); + + if (err != 0) { + DPRINTF("ucom_attach failed\n"); + goto detach; + } + DPRINTF("Found %d interfaces.\n", sc->sc_nucom); + + /* setup ifnet (Direct IP) */ + sc->sc_ifp = ifp = if_alloc(IFT_OTHER); + + if (ifp == NULL) { + device_printf(self, "Could not allocate a network interface\n"); + goto detach; + } + if_initname(ifp, "usie", device_get_unit(self)); + + ifp->if_softc = sc; + ifp->if_mtu = USIE_MTU_MAX; + ifp->if_flags |= IFF_NOARP; + ifp->if_init = usie_if_init; + ifp->if_ioctl = usie_if_ioctl; + ifp->if_start = usie_if_start; + ifp->if_output = usie_if_output; + IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); + ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; + IFQ_SET_READY(&ifp->if_snd); + + if_attach(ifp); + bpfattach(ifp, DLT_RAW, 0); + + if (fwattr & USIE_PM_AUTO) { + usbd_set_power_mode(uaa->device, USB_POWER_MODE_SAVE); + DPRINTF("enabling automatic suspend and resume\n"); + } else { + usbd_set_power_mode(uaa->device, USB_POWER_MODE_ON); + DPRINTF("USB power is always ON\n"); + } + + DPRINTF("device attached\n"); + return (0); + +detach: + usie_detach(self); + return (ENOMEM); +} + +static int +usie_detach(device_t self) +{ + struct usie_softc *sc = device_get_softc(self); + uint8_t x; + + /* detach ifnet */ + if (sc->sc_ifp != NULL) { + usie_if_stop(sc); + usbd_transfer_unsetup(sc->sc_if_xfer, USIE_IF_N_XFER); + bpfdetach(sc->sc_ifp); + if_detach(sc->sc_ifp); + if_free(sc->sc_ifp); + sc->sc_ifp = NULL; + } + /* detach ucom */ + if (sc->sc_nucom > 0) + ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); + + /* stop all USB transfers */ + usbd_transfer_unsetup(sc->sc_if_xfer, USIE_IF_N_XFER); + + for (x = 0; x != USIE_UCOM_MAX; x++) + usbd_transfer_unsetup(sc->sc_uc_xfer[x], USIE_UC_N_XFER); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +usie_uc_update_line_state(struct ucom_softc *ucom, uint8_t ls) +{ + struct usie_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + if (sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS] == NULL) + return; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = USIE_LINK_STATE; + USETW(req.wValue, ls); + USETW(req.wIndex, sc->sc_uc_ifnum[ucom->sc_subunit]); + USETW(req.wLength, 0); + + DPRINTF("sc_uc_ifnum=%d\n", sc->sc_uc_ifnum[ucom->sc_subunit]); + + usie_do_request(sc, &req, NULL); +} + +static void +usie_uc_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct usie_softc *sc = ucom->sc_parent; + + *msr = sc->sc_msr; + *lsr = sc->sc_lsr; +} + +static void +usie_uc_cfg_set_dtr(struct ucom_softc *ucom, uint8_t flag) +{ + uint8_t dtr; + + dtr = flag ? USIE_LS_DTR : 0; + usie_uc_update_line_state(ucom, dtr); +} + +static void +usie_uc_cfg_set_rts(struct ucom_softc *ucom, uint8_t flag) +{ + uint8_t rts; + + rts = flag ? USIE_LS_RTS : 0; + usie_uc_update_line_state(ucom, rts); +} + +static void +usie_uc_cfg_open(struct ucom_softc *ucom) +{ + struct usie_softc *sc = ucom->sc_parent; + + /* usbd_transfer_start() is NULL safe */ + + usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS]); +} + +static void +usie_uc_cfg_close(struct ucom_softc *ucom) +{ + struct usie_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS]); +} + +static void +usie_uc_start_read(struct ucom_softc *ucom) +{ + struct usie_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_RX]); +} + +static void +usie_uc_stop_read(struct ucom_softc *ucom) +{ + struct usie_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_RX]); +} + +static void +usie_uc_start_write(struct ucom_softc *ucom) +{ + struct usie_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_TX]); +} + +static void +usie_uc_stop_write(struct ucom_softc *ucom) +{ + struct usie_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_TX]); +} + +static void +usie_uc_rx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ucom_softc *ucom = usbd_xfer_softc(xfer); + struct usie_softc *sc = ucom->sc_parent; + struct usb_page_cache *pc; + uint32_t actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + + /* handle CnS response */ + if (ucom == sc->sc_ucom && actlen >= USIE_HIPCNS_MIN) { + + DPRINTF("transferred=%u\n", actlen); + + /* check if it is really CnS reply */ + usbd_copy_out(pc, 0, sc->sc_resp_temp, 1); + + if (sc->sc_resp_temp[0] == USIE_HIP_FRM_CHR) { + + /* verify actlen */ + if (actlen > USIE_BUFSIZE) + actlen = USIE_BUFSIZE; + + /* get complete message */ + usbd_copy_out(pc, 0, sc->sc_resp_temp, actlen); + usie_hip_rsp(sc, sc->sc_resp_temp, actlen); + + /* need to fall though */ + goto tr_setup; + } + /* else call ucom_put_data() */ + } + /* standard ucom transfer */ + ucom_put_data(ucom, pc, 0, actlen); + + /* fall though */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +usie_uc_tx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ucom_softc *ucom = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + + /* handle CnS request */ + struct mbuf *m = usbd_xfer_get_priv(xfer); + + if (m != NULL) { + usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); + usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len); + usbd_xfer_set_priv(xfer, NULL); + usbd_transfer_submit(xfer); + m_freem(m); + break; + } + /* standard ucom transfer */ + if (ucom_get_data(ucom, pc, 0, USIE_BUFSIZE, &actlen)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +usie_uc_status_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_page_cache *pc; + struct { + struct usb_device_request req; + uint16_t param; + } st; + uint32_t actlen; + uint16_t param; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(4, "info received, actlen=%u\n", actlen); + + if (actlen < sizeof(st)) { + DPRINTF("data too short actlen=%u\n", actlen); + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &st, sizeof(st)); + + if (st.req.bmRequestType == 0xa1 && st.req.bRequest == 0x20) { + struct ucom_softc *ucom = usbd_xfer_softc(xfer); + struct usie_softc *sc = ucom->sc_parent; + + param = le16toh(st.param); + DPRINTF("param=%x\n", param); + sc->sc_msr = sc->sc_lsr = 0; + sc->sc_msr |= (param & USIE_DCD) ? SER_DCD : 0; + sc->sc_msr |= (param & USIE_DSR) ? SER_DSR : 0; + sc->sc_msr |= (param & USIE_RI) ? SER_RI : 0; + sc->sc_msr |= (param & USIE_CTS) ? 0 : SER_CTS; + sc->sc_msr |= (param & USIE_RTS) ? SER_RTS : 0; + sc->sc_msr |= (param & USIE_DTR) ? SER_DTR : 0; + } + /* fall though */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTF("USB transfer error, %s\n", + usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +usie_if_rx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usie_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m0; + struct mbuf *m = NULL; + struct usie_desc *rxd; + uint32_t actlen; + uint16_t err; + uint16_t pkt; + uint16_t ipl; + uint16_t len; + uint16_t diff; + uint8_t pad; + uint8_t ipv; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(15, "rx done, actlen=%u\n", actlen); + + if (actlen < sizeof(struct usie_hip)) { + DPRINTF("data too short %u\n", actlen); + goto tr_setup; + } + m = sc->sc_rxm; + sc->sc_rxm = NULL; + + /* fall though */ + case USB_ST_SETUP: +tr_setup: + + if (sc->sc_rxm == NULL) { + sc->sc_rxm = m_getjcl(M_DONTWAIT, MT_DATA, M_PKTHDR, + MJUMPAGESIZE /* could be bigger than MCLBYTES */ ); + } + if (sc->sc_rxm == NULL) { + DPRINTF("could not allocate Rx mbuf\n"); + ifp->if_ierrors++; + usbd_xfer_set_stall(xfer); + usbd_xfer_set_frames(xfer, 0); + } else { + /* + * Directly loading a mbuf cluster into DMA to + * save some data copying. This works because + * there is only one cluster. + */ + usbd_xfer_set_frame_data(xfer, 0, + mtod(sc->sc_rxm, caddr_t), MIN(MJUMPAGESIZE, USIE_RXSZ_MAX)); + usbd_xfer_set_frames(xfer, 1); + } + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTF("USB transfer error, %s\n", usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + ifp->if_ierrors++; + goto tr_setup; + } + if (sc->sc_rxm != NULL) { + m_freem(sc->sc_rxm); + sc->sc_rxm = NULL; + } + break; + } + + if (m == NULL) + return; + + mtx_unlock(&sc->sc_mtx); + + m->m_pkthdr.len = m->m_len = actlen; + + err = pkt = 0; + + /* HW can aggregate multiple frames in a single USB xfer */ + for (;;) { + rxd = mtod(m, struct usie_desc *); + + len = be16toh(rxd->hip.len) & USIE_HIP_IP_LEN_MASK; + pad = (rxd->hip.id & USIE_HIP_PAD) ? 1 : 0; + ipl = (len - pad - ETHER_HDR_LEN); + if (ipl >= len) { + DPRINTF("Corrupt frame\n"); + m_freem(m); + break; + } + diff = sizeof(struct usie_desc) + ipl + pad; + + if (((rxd->hip.id & USIE_HIP_MASK) != USIE_HIP_IP) || + (be16toh(rxd->desc_type) & USIE_TYPE_MASK) != USIE_IP_RX) { + DPRINTF("received wrong type of packet\n"); + m->m_data += diff; + m->m_pkthdr.len = (m->m_len -= diff); + err++; + if (m->m_pkthdr.len > 0) + continue; + m_freem(m); + break; + } + switch (be16toh(rxd->ethhdr.ether_type)) { + case ETHERTYPE_IP: + ipv = NETISR_IP; + break; +#ifdef INET6 + case ETHERTYPE_IPV6: + ipv = NETISR_IPV6; + break; +#endif + default: + DPRINTF("unsupported ether type\n"); + err++; + break; + } + + /* the last packet */ + if (m->m_pkthdr.len <= diff) { + m->m_data += (sizeof(struct usie_desc) + pad); + m->m_pkthdr.len = m->m_len = ipl; + m->m_pkthdr.rcvif = ifp; + BPF_MTAP(sc->sc_ifp, m); + netisr_dispatch(ipv, m); + break; + } + /* copy aggregated frames to another mbuf */ + m0 = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (__predict_false(m0 == NULL)) { + DPRINTF("could not allocate mbuf\n"); + err++; + m_freem(m); + break; + } + m_copydata(m, sizeof(struct usie_desc) + pad, ipl, mtod(m0, caddr_t)); + m0->m_pkthdr.rcvif = ifp; + m0->m_pkthdr.len = m0->m_len = ipl; + + BPF_MTAP(sc->sc_ifp, m0); + netisr_dispatch(ipv, m0); + + m->m_data += diff; + m->m_pkthdr.len = (m->m_len -= diff); + } + + mtx_lock(&sc->sc_mtx); + + ifp->if_ierrors += err; + ifp->if_ipackets += pkt; +} + +static void +usie_if_tx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usie_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + uint16_t size; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_opackets++; + + /* fall though */ + case USB_ST_SETUP: +tr_setup: + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + break; + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + + if (m->m_pkthdr.len > (MCLBYTES - ETHER_HDR_LEN + + ETHER_CRC_LEN - sizeof(sc->sc_txd))) { + DPRINTF("packet len is too big: %d\n", + m->m_pkthdr.len); + break; + } + pc = usbd_xfer_get_frame(xfer, 0); + + sc->sc_txd.hip.len = htobe16(m->m_pkthdr.len + + ETHER_HDR_LEN + ETHER_CRC_LEN); + size = sizeof(sc->sc_txd); + + usbd_copy_in(pc, 0, &sc->sc_txd, size); + usbd_m_copy_in(pc, size, m, 0, m->m_pkthdr.len); + usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len + + size + ETHER_CRC_LEN); + + BPF_MTAP(ifp, m); + + m_freem(m); + + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTF("USB transfer error, %s\n", + usbd_errstr(error)); + ifp->if_oerrors++; + + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + ifp->if_ierrors++; + goto tr_setup; + } + break; + } +} + +static void +usie_if_status_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usie_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + struct usb_cdc_notification cdc; + uint32_t actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(4, "info received, actlen=%d\n", actlen); + + /* usb_cdc_notification - .data[16] */ + if (actlen < (sizeof(cdc) - 16)) { + DPRINTF("data too short %d\n", actlen); + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &cdc, (sizeof(cdc) - 16)); + + DPRINTFN(4, "bNotification=%x\n", cdc.bNotification); + + if (cdc.bNotification & UCDC_N_RESPONSE_AVAILABLE) { + taskqueue_enqueue(taskqueue_thread, + &sc->sc_if_status_task); + } + /* fall though */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTF("USB transfer error, %s\n", + usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +usie_if_sync_to(void *arg) +{ + struct usie_softc *sc = arg; + + taskqueue_enqueue(taskqueue_thread, &sc->sc_if_sync_task); +} + +static void +usie_if_sync_cb(void *arg, int pending) +{ + struct usie_softc *sc = arg; + + mtx_lock(&sc->sc_mtx); + + /* call twice */ + usie_if_cmd(sc, USIE_HIP_SYNC2M); + usie_if_cmd(sc, USIE_HIP_SYNC2M); + + usb_callout_reset(&sc->sc_if_sync_ch, 2 * hz, usie_if_sync_to, sc); + + mtx_unlock(&sc->sc_mtx); +} + +static void +usie_if_status_cb(void *arg, int pending) +{ + struct usie_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct usb_device_request req; + struct usie_hip *hip; + struct usie_lsi *lsi; + uint16_t actlen; + uint8_t ntries; + uint8_t pad; + + mtx_lock(&sc->sc_mtx); + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_if_ifnum); + USETW(req.wLength, sizeof(sc->sc_status_temp)); + + for (ntries = 0; ntries != 10; ntries++) { + int err; + + err = usbd_do_request_flags(sc->sc_udev, + &sc->sc_mtx, &req, sc->sc_status_temp, USB_SHORT_XFER_OK, + &actlen, USB_DEFAULT_TIMEOUT); + + if (err == 0) + break; + + DPRINTF("Control request failed: %s %d/10\n", + usbd_errstr(err), ntries); + + usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10)); + } + + if (ntries == 10) { + mtx_unlock(&sc->sc_mtx); + DPRINTF("Timeout\n"); + return; + } + + hip = (struct usie_hip *)sc->sc_status_temp; + + pad = (hip->id & USIE_HIP_PAD) ? 1 : 0; + + DPRINTF("hip.id=%x hip.len=%d actlen=%u pad=%d\n", + hip->id, be16toh(hip->len), actlen, pad); + + switch (hip->id & USIE_HIP_MASK) { + case USIE_HIP_SYNC2H: + usie_if_cmd(sc, USIE_HIP_SYNC2M); + break; + case USIE_HIP_RESTR: + usb_callout_stop(&sc->sc_if_sync_ch); + break; + case USIE_HIP_UMTS: + lsi = (struct usie_lsi *)( + sc->sc_status_temp + sizeof(struct usie_hip) + pad); + + DPRINTF("lsi.proto=%x lsi.len=%d\n", lsi->proto, + be16toh(lsi->len)); + + if (lsi->proto != USIE_LSI_UMTS) + break; + + if (lsi->area == USIE_LSI_AREA_NO || + lsi->area == USIE_LSI_AREA_NODATA) { + device_printf(sc->sc_dev, "no service available\n"); + break; + } + if (lsi->state == USIE_LSI_STATE_IDLE) { + DPRINTF("lsi.state=%x\n", lsi->state); + break; + } + DPRINTF("ctx=%x\n", hip->param); + sc->sc_txd.hip.param = hip->param; + + sc->sc_net.addr_len = lsi->pdp_addr_len; + memcpy(&sc->sc_net.dns1_addr, &lsi->dns1_addr, 16); + memcpy(&sc->sc_net.dns2_addr, &lsi->dns2_addr, 16); + memcpy(sc->sc_net.pdp_addr, lsi->pdp_addr, 16); + memcpy(sc->sc_net.gw_addr, lsi->gw_addr, 16); + ifp->if_flags |= IFF_UP; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + device_printf(sc->sc_dev, "IP Addr=%d.%d.%d.%d\n", + *lsi->pdp_addr, *(lsi->pdp_addr + 1), + *(lsi->pdp_addr + 2), *(lsi->pdp_addr + 3)); + device_printf(sc->sc_dev, "Gateway Addr=%d.%d.%d.%d\n", + *lsi->gw_addr, *(lsi->gw_addr + 1), + *(lsi->gw_addr + 2), *(lsi->gw_addr + 3)); + device_printf(sc->sc_dev, "Prim NS Addr=%d.%d.%d.%d\n", + *lsi->dns1_addr, *(lsi->dns1_addr + 1), + *(lsi->dns1_addr + 2), *(lsi->dns1_addr + 3)); + device_printf(sc->sc_dev, "Scnd NS Addr=%d.%d.%d.%d\n", + *lsi->dns2_addr, *(lsi->dns2_addr + 1), + *(lsi->dns2_addr + 2), *(lsi->dns2_addr + 3)); + + usie_cns_req(sc, USIE_CNS_ID_RSSI, USIE_CNS_OB_RSSI); + break; + + case USIE_HIP_RCGI: + /* ignore, workaround for sloppy windows */ + break; + default: + DPRINTF("undefined msgid: %x\n", hip->id); + break; + } + + mtx_unlock(&sc->sc_mtx); +} + +static void +usie_if_start(struct ifnet *ifp) +{ + struct usie_softc *sc = ifp->if_softc; + + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + DPRINTF("Not running\n"); + return; + } + mtx_lock(&sc->sc_mtx); + usbd_transfer_start(sc->sc_if_xfer[USIE_IF_TX]); + mtx_unlock(&sc->sc_mtx); + + DPRINTFN(3, "interface started\n"); +} + +static int +usie_if_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst, + struct route *ro) +{ + int err; + + DPRINTF("proto=%x\n", dst->sa_family); + + switch (dst->sa_family) { +#ifdef INET6 + case AF_INET6; + /* fall though */ +#endif + case AF_INET: + break; + + /* silently drop dhclient packets */ + case AF_UNSPEC: + m_freem(m); + return (0); + + /* drop other packet types */ + default: + m_freem(m); + return (EAFNOSUPPORT); + } + + err = (ifp->if_transmit)(ifp, m); + if (err) { + ifp->if_oerrors++; + return (ENOBUFS); + } + ifp->if_opackets++; + + return (0); +} + +static void +usie_if_init(void *arg) +{ + struct usie_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + uint8_t i; + + mtx_lock(&sc->sc_mtx); + + /* write tx descriptor */ + sc->sc_txd.hip.id = USIE_HIP_CTX; + sc->sc_txd.hip.param = 0; /* init value */ + sc->sc_txd.desc_type = htobe16(USIE_IP_TX); + + for (i = 0; i != USIE_IF_N_XFER; i++) + usbd_xfer_set_stall(sc->sc_if_xfer[i]); + + usbd_transfer_start(sc->sc_uc_xfer[USIE_HIP_IF][USIE_UC_RX]); + usbd_transfer_start(sc->sc_if_xfer[USIE_IF_STATUS]); + usbd_transfer_start(sc->sc_if_xfer[USIE_IF_RX]); + + /* if not running, initiate the modem */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + usie_cns_req(sc, USIE_CNS_ID_INIT, USIE_CNS_OB_LINK_UPDATE); + + mtx_unlock(&sc->sc_mtx); + + DPRINTF("ifnet initialized\n"); +} + +static void +usie_if_stop(struct usie_softc *sc) +{ + usb_callout_drain(&sc->sc_if_sync_ch); + + mtx_lock(&sc->sc_mtx); + + /* usie_cns_req() clears IFF_* flags */ + usie_cns_req(sc, USIE_CNS_ID_STOP, USIE_CNS_OB_LINK_UPDATE); + + usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_TX]); + usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_RX]); + usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_STATUS]); + + /* shutdown device */ + usie_if_cmd(sc, USIE_HIP_DOWN); + + mtx_unlock(&sc->sc_mtx); +} + +static int +usie_if_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct usie_softc *sc = ifp->if_softc; + struct ieee80211req *ireq; + struct ieee80211req_sta_info si; + struct ifmediareq *ifmr; + + switch (cmd) { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + usie_if_init(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + usie_if_stop(sc); + } + break; + + case SIOCSIFCAP: + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + device_printf(sc->sc_dev, + "Connect to the network first.\n"); + break; + } + mtx_lock(&sc->sc_mtx); + usie_cns_req(sc, USIE_CNS_ID_RSSI, USIE_CNS_OB_RSSI); + mtx_unlock(&sc->sc_mtx); + break; + + case SIOCG80211: + ireq = (struct ieee80211req *)data; + + if (ireq->i_type != IEEE80211_IOC_STA_INFO) + break; + + memset(&si, 0, sizeof(si)); + si.isi_len = sizeof(si); + /* + * ifconfig expects RSSI in 0.5dBm units + * relative to the noise floor. + */ + si.isi_rssi = 2 * sc->sc_rssi; + if (copyout(&si, (uint8_t *)ireq->i_data + 8, + sizeof(struct ieee80211req_sta_info))) + DPRINTF("copyout failed\n"); + DPRINTF("80211\n"); + break; + + case SIOCGIFMEDIA: /* to fool ifconfig */ + ifmr = (struct ifmediareq *)data; + ifmr->ifm_count = 1; + DPRINTF("media\n"); + break; + + case SIOCSIFADDR: + case SIOCSIFDSTADDR: + break; + + default: + return (EINVAL); + } + return (0); +} + +static int +usie_do_request(struct usie_softc *sc, struct usb_device_request *req, + void *data) +{ + int err = 0; + int ntries; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + for (ntries = 0; ntries != 10; ntries++) { + err = usbd_do_request(sc->sc_udev, + &sc->sc_mtx, req, data); + if (err == 0) + break; + + DPRINTF("Control request failed: %s %d/10\n", + usbd_errstr(err), ntries); + + usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10)); + } + return (err); +} + +static int +usie_if_cmd(struct usie_softc *sc, uint8_t cmd) +{ + struct usb_device_request req; + struct usie_hip msg; + + msg.len = 0; + msg.id = cmd; + msg.param = 0; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_if_ifnum); + USETW(req.wLength, sizeof(msg)); + + DPRINTF("cmd=%x\n", cmd); + + return (usie_do_request(sc, &req, &msg)); +} + +static void +usie_cns_req(struct usie_softc *sc, uint32_t id, uint16_t obj) +{ + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + struct usb_xfer *xfer; + struct usie_hip *hip; + struct usie_cns *cns; + uint8_t *param; + uint8_t *tmp; + uint8_t cns_len; + + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (__predict_false(m == NULL)) { + DPRINTF("could not allocate mbuf\n"); + ifp->if_ierrors++; + return; + } + /* to align usie_hip{} on 32 bit */ + m->m_data += 3; + param = mtod(m, uint8_t *); + *param++ = USIE_HIP_FRM_CHR; + hip = (struct usie_hip *)param; + cns = (struct usie_cns *)(hip + 1); + + tmp = param + USIE_HIPCNS_MIN - 2; + + switch (obj) { + case USIE_CNS_OB_LINK_UPDATE: + cns_len = 2; + cns->op = USIE_CNS_OP_SET; + *tmp++ = 1; /* profile ID, always use 1 for now */ + *tmp++ = id == USIE_CNS_ID_INIT ? 1 : 0; + break; + + case USIE_CNS_OB_PROF_WRITE: + cns_len = 245; + cns->op = USIE_CNS_OP_SET; + *tmp++ = 1; /* profile ID, always use 1 for now */ + *tmp++ = 2; + memcpy(tmp, &sc->sc_net, 34); + memset(tmp + 35, 0, 245 - 36); + tmp += 243; + break; + + case USIE_CNS_OB_RSSI: + cns_len = 0; + cns->op = USIE_CNS_OP_REQ; + break; + + default: + DPRINTF("unsupported CnS object type\n"); + return; + } + *tmp = USIE_HIP_FRM_CHR; + + hip->len = htobe16(sizeof(struct usie_cns) + cns_len); + hip->id = USIE_HIP_CNS2M; + hip->param = 0; /* none for CnS */ + + cns->obj = htobe16(obj); + cns->id = htobe32(id); + cns->len = cns_len; + cns->rsv0 = cns->rsv1 = 0; /* always '0' */ + + param = (uint8_t *)(cns + 1); + + DPRINTF("param: %16D\n", param, ":"); + + m->m_pkthdr.len = m->m_len = USIE_HIPCNS_MIN + cns_len + 2; + + xfer = sc->sc_uc_xfer[USIE_HIP_IF][USIE_UC_TX]; + + if (usbd_xfer_get_priv(xfer) == NULL) { + usbd_xfer_set_priv(xfer, m); + usbd_transfer_start(xfer); + } else { + DPRINTF("Dropped CNS event\n"); + m_freem(m); + } +} + +static void +usie_cns_rsp(struct usie_softc *sc, struct usie_cns *cns) +{ + struct ifnet *ifp = sc->sc_ifp; + + DPRINTF("received CnS\n"); + + switch (be16toh(cns->obj)) { + case USIE_CNS_OB_LINK_UPDATE: + if (be32toh(cns->id) & USIE_CNS_ID_INIT) + usie_if_sync_to(sc); + else if (be32toh(cns->id) & USIE_CNS_ID_STOP) { + ifp->if_flags &= ~IFF_UP; + ifp->if_drv_flags &= + ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + } else + DPRINTF("undefined link update\n"); + break; + + case USIE_CNS_OB_RSSI: + sc->sc_rssi = be16toh(*(int16_t *)(cns + 1)); + if (sc->sc_rssi <= 0) + device_printf(sc->sc_dev, "No signal\n"); + else { + device_printf(sc->sc_dev, "RSSI=%ddBm\n", + sc->sc_rssi - 110); + } + break; + + case USIE_CNS_OB_PROF_WRITE: + break; + + case USIE_CNS_OB_PDP_READ: + break; + + default: + DPRINTF("undefined CnS\n"); + break; + } +} + +static void +usie_hip_rsp(struct usie_softc *sc, uint8_t *rsp, uint32_t len) +{ + struct usie_hip *hip; + struct usie_cns *cns; + uint32_t i; + uint32_t j; + uint32_t off; + uint8_t tmp[USIE_HIPCNS_MAX] __aligned(4); + + for (off = 0; (off + USIE_HIPCNS_MIN) <= len; off++) { + + uint8_t pad; + + while ((off < len) && (rsp[off] == USIE_HIP_FRM_CHR)) + off++; + + /* Unstuff the bytes */ + for (i = j = 0; ((i + off) < len) && + (j < USIE_HIPCNS_MAX); i++) { + + if (rsp[i + off] == USIE_HIP_FRM_CHR) + break; + + if (rsp[i + off] == USIE_HIP_ESC_CHR) { + if ((i + off + 1) >= len) + break; + tmp[j++] = rsp[i++ + off + 1] ^ 0x20; + } else { + tmp[j++] = rsp[i + off]; + } + } + + off += i; + + DPRINTF("frame len=%d\n", j); + + if (j < sizeof(struct usie_hip)) { + DPRINTF("too little data\n"); + break; + } + /* + * Make sure we are not reading the stack if something + * is wrong. + */ + memset(tmp + j, 0, sizeof(tmp) - j); + + hip = (struct usie_hip *)tmp; + + DPRINTF("hip: len=%d msgID=%02x, param=%02x\n", + be16toh(hip->len), hip->id, hip->param); + + pad = (hip->id & USIE_HIP_PAD) ? 1 : 0; + + if ((hip->id & USIE_HIP_MASK) == USIE_HIP_CNS2H) { + cns = (struct usie_cns *)(((uint8_t *)(hip + 1)) + pad); + + if (j < (sizeof(struct usie_cns) + + sizeof(struct usie_hip) + pad)) { + DPRINTF("too little data\n"); + break; + } + DPRINTF("cns: obj=%04x, op=%02x, rsv0=%02x, " + "app=%08x, rsv1=%02x, len=%d\n", + be16toh(cns->obj), cns->op, cns->rsv0, + be32toh(cns->id), cns->rsv1, cns->len); + + if (cns->op & USIE_CNS_OP_ERR) + DPRINTF("CnS error response\n"); + else + usie_cns_rsp(sc, cns); + + i = sizeof(struct usie_hip) + pad + sizeof(struct usie_cns); + j = cns->len; + } else { + i = sizeof(struct usie_hip) + pad; + j = be16toh(hip->len); + } +#ifdef USB_DEBUG + if (usie_debug == 0) + continue; + + while (i < USIE_HIPCNS_MAX && j > 0) { + DPRINTF("param[0x%02x] = 0x%02x\n", i, tmp[i]); + i++; + j--; + } +#endif + } +} + +static int +usie_driver_loaded(struct module *mod, int what, void *arg) +{ + switch (what) { + case MOD_LOAD: + /* register autoinstall handler */ + usie_etag = EVENTHANDLER_REGISTER(usb_dev_configured, + usie_autoinst, NULL, EVENTHANDLER_PRI_ANY); + break; + case MOD_UNLOAD: + EVENTHANDLER_DEREGISTER(usb_dev_configured, usie_etag); + break; + default: + return (EOPNOTSUPP); + } + return (0); +} + diff --git a/sys/bus/u4b/net/if_usievar.h b/sys/bus/u4b/net/if_usievar.h new file mode 100644 index 0000000000..9ba0dc8b12 --- /dev/null +++ b/sys/bus/u4b/net/if_usievar.h @@ -0,0 +1,256 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2011 Anybots Inc + * written by Akinori Furukoshi + * - ucom part is based on u3g.c + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _IF_USEVAR_H_ +#define _IF_USEVAR_H_ + +#define USIE_DCD 0x0001 +#define USIE_DSR 0x0002 +#define USIE_DTR 0x0004 +#define USIE_RI 0x0008 +#define USIE_CTS 0x0100 +#define USIE_RTS 0x0200 + +#define USIE_HIP_FRM_CHR 0x7e +#define USIE_HIP_ESC_CHR 0x7d +#define USIE_HIP_IF 0 + +#define USIE_HIPCNS_MIN 16 /* HIP + CnS + 2 framing char */ +#define USIE_HIPCNS_MAX 261 /* HIP + max CnS 255 + 2 framing char */ + +#define USIE_CNFG_INDEX 0 +#define USIE_IFACE_INDEX 0 +#define USIE_IFACE_MAX 12 +#define USIE_BUFSIZE 2048 +#define USIE_MTU_MAX 1500 +#define USIE_RXSZ_MAX 4096 + +/* USB control pipe request */ +#define USIE_POWER 0x00 +#define USIE_FW_ATTR 0x06 +#define USIE_NMEA 0x07 +#define USIE_LINK_STATE 0x22 + +/* firmware attr flags */ +#define USIE_PM_AUTO (1 << 1) +#define USIE_FW_DHCP (1 << 3) /* DHCP capable */ + +/* line state flags */ +#define USIE_LS_DTR (1 << 0) +#define USIE_LS_RTS (1 << 1) + +/* Host Interface Porotocol Header */ +struct usie_hip { + uint16_t len; +#define USIE_HIP_LEN_MASK 0x3fff +#define USIE_HIP_IP_LEN_MASK 0x07ff + + uint8_t id; +#define USIE_HIP_PAD (1 << 7) +#define USIE_HIP_MASK 0x7f +#define USIE_HIP_SYNC2M 0x20 /* host -> modem */ +#define USIE_HIP_DOWN 0x26 +#define USIE_HIP_CNS2M 0x2b /* h -> m */ +#define USIE_HIP_CTX 0x3f +#define USIE_HIP_SYNC2H 0x60 /* h <- m */ +#define USIE_HIP_RESTR 0x62 +#define USIE_HIP_RCGI 0x64 +#define USIE_HIP_CNS2H 0x6b /* h <- m */ +#define USIE_HIP_UMTS 0x78 +#define USIE_HIP_IP 0x7f + + uint8_t param; +} __packed __aligned(4); + +/* Control and Status Header */ +struct usie_cns { + uint16_t obj; /* object type */ +#define USIE_CNS_OB_RSSI 0x1001 /* read RSSI */ +#define USIE_CNS_OB_HW_DISABLE 0x1011 /* disable h/w */ +#define USIE_CNS_OB_PW_SW 0x1071 /* power on/off */ +#define USIE_CNS_OB_PROF_WRITE 0x7003 /* write profile */ +#define USIE_CNS_OB_LINK_UPDATE 0x7004 /* dis/connect */ +#define USIE_CNS_OB_PDP_READ 0x7006 /* read out IP addr */ + + uint8_t op; /* operation type */ +#define USIE_CNS_OP_ERR (1 << 7)/* | == error */ +#define USIE_CNS_OP_REQ 0x01 /* host -> modem */ +#define USIE_CNS_OP_RSP 0x02 /* h <- m */ +#define USIE_CNS_OP_SET 0x03 /* h -> m */ +#define USIE_CNS_OP_ACK 0x04 /* h <- m */ +#define USIE_CNS_OP_NOTIF_ON 0x05 /* h -> m */ +#define USIE_CNS_OP_RSP_ON 0x06 /* h <- m */ +#define USIE_CNS_OP_NOTIF 0x07 /* h <- m */ +#define USIE_CNS_OP_NOTIF_OFF 0x08 /* h -> m */ +#define USIE_CNS_OP_RSP_OFF 0x09 /* h <- m */ +#define USIE_CNS_OP_REQ_CHG 0x0a /* h -> m */ +#define USIE_CNS_OP_RSP_CHG 0x0b /* h <- m */ + + uint8_t rsv0; /* reserved, always '0' */ + uint32_t id; /* caller ID */ +/* + * .id is to identify calling functions + * h/w responses with the same .id used in request. Only '0' is reserved + * for notification (asynchronous message generated by h/w without any + * request). All other values are user defineable. + */ +#define USIE_CNS_ID_NOTIF 0x00000000 /* reserved */ +#define USIE_CNS_ID_INIT 0x00000001 +#define USIE_CNS_ID_STOP 0x00000002 +#define USIE_CNS_ID_DNS 0x00000003 +#define USIE_CNS_ID_RSSI 0x00000004 + + uint8_t rsv1; /* reserved, always '0' */ + uint8_t len; /* length of param */ +} __packed; + +/* + * CnS param attached to struct usie_cns + * usie_cns.len is total size of this param + * max 255 + */ +#define USIE_CNS_PM_UP 0x01 +#define USIE_CNS_PM_DOWN 0x00 + +/* Link Sense Indication data structure */ +struct usie_lsi { + uint8_t proto; +#define USIE_LSI_UMTS 0x01 + + uint8_t pad0; + uint16_t len; + uint8_t area; +#define USIE_LSI_AREA_NO 0x00 +#define USIE_LSI_AREA_NODATA 0x01 + + uint8_t pad1[41]; + uint8_t state; +#define USIE_LSI_STATE_IDLE 0x00 + + uint8_t pad2[33]; + uint8_t type; +#define USIE_LSI_IP4 0x00 + + uint8_t pdp_addr_len; /* PDP addr */ + uint8_t pdp_addr[16]; + uint8_t pad3[23]; + uint8_t dns1_addr_len; /* DNS addr */ + uint8_t dns1_addr[16]; + uint8_t dns2_addr_len; + uint8_t dns2_addr[16]; + uint8_t wins1_addr_len; /* Wins addr */ + uint8_t wins1_addr[16]; + uint8_t wins2_addr_len; + uint8_t wins2_addr[16]; + uint8_t pad4[4]; + uint8_t gw_addr_len; /* GW addr */ + uint8_t gw_addr[16]; + uint8_t rsv[8]; +} __packed; + +struct usie_net_info { + uint8_t addr_len; + uint8_t pdp_addr[16]; + uint8_t dns1_addr[16]; + uint8_t dns2_addr[16]; + uint8_t gw_addr[16]; +} __packed; + +/* Tx/Rx IP packet descriptor */ +struct usie_desc { + struct usie_hip hip; + uint16_t desc_type; +#define USIE_TYPE_MASK 0x03ff +#define USIE_IP_TX 0x0002 +#define USIE_IP_RX 0x0202 + + struct ether_header ethhdr; +} __packed; + +enum { + USIE_UC_STATUS, + USIE_UC_RX, + USIE_UC_TX, + USIE_UC_N_XFER +}; + +enum { + USIE_IF_STATUS, + USIE_IF_RX, + USIE_IF_TX, + USIE_IF_N_XFER +}; + +struct usie_softc { + struct ucom_super_softc sc_super_ucom; + +#define USIE_UCOM_MAX 6 + struct ucom_softc sc_ucom[USIE_UCOM_MAX]; + uint8_t sc_uc_ifnum[USIE_UCOM_MAX]; + + struct mtx sc_mtx; + + struct task sc_if_status_task; + struct task sc_if_sync_task; + struct usb_callout sc_if_sync_ch; + + struct usie_net_info sc_net; + + struct usie_desc sc_txd; + + struct usb_xfer *sc_uc_xfer[USIE_UCOM_MAX][USIE_UC_N_XFER]; + struct usb_xfer *sc_if_xfer[USIE_IF_N_XFER]; + + struct ifnet *sc_ifp; + struct usb_device *sc_udev; + device_t sc_dev; + + struct mbuf *sc_rxm; + + uint16_t sc_if_ifnum; + + int16_t sc_rssi; + + uint8_t sc_msr; + uint8_t sc_lsr; + uint8_t sc_nucom; + + uint8_t sc_resp_temp[USIE_BUFSIZE] __aligned(4); + uint8_t sc_status_temp[USIE_BUFSIZE] __aligned(4); +}; + +/* Some code assumptions */ + +extern uint8_t usie_assert[((sizeof(struct usie_hip) + + sizeof(struct usie_lsi) + 1) <= USIE_BUFSIZE) ? 1 : -1]; + +extern uint8_t ucdc_assert[(sizeof(struct usb_cdc_notification) + >= 16) ? 1 : -1]; + +#endif /* _IF_USEVAR_H_ */ diff --git a/sys/bus/u4b/net/ruephy.c b/sys/bus/u4b/net/ruephy.c new file mode 100644 index 0000000000..839c0225d1 --- /dev/null +++ b/sys/bus/u4b/net/ruephy.c @@ -0,0 +1,232 @@ +/*- + * Copyright (c) 2001-2003, Shunsuke Akiyama . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * driver for RealTek RTL8150 internal PHY + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include "miidevs.h" + +#include + +#include "miibus_if.h" + +static int ruephy_probe(device_t); +static int ruephy_attach(device_t); + +static device_method_t ruephy_methods[] = { + /* device interface */ + DEVMETHOD(device_probe, ruephy_probe), + DEVMETHOD(device_attach, ruephy_attach), + DEVMETHOD(device_detach, mii_phy_detach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD_END +}; + +static devclass_t ruephy_devclass; + +static driver_t ruephy_driver = { + "ruephy", + ruephy_methods, + sizeof(struct mii_softc) +}; + +DRIVER_MODULE(ruephy, miibus, ruephy_driver, ruephy_devclass, 0, 0); + +static int ruephy_service(struct mii_softc *, struct mii_data *, int); +static void ruephy_reset(struct mii_softc *); +static void ruephy_status(struct mii_softc *); + +/* + * The RealTek RTL8150 internal PHY doesn't have vendor/device ID + * registers; rue(4) fakes up a return value of all zeros. + */ +static const struct mii_phydesc ruephys[] = { + { 0, 0, "RealTek RTL8150 internal media interface" }, + MII_PHY_END +}; + +static const struct mii_phy_funcs ruephy_funcs = { + ruephy_service, + ruephy_status, + ruephy_reset +}; + +static int +ruephy_probe(device_t dev) +{ + + if (strcmp(device_get_name(device_get_parent(device_get_parent(dev))), + "rue") == 0) + return (mii_phy_dev_probe(dev, ruephys, BUS_PROBE_DEFAULT)); + return (ENXIO); +} + +static int +ruephy_attach(device_t dev) +{ + + mii_phy_dev_attach(dev, MIIF_NOISOLATE | MIIF_NOMANPAUSE, + &ruephy_funcs, 1); + return (0); +} + +static int +ruephy_service(struct mii_softc *sc, struct mii_data *mii, int cmd) +{ + struct ifmedia_entry *ife = mii->mii_media.ifm_cur; + int reg; + + switch (cmd) { + case MII_POLLSTAT: + break; + + case MII_MEDIACHG: + /* + * If the interface is not up, don't do anything. + */ + if ((mii->mii_ifp->if_flags & IFF_UP) == 0) + break; + + mii_phy_setmedia(sc); + break; + + case MII_TICK: + /* + * Is the interface even up? + */ + if ((mii->mii_ifp->if_flags & IFF_UP) == 0) + return (0); + + /* + * Only used for autonegotiation. + */ + if (IFM_SUBTYPE(ife->ifm_media) != IFM_AUTO) + break; + + /* + * Check to see if we have link. If we do, we don't + * need to restart the autonegotiation process. Read + * the MSR twice in case it's latched. + */ + reg = PHY_READ(sc, RUEPHY_MII_MSR) | + PHY_READ(sc, RUEPHY_MII_MSR); + if (reg & RUEPHY_MSR_LINK) + break; + + /* Only retry autonegotiation every mii_anegticks seconds. */ + if (sc->mii_ticks <= sc->mii_anegticks) + break; + + sc->mii_ticks = 0; + PHY_RESET(sc); + if (mii_phy_auto(sc) == EJUSTRETURN) + return (0); + break; + } + + /* Update the media status. */ + PHY_STATUS(sc); + + /* Callback if something changed. */ + mii_phy_update(sc, cmd); + + return (0); +} + +static void +ruephy_reset(struct mii_softc *sc) +{ + + mii_phy_reset(sc); + + /* + * XXX RealTek RTL8150 PHY doesn't set the BMCR properly after + * XXX reset, which breaks autonegotiation. + */ + PHY_WRITE(sc, MII_BMCR, (BMCR_S100 | BMCR_AUTOEN | BMCR_FDX)); +} + +static void +ruephy_status(struct mii_softc *phy) +{ + struct mii_data *mii = phy->mii_pdata; + struct ifmedia_entry *ife = mii->mii_media.ifm_cur; + int bmsr, bmcr, msr; + + mii->mii_media_status = IFM_AVALID; + mii->mii_media_active = IFM_ETHER; + + msr = PHY_READ(phy, RUEPHY_MII_MSR) | PHY_READ(phy, RUEPHY_MII_MSR); + if (msr & RUEPHY_MSR_LINK) + mii->mii_media_status |= IFM_ACTIVE; + + bmcr = PHY_READ(phy, MII_BMCR); + if (bmcr & BMCR_ISO) { + mii->mii_media_active |= IFM_NONE; + mii->mii_media_status = 0; + return; + } + + bmsr = PHY_READ(phy, MII_BMSR) | PHY_READ(phy, MII_BMSR); + if (bmcr & BMCR_AUTOEN) { + if ((bmsr & BMSR_ACOMP) == 0) { + /* Erg, still trying, I guess... */ + mii->mii_media_active |= IFM_NONE; + return; + } + + if (msr & RUEPHY_MSR_SPEED100) + mii->mii_media_active |= IFM_100_TX; + else + mii->mii_media_active |= IFM_10_T; + + if (msr & RUEPHY_MSR_DUPLEX) + mii->mii_media_active |= + IFM_FDX | mii_phy_flowstatus(phy); + else + mii->mii_media_active |= IFM_HDX; + } else + mii->mii_media_active = ife->ifm_media; +} diff --git a/sys/bus/u4b/net/ruephyreg.h b/sys/bus/u4b/net/ruephyreg.h new file mode 100644 index 0000000000..01d3cc1722 --- /dev/null +++ b/sys/bus/u4b/net/ruephyreg.h @@ -0,0 +1,38 @@ +/*- + * Copyright (c) 2001-2003, Shunsuke Akiyama . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _RUEPHYREG_H_ +#define _RUEPHYREG_H_ + +#define RUEPHY_MII_MSR 0x0137 /* B, R/W */ +#define RUEPHY_MSR_RXFCE 0x40 +#define RUEPHY_MSR_DUPLEX 0x10 +#define RUEPHY_MSR_SPEED100 0x08 +#define RUEPHY_MSR_LINK 0x04 + +#endif /* _RUEPHYREG_H_ */ diff --git a/sys/bus/u4b/net/uhso.c b/sys/bus/u4b/net/uhso.c new file mode 100644 index 0000000000..821117083f --- /dev/null +++ b/sys/bus/u4b/net/uhso.c @@ -0,0 +1,1909 @@ +/*- + * Copyright (c) 2010 Fredrik Lindberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" +#define USB_DEBUG_VAR uhso_debug +#include +#include +#include +#include + +#include + +struct uhso_tty { + struct uhso_softc *ht_sc; + struct usb_xfer *ht_xfer[3]; + int ht_muxport; /* Mux. port no */ + int ht_open; + char ht_name[32]; +}; + +struct uhso_softc { + device_t sc_dev; + struct usb_device *sc_udev; + struct mtx sc_mtx; + uint32_t sc_type; /* Interface definition */ + int sc_radio; + + struct usb_xfer *sc_xfer[3]; + uint8_t sc_iface_no; + uint8_t sc_iface_index; + + /* Control pipe */ + struct usb_xfer * sc_ctrl_xfer[2]; + uint8_t sc_ctrl_iface_no; + + /* Network */ + struct usb_xfer *sc_if_xfer[2]; + struct ifnet *sc_ifp; + struct mbuf *sc_mwait; /* Partial packet */ + size_t sc_waitlen; /* No. of outstanding bytes */ + struct ifqueue sc_rxq; + struct callout sc_c; + + /* TTY related structures */ + struct ucom_super_softc sc_super_ucom; + int sc_ttys; + struct uhso_tty *sc_tty; + struct ucom_softc *sc_ucom; + int sc_msr; + int sc_lsr; + int sc_line; +}; + +#define UHSO_MAX_MTU 2048 + +/* + * There are mainly two type of cards floating around. + * The first one has 2,3 or 4 interfaces with a multiplexed serial port + * and packet interface on the first interface and bulk serial ports + * on the others. + * The second type of card has several other interfaces, their purpose + * can be detected during run-time. + */ +#define UHSO_IFACE_SPEC(usb_type, port, port_type) \ + (((usb_type) << 24) | ((port) << 16) | (port_type)) + +#define UHSO_IFACE_USB_TYPE(x) ((x >> 24) & 0xff) +#define UHSO_IFACE_PORT(x) ((x >> 16) & 0xff) +#define UHSO_IFACE_PORT_TYPE(x) (x & 0xff) + +/* + * USB interface types + */ +#define UHSO_IF_NET 0x01 /* Network packet interface */ +#define UHSO_IF_MUX 0x02 /* Multiplexed serial port */ +#define UHSO_IF_BULK 0x04 /* Bulk interface */ + +/* + * Port types + */ +#define UHSO_PORT_UNKNOWN 0x00 +#define UHSO_PORT_SERIAL 0x01 /* Serial port */ +#define UHSO_PORT_NETWORK 0x02 /* Network packet interface */ + +/* + * Multiplexed serial port destination sub-port names + */ +#define UHSO_MPORT_TYPE_CTL 0x00 /* Control port */ +#define UHSO_MPORT_TYPE_APP 0x01 /* Application */ +#define UHSO_MPORT_TYPE_PCSC 0x02 +#define UHSO_MPORT_TYPE_GPS 0x03 +#define UHSO_MPORT_TYPE_APP2 0x04 /* Secondary application */ +#define UHSO_MPORT_TYPE_MAX UHSO_MPORT_TYPE_APP2 +#define UHSO_MPORT_TYPE_NOMAX 8 /* Max number of mux ports */ + +/* + * Port definitions + * Note that these definitions are arbitrary and do not match the values + * returned by the auto config descriptor. + */ +#define UHSO_PORT_TYPE_UNKNOWN 0x00 +#define UHSO_PORT_TYPE_CTL 0x01 +#define UHSO_PORT_TYPE_APP 0x02 +#define UHSO_PORT_TYPE_APP2 0x03 +#define UHSO_PORT_TYPE_MODEM 0x04 +#define UHSO_PORT_TYPE_NETWORK 0x05 +#define UHSO_PORT_TYPE_DIAG 0x06 +#define UHSO_PORT_TYPE_DIAG2 0x07 +#define UHSO_PORT_TYPE_GPS 0x08 +#define UHSO_PORT_TYPE_GPSCTL 0x09 +#define UHSO_PORT_TYPE_PCSC 0x0a +#define UHSO_PORT_TYPE_MSD 0x0b +#define UHSO_PORT_TYPE_VOICE 0x0c +#define UHSO_PORT_TYPE_MAX 0x0c + +static eventhandler_tag uhso_etag; + +/* Overall port type */ +static char *uhso_port[] = { + "Unknown", + "Serial", + "Network", + "Network/Serial" +}; + +/* + * Map between interface port type read from device and description type. + * The position in this array is a direct map to the auto config + * descriptor values. + */ +static unsigned char uhso_port_map[] = { + UHSO_PORT_TYPE_UNKNOWN, + UHSO_PORT_TYPE_DIAG, + UHSO_PORT_TYPE_GPS, + UHSO_PORT_TYPE_GPSCTL, + UHSO_PORT_TYPE_APP, + UHSO_PORT_TYPE_APP2, + UHSO_PORT_TYPE_CTL, + UHSO_PORT_TYPE_NETWORK, + UHSO_PORT_TYPE_MODEM, + UHSO_PORT_TYPE_MSD, + UHSO_PORT_TYPE_PCSC, + UHSO_PORT_TYPE_VOICE +}; +static char uhso_port_map_max = sizeof(uhso_port_map) / sizeof(char); + +static unsigned char uhso_mux_port_map[] = { + UHSO_PORT_TYPE_CTL, + UHSO_PORT_TYPE_APP, + UHSO_PORT_TYPE_PCSC, + UHSO_PORT_TYPE_GPS, + UHSO_PORT_TYPE_APP2 +}; + +static char *uhso_port_type[] = { + "Unknown", /* Not a valid port */ + "Control", + "Application", + "Application (Secondary)", + "Modem", + "Network", + "Diagnostic", + "Diagnostic (Secondary)", + "GPS", + "GPS Control", + "PC Smartcard", + "MSD", + "Voice", +}; + +static char *uhso_port_type_sysctl[] = { + "unknown", + "control", + "application", + "application", + "modem", + "network", + "diagnostic", + "diagnostic", + "gps", + "gps_control", + "pcsc", + "msd", + "voice", +}; + +#define UHSO_STATIC_IFACE 0x01 +#define UHSO_AUTO_IFACE 0x02 + +/* ifnet device unit allocations */ +static struct unrhdr *uhso_ifnet_unit = NULL; + +static const STRUCT_USB_HOST_ID uhso_devs[] = { +#define UHSO_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } + /* Option GlobeTrotter MAX 7.2 with upgraded firmware */ + UHSO_DEV(OPTION, GTMAX72, UHSO_STATIC_IFACE), + /* Option GlobeSurfer iCON 7.2 */ + UHSO_DEV(OPTION, GSICON72, UHSO_STATIC_IFACE), + /* Option iCON 225 */ + UHSO_DEV(OPTION, GTHSDPA, UHSO_STATIC_IFACE), + /* Option GlobeSurfer iCON HSUPA */ + UHSO_DEV(OPTION, GSICONHSUPA, UHSO_STATIC_IFACE), + /* Option GlobeTrotter HSUPA */ + UHSO_DEV(OPTION, GTHSUPA, UHSO_STATIC_IFACE), + /* GE40x */ + UHSO_DEV(OPTION, GE40X, UHSO_AUTO_IFACE), + UHSO_DEV(OPTION, GE40X_1, UHSO_AUTO_IFACE), + UHSO_DEV(OPTION, GE40X_2, UHSO_AUTO_IFACE), + UHSO_DEV(OPTION, GE40X_3, UHSO_AUTO_IFACE), + /* Option GlobeSurfer iCON 401 */ + UHSO_DEV(OPTION, ICON401, UHSO_AUTO_IFACE), + /* Option GlobeTrotter Module 382 */ + UHSO_DEV(OPTION, GMT382, UHSO_AUTO_IFACE), + /* Option iCON EDGE */ + UHSO_DEV(OPTION, ICONEDGE, UHSO_STATIC_IFACE), + /* Option Module HSxPA */ + UHSO_DEV(OPTION, MODHSXPA, UHSO_STATIC_IFACE), + /* Option iCON 321 */ + UHSO_DEV(OPTION, ICON321, UHSO_STATIC_IFACE), + /* Option iCON 322 */ + UHSO_DEV(OPTION, GTICON322, UHSO_STATIC_IFACE), + /* Option iCON 505 */ + UHSO_DEV(OPTION, ICON505, UHSO_AUTO_IFACE), + /* Option iCON 452 */ + UHSO_DEV(OPTION, ICON505, UHSO_AUTO_IFACE), +#undef UHSO_DEV +}; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uhso, CTLFLAG_RW, 0, "USB uhso"); +static int uhso_autoswitch = 1; +SYSCTL_INT(_hw_usb_uhso, OID_AUTO, auto_switch, CTLFLAG_RW, + &uhso_autoswitch, 0, "Automatically switch to modem mode"); + +#ifdef USB_DEBUG +#ifdef UHSO_DEBUG +static int uhso_debug = UHSO_DEBUG; +#else +static int uhso_debug = -1; +#endif + +SYSCTL_INT(_hw_usb_uhso, OID_AUTO, debug, CTLFLAG_RW, + &uhso_debug, 0, "Debug level"); + +#define UHSO_DPRINTF(n, x, ...) {\ + if (uhso_debug >= n) {\ + printf("%s: " x, __func__, ##__VA_ARGS__);\ + }\ +} +#else +#define UHSO_DPRINTF(n, x, ...) +#endif + +#ifdef UHSO_DEBUG_HEXDUMP +# define UHSO_HEXDUMP(_buf, _len) do { \ + { \ + size_t __tmp; \ + const char *__buf = (const char *)_buf; \ + for (__tmp = 0; __tmp < _len; __tmp++) \ + printf("%02hhx ", *__buf++); \ + printf("\n"); \ + } \ +} while(0) +#else +# define UHSO_HEXDUMP(_buf, _len) +#endif + +enum { + UHSO_MUX_ENDPT_INTR = 0, + UHSO_MUX_ENDPT_MAX +}; + +enum { + UHSO_CTRL_READ = 0, + UHSO_CTRL_WRITE, + UHSO_CTRL_MAX +}; + +enum { + UHSO_IFNET_READ = 0, + UHSO_IFNET_WRITE, + UHSO_IFNET_MAX +}; + +enum { + UHSO_BULK_ENDPT_READ = 0, + UHSO_BULK_ENDPT_WRITE, + UHSO_BULK_ENDPT_INTR, + UHSO_BULK_ENDPT_MAX +}; + +static usb_callback_t uhso_mux_intr_callback; +static usb_callback_t uhso_mux_read_callback; +static usb_callback_t uhso_mux_write_callback; +static usb_callback_t uhso_bs_read_callback; +static usb_callback_t uhso_bs_write_callback; +static usb_callback_t uhso_bs_intr_callback; +static usb_callback_t uhso_ifnet_read_callback; +static usb_callback_t uhso_ifnet_write_callback; + +/* Config used for the default control pipes */ +static const struct usb_config uhso_ctrl_config[UHSO_CTRL_MAX] = { + [UHSO_CTRL_READ] = { + .type = UE_CONTROL, + .endpoint = 0x00, + .direction = UE_DIR_ANY, + .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, + .bufsize = sizeof(struct usb_device_request) + 1024, + .callback = &uhso_mux_read_callback + }, + + [UHSO_CTRL_WRITE] = { + .type = UE_CONTROL, + .endpoint = 0x00, + .direction = UE_DIR_ANY, + .flags = { .pipe_bof = 1, .force_short_xfer = 1 }, + .bufsize = sizeof(struct usb_device_request) + 1024, + .timeout = 1000, + .callback = &uhso_mux_write_callback + } +}; + +/* Config for the multiplexed serial ports */ +static const struct usb_config uhso_mux_config[UHSO_MUX_ENDPT_MAX] = { + [UHSO_MUX_ENDPT_INTR] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = { .short_xfer_ok = 1 }, + .bufsize = 0, + .callback = &uhso_mux_intr_callback, + } +}; + +/* Config for the raw IP-packet interface */ +static const struct usb_config uhso_ifnet_config[UHSO_IFNET_MAX] = { + [UHSO_IFNET_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, + .bufsize = MCLBYTES, + .callback = &uhso_ifnet_read_callback + }, + [UHSO_IFNET_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .flags = { .pipe_bof = 1, .force_short_xfer = 1 }, + .bufsize = MCLBYTES, + .timeout = 5 * USB_MS_HZ, + .callback = &uhso_ifnet_write_callback + } +}; + +/* Config for interfaces with normal bulk serial ports */ +static const struct usb_config uhso_bs_config[UHSO_BULK_ENDPT_MAX] = { + [UHSO_BULK_ENDPT_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, + .bufsize = 4096, + .callback = &uhso_bs_read_callback + }, + + [UHSO_BULK_ENDPT_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .flags = { .pipe_bof = 1, .force_short_xfer = 1 }, + .bufsize = 8192, + .callback = &uhso_bs_write_callback + }, + + [UHSO_BULK_ENDPT_INTR] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = { .short_xfer_ok = 1 }, + .bufsize = 0, + .callback = &uhso_bs_intr_callback, + } +}; + +static int uhso_probe_iface(struct uhso_softc *, int, + int (*probe)(struct usb_device *, int)); +static int uhso_probe_iface_auto(struct usb_device *, int); +static int uhso_probe_iface_static(struct usb_device *, int); +static int uhso_attach_muxserial(struct uhso_softc *, struct usb_interface *, + int type); +static int uhso_attach_bulkserial(struct uhso_softc *, struct usb_interface *, + int type); +static int uhso_attach_ifnet(struct uhso_softc *, struct usb_interface *, + int type); +static void uhso_test_autoinst(void *, struct usb_device *, + struct usb_attach_arg *); +static int uhso_driver_loaded(struct module *, int, void *); +static int uhso_radio_sysctl(SYSCTL_HANDLER_ARGS); +static int uhso_radio_ctrl(struct uhso_softc *, int); + +static void uhso_ucom_start_read(struct ucom_softc *); +static void uhso_ucom_stop_read(struct ucom_softc *); +static void uhso_ucom_start_write(struct ucom_softc *); +static void uhso_ucom_stop_write(struct ucom_softc *); +static void uhso_ucom_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); +static void uhso_ucom_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void uhso_ucom_cfg_set_rts(struct ucom_softc *, uint8_t); +static void uhso_if_init(void *); +static void uhso_if_start(struct ifnet *); +static void uhso_if_stop(struct uhso_softc *); +static int uhso_if_ioctl(struct ifnet *, u_long, caddr_t); +static int uhso_if_output(struct ifnet *, struct mbuf *, struct sockaddr *, + struct route *); +static void uhso_if_rxflush(void *); + +static device_probe_t uhso_probe; +static device_attach_t uhso_attach; +static device_detach_t uhso_detach; + +static device_method_t uhso_methods[] = { + DEVMETHOD(device_probe, uhso_probe), + DEVMETHOD(device_attach, uhso_attach), + DEVMETHOD(device_detach, uhso_detach), + { 0, 0 } +}; + +static driver_t uhso_driver = { + "uhso", + uhso_methods, + sizeof(struct uhso_softc) +}; + +static devclass_t uhso_devclass; +DRIVER_MODULE(uhso, uhub, uhso_driver, uhso_devclass, uhso_driver_loaded, 0); +MODULE_DEPEND(uhso, ucom, 1, 1, 1); +MODULE_DEPEND(uhso, usb, 1, 1, 1); +MODULE_VERSION(uhso, 1); + +static struct ucom_callback uhso_ucom_callback = { + .ucom_cfg_get_status = &uhso_ucom_cfg_get_status, + .ucom_cfg_set_dtr = &uhso_ucom_cfg_set_dtr, + .ucom_cfg_set_rts = &uhso_ucom_cfg_set_rts, + .ucom_start_read = uhso_ucom_start_read, + .ucom_stop_read = uhso_ucom_stop_read, + .ucom_start_write = uhso_ucom_start_write, + .ucom_stop_write = uhso_ucom_stop_write +}; + +static int +uhso_probe(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + int error; + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != 0) + return (ENXIO); + if (uaa->info.bDeviceClass != 0xff) + return (ENXIO); + + error = usbd_lookup_id_by_uaa(uhso_devs, sizeof(uhso_devs), uaa); + if (error != 0) + return (error); + + /* + * Probe device to see if we are able to attach + * to this interface or not. + */ + if (USB_GET_DRIVER_INFO(uaa) == UHSO_AUTO_IFACE) { + if (uhso_probe_iface_auto(uaa->device, + uaa->info.bIfaceNum) == 0) + return (ENXIO); + } + return (error); +} + +static int +uhso_attach(device_t self) +{ + struct uhso_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + struct usb_config_descriptor *cd; + struct usb_interface_descriptor *id; + struct sysctl_ctx_list *sctx; + struct sysctl_oid *soid; + struct sysctl_oid *tree = NULL, *tty_node; + struct ucom_softc *ucom; + struct uhso_tty *ht; + int i, error, port; + void *probe_f; + usb_error_t uerr; + char *desc; + + sc->sc_dev = self; + sc->sc_udev = uaa->device; + mtx_init(&sc->sc_mtx, "uhso", NULL, MTX_DEF); + + sc->sc_ucom = NULL; + sc->sc_ttys = 0; + sc->sc_radio = 1; + + cd = usbd_get_config_descriptor(uaa->device); + id = usbd_get_interface_descriptor(uaa->iface); + sc->sc_ctrl_iface_no = id->bInterfaceNumber; + + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = uaa->info.bIfaceIndex; + + /* Setup control pipe */ + uerr = usbd_transfer_setup(uaa->device, + &sc->sc_iface_index, sc->sc_ctrl_xfer, + uhso_ctrl_config, UHSO_CTRL_MAX, sc, &sc->sc_mtx); + if (uerr) { + device_printf(self, "Failed to setup control pipe: %s\n", + usbd_errstr(uerr)); + goto out; + } + + if (USB_GET_DRIVER_INFO(uaa) == UHSO_STATIC_IFACE) + probe_f = uhso_probe_iface_static; + else if (USB_GET_DRIVER_INFO(uaa) == UHSO_AUTO_IFACE) + probe_f = uhso_probe_iface_auto; + else + goto out; + + error = uhso_probe_iface(sc, uaa->info.bIfaceNum, probe_f); + if (error != 0) + goto out; + + sctx = device_get_sysctl_ctx(sc->sc_dev); + soid = device_get_sysctl_tree(sc->sc_dev); + + SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "type", + CTLFLAG_RD, uhso_port[UHSO_IFACE_PORT(sc->sc_type)], 0, + "Port available at this interface"); + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "radio", + CTLTYPE_INT | CTLFLAG_RW, sc, 0, uhso_radio_sysctl, "I", "Enable radio"); + + /* + * The default interface description on most Option devices isn't + * very helpful. So we skip device_set_usb_desc and set the + * device description manually. + */ + device_set_desc_copy(self, uhso_port_type[UHSO_IFACE_PORT_TYPE(sc->sc_type)]); + /* Announce device */ + device_printf(self, "<%s port> at <%s %s> on %s\n", + uhso_port_type[UHSO_IFACE_PORT_TYPE(sc->sc_type)], + usb_get_manufacturer(uaa->device), + usb_get_product(uaa->device), + device_get_nameunit(device_get_parent(self))); + + if (sc->sc_ttys > 0) { + SYSCTL_ADD_INT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "ports", + CTLFLAG_RD, &sc->sc_ttys, 0, "Number of attached serial ports"); + + tree = SYSCTL_ADD_NODE(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, + "port", CTLFLAG_RD, NULL, "Serial ports"); + } + + /* + * Loop through the number of found TTYs and create sysctl + * nodes for them. + */ + for (i = 0; i < sc->sc_ttys; i++) { + ht = &sc->sc_tty[i]; + ucom = &sc->sc_ucom[i]; + + if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) + port = uhso_mux_port_map[ht->ht_muxport]; + else + port = UHSO_IFACE_PORT_TYPE(sc->sc_type); + + desc = uhso_port_type_sysctl[port]; + + tty_node = SYSCTL_ADD_NODE(sctx, SYSCTL_CHILDREN(tree), OID_AUTO, + desc, CTLFLAG_RD, NULL, ""); + + ht->ht_name[0] = 0; + if (sc->sc_ttys == 1) + snprintf(ht->ht_name, 32, "cuaU%d", ucom->sc_super->sc_unit); + else { + snprintf(ht->ht_name, 32, "cuaU%d.%d", + ucom->sc_super->sc_unit, ucom->sc_subunit); + } + + desc = uhso_port_type[port]; + SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(tty_node), OID_AUTO, + "tty", CTLFLAG_RD, ht->ht_name, 0, ""); + SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(tty_node), OID_AUTO, + "desc", CTLFLAG_RD, desc, 0, ""); + + if (bootverbose) + device_printf(sc->sc_dev, + "\"%s\" port at %s\n", desc, ht->ht_name); + } + + return (0); +out: + uhso_detach(sc->sc_dev); + return (ENXIO); +} + +static int +uhso_detach(device_t self) +{ + struct uhso_softc *sc = device_get_softc(self); + int i; + + usbd_transfer_unsetup(sc->sc_xfer, 3); + usbd_transfer_unsetup(sc->sc_ctrl_xfer, UHSO_CTRL_MAX); + if (sc->sc_ttys > 0) { + ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); + + for (i = 0; i < sc->sc_ttys; i++) { + if (sc->sc_tty[i].ht_muxport != -1) { + usbd_transfer_unsetup(sc->sc_tty[i].ht_xfer, + UHSO_CTRL_MAX); + } + } + + free(sc->sc_tty, M_USBDEV); + free(sc->sc_ucom, M_USBDEV); + } + + if (sc->sc_ifp != NULL) { + callout_drain(&sc->sc_c); + free_unr(uhso_ifnet_unit, sc->sc_ifp->if_dunit); + mtx_lock(&sc->sc_mtx); + uhso_if_stop(sc); + bpfdetach(sc->sc_ifp); + if_detach(sc->sc_ifp); + if_free(sc->sc_ifp); + mtx_unlock(&sc->sc_mtx); + usbd_transfer_unsetup(sc->sc_if_xfer, UHSO_IFNET_MAX); + } + + mtx_destroy(&sc->sc_mtx); + return (0); +} + +static void +uhso_test_autoinst(void *arg, struct usb_device *udev, + struct usb_attach_arg *uaa) +{ + struct usb_interface *iface; + struct usb_interface_descriptor *id; + + if (uaa->dev_state != UAA_DEV_READY || !uhso_autoswitch) + return; + + iface = usbd_get_iface(udev, 0); + if (iface == NULL) + return; + id = iface->idesc; + if (id == NULL || id->bInterfaceClass != UICLASS_MASS) + return; + if (usbd_lookup_id_by_uaa(uhso_devs, sizeof(uhso_devs), uaa)) + return; /* no device match */ + + if (usb_msc_eject(udev, 0, MSC_EJECT_REZERO) == 0) { + /* success, mark the udev as disappearing */ + uaa->dev_state = UAA_DEV_EJECTING; + } +} + +static int +uhso_driver_loaded(struct module *mod, int what, void *arg) +{ + switch (what) { + case MOD_LOAD: + /* register our autoinstall handler */ + uhso_etag = EVENTHANDLER_REGISTER(usb_dev_configured, + uhso_test_autoinst, NULL, EVENTHANDLER_PRI_ANY); + /* create our unit allocator for inet devs */ + uhso_ifnet_unit = new_unrhdr(0, INT_MAX, NULL); + break; + case MOD_UNLOAD: + EVENTHANDLER_DEREGISTER(usb_dev_configured, uhso_etag); + delete_unrhdr(uhso_ifnet_unit); + break; + default: + return (EOPNOTSUPP); + } + return (0); +} + +/* + * Probe the interface type by querying the device. The elements + * of an array indicates the capabilities of a particular interface. + * Returns a bit mask with the interface capabilities. + */ +static int +uhso_probe_iface_auto(struct usb_device *udev, int index) +{ + struct usb_device_request req; + usb_error_t uerr; + uint16_t actlen = 0; + char port; + char buf[17] = {0}; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = 0x86; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 17); + + uerr = usbd_do_request_flags(udev, NULL, &req, buf, + 0, &actlen, USB_MS_HZ); + if (uerr != 0) { + printf("%s: usbd_do_request_flags failed, %s\n", + __func__, usbd_errstr(uerr)); + return (0); + } + + UHSO_DPRINTF(1, "actlen=%d\n", actlen); + UHSO_HEXDUMP(buf, 17); + + if (index < 0 || index > 16) { + UHSO_DPRINTF(0, "Index %d out of range\n", index); + return (0); + } + + UHSO_DPRINTF(1, "index=%d, type=%x[%s]\n", index, buf[index], + uhso_port_type[(int)uhso_port_map[(int)buf[index]]]); + + if (buf[index] >= uhso_port_map_max) + port = 0; + else + port = uhso_port_map[(int)buf[index]]; + + switch (port) { + case UHSO_PORT_TYPE_NETWORK: + return (UHSO_IFACE_SPEC(UHSO_IF_NET | UHSO_IF_MUX, + UHSO_PORT_SERIAL | UHSO_PORT_NETWORK, port)); + case UHSO_PORT_TYPE_DIAG: + case UHSO_PORT_TYPE_DIAG2: + case UHSO_PORT_TYPE_CTL: + case UHSO_PORT_TYPE_APP: + case UHSO_PORT_TYPE_APP2: + case UHSO_PORT_TYPE_MODEM: + return (UHSO_IFACE_SPEC(UHSO_IF_BULK, + UHSO_PORT_SERIAL, port)); + case UHSO_PORT_TYPE_MSD: + return (0); + case UHSO_PORT_TYPE_UNKNOWN: + default: + return (0); + } + + return (0); +} + +/* + * Returns the capabilities of interfaces for devices that don't + * support the automatic query. + * Returns a bit mask with the interface capabilities. + */ +static int +uhso_probe_iface_static(struct usb_device *udev, int index) +{ + struct usb_config_descriptor *cd; + + cd = usbd_get_config_descriptor(udev); + if (cd->bNumInterface <= 3) { + /* Cards with 3 or less interfaces */ + switch (index) { + case 0: + return UHSO_IFACE_SPEC(UHSO_IF_NET | UHSO_IF_MUX, + UHSO_PORT_SERIAL | UHSO_PORT_NETWORK, + UHSO_PORT_TYPE_NETWORK); + case 1: + return UHSO_IFACE_SPEC(UHSO_IF_BULK, + UHSO_PORT_SERIAL, UHSO_PORT_TYPE_DIAG); + case 2: + return UHSO_IFACE_SPEC(UHSO_IF_BULK, + UHSO_PORT_SERIAL, UHSO_PORT_TYPE_MODEM); + } + } else { + /* Cards with 4 interfaces */ + switch (index) { + case 0: + return UHSO_IFACE_SPEC(UHSO_IF_NET | UHSO_IF_MUX, + UHSO_PORT_SERIAL | UHSO_PORT_NETWORK, + UHSO_PORT_TYPE_NETWORK); + case 1: + return UHSO_IFACE_SPEC(UHSO_IF_BULK, + UHSO_PORT_SERIAL, UHSO_PORT_TYPE_DIAG2); + case 2: + return UHSO_IFACE_SPEC(UHSO_IF_BULK, + UHSO_PORT_SERIAL, UHSO_PORT_TYPE_MODEM); + case 3: + return UHSO_IFACE_SPEC(UHSO_IF_BULK, + UHSO_PORT_SERIAL, UHSO_PORT_TYPE_DIAG); + } + } + return (0); +} + +/* + * Probes an interface for its particular capabilities and attaches if + * it's a supported interface. + */ +static int +uhso_probe_iface(struct uhso_softc *sc, int index, + int (*probe)(struct usb_device *, int)) +{ + struct usb_interface *iface; + int type, error; + + UHSO_DPRINTF(1, "Probing for interface %d, probe_func=%p\n", index, probe); + + type = probe(sc->sc_udev, index); + UHSO_DPRINTF(1, "Probe result %x\n", type); + if (type <= 0) + return (ENXIO); + + sc->sc_type = type; + iface = usbd_get_iface(sc->sc_udev, index); + + if (UHSO_IFACE_PORT_TYPE(type) == UHSO_PORT_TYPE_NETWORK) { + error = uhso_attach_ifnet(sc, iface, type); + if (error) { + UHSO_DPRINTF(1, "uhso_attach_ifnet failed"); + return (ENXIO); + } + + /* + * If there is an additional interrupt endpoint on this + * interface then we most likely have a multiplexed serial port + * available. + */ + if (iface->idesc->bNumEndpoints < 3) { + sc->sc_type = UHSO_IFACE_SPEC( + UHSO_IFACE_USB_TYPE(type) & ~UHSO_IF_MUX, + UHSO_IFACE_PORT(type) & ~UHSO_PORT_SERIAL, + UHSO_IFACE_PORT_TYPE(type)); + return (0); + } + + UHSO_DPRINTF(1, "Trying to attach mux. serial\n"); + error = uhso_attach_muxserial(sc, iface, type); + if (error == 0 && sc->sc_ttys > 0) { + error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, + sc->sc_ttys, sc, &uhso_ucom_callback, &sc->sc_mtx); + if (error) { + device_printf(sc->sc_dev, "ucom_attach failed\n"); + return (ENXIO); + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, sc->sc_dev); + + mtx_lock(&sc->sc_mtx); + usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]); + mtx_unlock(&sc->sc_mtx); + } + } else if ((UHSO_IFACE_USB_TYPE(type) & UHSO_IF_BULK) && + UHSO_IFACE_PORT(type) & UHSO_PORT_SERIAL) { + + error = uhso_attach_bulkserial(sc, iface, type); + if (error) + return (ENXIO); + + error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, + sc->sc_ttys, sc, &uhso_ucom_callback, &sc->sc_mtx); + if (error) { + device_printf(sc->sc_dev, "ucom_attach failed\n"); + return (ENXIO); + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, sc->sc_dev); + } + else { + UHSO_DPRINTF(0, "Unknown type %x\n", type); + return (ENXIO); + } + + return (0); +} + +static int +uhso_radio_ctrl(struct uhso_softc *sc, int onoff) +{ + struct usb_device_request req; + usb_error_t uerr; + + req.bmRequestType = UT_VENDOR; + req.bRequest = onoff ? 0x82 : 0x81; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + uerr = usbd_do_request(sc->sc_udev, NULL, &req, NULL); + if (uerr != 0) { + device_printf(sc->sc_dev, "usbd_do_request_flags failed: %s\n", + usbd_errstr(uerr)); + return (-1); + } + return (onoff); +} + +static int +uhso_radio_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct uhso_softc *sc = arg1; + int error, radio; + + radio = sc->sc_radio; + error = sysctl_handle_int(oidp, &radio, 0, req); + if (error) + return (error); + if (radio != sc->sc_radio) { + radio = radio != 0 ? 1 : 0; + error = uhso_radio_ctrl(sc, radio); + if (error != -1) + sc->sc_radio = radio; + + } + return (0); +} + +/* + * Expands allocated memory to fit an additional TTY. + * Two arrays are kept with matching indexes, one for ucom and one + * for our private data. + */ +static int +uhso_alloc_tty(struct uhso_softc *sc) +{ + + sc->sc_ttys++; + sc->sc_tty = reallocf(sc->sc_tty, sizeof(struct uhso_tty) * sc->sc_ttys, + M_USBDEV, M_WAITOK | M_ZERO); + if (sc->sc_tty == NULL) + return (-1); + + sc->sc_ucom = reallocf(sc->sc_ucom, + sizeof(struct ucom_softc) * sc->sc_ttys, M_USBDEV, M_WAITOK | M_ZERO); + if (sc->sc_ucom == NULL) + return (-1); + + sc->sc_tty[sc->sc_ttys - 1].ht_sc = sc; + + UHSO_DPRINTF(1, "Allocated TTY %d\n", sc->sc_ttys - 1); + return (sc->sc_ttys - 1); +} + +/* + * Attach a multiplexed serial port + * Data is read/written with requests on the default control pipe. An interrupt + * endpoint returns when there is new data to be read. + */ +static int +uhso_attach_muxserial(struct uhso_softc *sc, struct usb_interface *iface, + int type) +{ + struct usb_descriptor *desc; + int i, port, tty; + usb_error_t uerr; + + /* + * The class specific interface (type 0x24) descriptor subtype field + * contains a bitmask that specifies which (and how many) ports that + * are available through this multiplexed serial port. + */ + desc = usbd_find_descriptor(sc->sc_udev, NULL, + iface->idesc->bInterfaceNumber, UDESC_CS_INTERFACE, 0xff, 0, 0); + if (desc == NULL) { + UHSO_DPRINTF(0, "Failed to find UDESC_CS_INTERFACE\n"); + return (ENXIO); + } + + UHSO_DPRINTF(1, "Mux port mask %x\n", desc->bDescriptorSubtype); + if (desc->bDescriptorSubtype == 0) + return (ENXIO); + + /* + * The bitmask is one octet, loop through the number of + * bits that are set and create a TTY for each. + */ + for (i = 0; i < 8; i++) { + port = (1 << i); + if ((port & desc->bDescriptorSubtype) == port) { + UHSO_DPRINTF(2, "Found mux port %x (%d)\n", port, i); + tty = uhso_alloc_tty(sc); + if (tty < 0) + return (ENOMEM); + sc->sc_tty[tty].ht_muxport = i; + uerr = usbd_transfer_setup(sc->sc_udev, + &sc->sc_iface_index, sc->sc_tty[tty].ht_xfer, + uhso_ctrl_config, UHSO_CTRL_MAX, sc, &sc->sc_mtx); + if (uerr) { + device_printf(sc->sc_dev, + "Failed to setup control pipe: %s\n", + usbd_errstr(uerr)); + return (ENXIO); + } + } + } + + /* Setup the intr. endpoint */ + uerr = usbd_transfer_setup(sc->sc_udev, + &iface->idesc->bInterfaceNumber, sc->sc_xfer, + uhso_mux_config, 1, sc, &sc->sc_mtx); + if (uerr) + return (ENXIO); + + return (0); +} + +/* + * Interrupt callback for the multiplexed serial port. Indicates + * which serial port has data waiting. + */ +static void +uhso_mux_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_page_cache *pc; + struct usb_page_search res; + struct uhso_softc *sc = usbd_xfer_softc(xfer); + unsigned int i, mux; + + UHSO_DPRINTF(3, "status %d\n", USB_GET_STATE(xfer)); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + /* + * The multiplexed port number can be found at the first byte. + * It contains a bit mask, we transform this in to an integer. + */ + pc = usbd_xfer_get_frame(xfer, 0); + usbd_get_page(pc, 0, &res); + + i = *((unsigned char *)res.buffer); + mux = 0; + while (i >>= 1) { + mux++; + } + + UHSO_DPRINTF(3, "mux port %d (%d)\n", mux, i); + if (mux > UHSO_MPORT_TYPE_NOMAX) + break; + + /* Issue a read for this serial port */ + usbd_xfer_set_priv( + sc->sc_tty[mux].ht_xfer[UHSO_CTRL_READ], + &sc->sc_tty[mux]); + usbd_transfer_start(sc->sc_tty[mux].ht_xfer[UHSO_CTRL_READ]); + + break; + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + default: + UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); + if (error == USB_ERR_CANCELLED) + break; + + usbd_xfer_set_stall(xfer); + goto tr_setup; + } +} + +static void +uhso_mux_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uhso_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + struct usb_device_request req; + struct uhso_tty *ht; + int actlen, len; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + UHSO_DPRINTF(3, "status %d\n", USB_GET_STATE(xfer)); + + ht = usbd_xfer_get_priv(xfer); + UHSO_DPRINTF(3, "ht=%p open=%d\n", ht, ht->ht_open); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + /* Got data, send to ucom */ + pc = usbd_xfer_get_frame(xfer, 1); + len = usbd_xfer_frame_len(xfer, 1); + + UHSO_DPRINTF(3, "got %d bytes on mux port %d\n", len, + ht->ht_muxport); + if (len <= 0) { + usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]); + break; + } + + /* Deliver data if the TTY is open, discard otherwise */ + if (ht->ht_open) + ucom_put_data(&sc->sc_ucom[ht->ht_muxport], pc, 0, len); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + memset(&req, 0, sizeof(struct usb_device_request)); + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE; + USETW(req.wValue, 0); + USETW(req.wIndex, ht->ht_muxport); + USETW(req.wLength, 1024); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, 1024); + usbd_xfer_set_frames(xfer, 2); + usbd_transfer_submit(xfer); + break; + default: + UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); + if (error == USB_ERR_CANCELLED) + break; + usbd_xfer_set_stall(xfer); + goto tr_setup; + } +} + +static void +uhso_mux_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uhso_softc *sc = usbd_xfer_softc(xfer); + struct uhso_tty *ht; + struct usb_page_cache *pc; + struct usb_device_request req; + int actlen; + struct usb_page_search res; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + ht = usbd_xfer_get_priv(xfer); + UHSO_DPRINTF(3, "status=%d, using mux port %d\n", + USB_GET_STATE(xfer), ht->ht_muxport); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + UHSO_DPRINTF(3, "wrote %zd data bytes to muxport %d\n", + actlen - sizeof(struct usb_device_request) , + ht->ht_muxport); + /* FALLTHROUGH */ + case USB_ST_SETUP: + pc = usbd_xfer_get_frame(xfer, 1); + if (ucom_get_data(&sc->sc_ucom[ht->ht_muxport], pc, + 0, 32, &actlen)) { + + usbd_get_page(pc, 0, &res); + + memset(&req, 0, sizeof(struct usb_device_request)); + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND; + USETW(req.wValue, 0); + USETW(req.wIndex, ht->ht_muxport); + USETW(req.wLength, actlen); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, actlen); + usbd_xfer_set_frames(xfer, 2); + + UHSO_DPRINTF(3, "Prepared %d bytes for transmit " + "on muxport %d\n", actlen, ht->ht_muxport); + + usbd_transfer_submit(xfer); + } + break; + default: + UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); + if (error == USB_ERR_CANCELLED) + break; + break; + } +} + +static int +uhso_attach_bulkserial(struct uhso_softc *sc, struct usb_interface *iface, + int type) +{ + usb_error_t uerr; + int tty; + + /* Try attaching RD/WR/INTR first */ + uerr = usbd_transfer_setup(sc->sc_udev, + &iface->idesc->bInterfaceNumber, sc->sc_xfer, + uhso_bs_config, UHSO_BULK_ENDPT_MAX, sc, &sc->sc_mtx); + if (uerr) { + /* Try only RD/WR */ + uerr = usbd_transfer_setup(sc->sc_udev, + &iface->idesc->bInterfaceNumber, sc->sc_xfer, + uhso_bs_config, UHSO_BULK_ENDPT_MAX - 1, sc, &sc->sc_mtx); + } + if (uerr) { + UHSO_DPRINTF(0, "usbd_transfer_setup failed"); + return (-1); + } + + tty = uhso_alloc_tty(sc); + if (tty < 0) { + usbd_transfer_unsetup(sc->sc_xfer, UHSO_BULK_ENDPT_MAX); + return (ENOMEM); + } + + sc->sc_tty[tty].ht_muxport = -1; + return (0); +} + +static void +uhso_bs_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uhso_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom[0], pc, 0, actlen); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + default: + UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); + if (error == USB_ERR_CANCELLED) + break; + usbd_xfer_set_stall(xfer); + goto tr_setup; + } +} + +static void +uhso_bs_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uhso_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom[0], pc, 0, 8192, &actlen)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + break; + break; + default: + UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); + if (error == USB_ERR_CANCELLED) + break; + usbd_xfer_set_stall(xfer); + goto tr_setup; + } +} + +static void +uhso_bs_cfg(struct uhso_softc *sc) +{ + struct usb_device_request req; + usb_error_t uerr; + + if (!(UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK)) + return; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, 0); + + uerr = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom[0], &req, NULL, 0, 1000); + if (uerr != 0) { + device_printf(sc->sc_dev, "failed to set ctrl line state to " + "0x%02x: %s\n", sc->sc_line, usbd_errstr(uerr)); + } +} + +static void +uhso_bs_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uhso_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + struct usb_cdc_notification cdc; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen < UCDC_NOTIFICATION_LENGTH) { + UHSO_DPRINTF(0, "UCDC notification too short: %d\n", actlen); + goto tr_setup; + } + else if (actlen > sizeof(struct usb_cdc_notification)) { + UHSO_DPRINTF(0, "UCDC notification too large: %d\n", actlen); + actlen = sizeof(struct usb_cdc_notification); + } + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &cdc, actlen); + + if (UGETW(cdc.wIndex) != sc->sc_iface_no) { + UHSO_DPRINTF(0, "Interface mismatch, got %d expected %d\n", + UGETW(cdc.wIndex), sc->sc_iface_no); + goto tr_setup; + } + + if (cdc.bmRequestType == UCDC_NOTIFICATION && + cdc.bNotification == UCDC_N_SERIAL_STATE) { + UHSO_DPRINTF(2, "notify = 0x%02x\n", cdc.data[0]); + + sc->sc_msr = 0; + sc->sc_lsr = 0; + if (cdc.data[0] & UCDC_N_SERIAL_RI) + sc->sc_msr |= SER_RI; + if (cdc.data[0] & UCDC_N_SERIAL_DSR) + sc->sc_msr |= SER_DSR; + if (cdc.data[0] & UCDC_N_SERIAL_DCD) + sc->sc_msr |= SER_DCD; + + ucom_status_change(&sc->sc_ucom[0]); + } + case USB_ST_SETUP: +tr_setup: + default: + if (error == USB_ERR_CANCELLED) + break; + usbd_xfer_set_stall(xfer); + goto tr_setup; + } +} + +static void +uhso_ucom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uhso_softc *sc = ucom->sc_parent; + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +uhso_ucom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uhso_softc *sc = ucom->sc_parent; + + if (!(UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK)) + return; + + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + uhso_bs_cfg(sc); +} + +static void +uhso_ucom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uhso_softc *sc = ucom->sc_parent; + + if (!(UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK)) + return; + + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + uhso_bs_cfg(sc); +} + +static void +uhso_ucom_start_read(struct ucom_softc *ucom) +{ + struct uhso_softc *sc = ucom->sc_parent; + + UHSO_DPRINTF(3, "unit=%d, subunit=%d\n", + ucom->sc_super->sc_unit, ucom->sc_subunit); + + if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) { + sc->sc_tty[ucom->sc_subunit].ht_open = 1; + usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]); + } + else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) { + sc->sc_tty[0].ht_open = 1; + usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_READ]); + if (sc->sc_xfer[UHSO_BULK_ENDPT_INTR] != NULL) + usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_INTR]); + } +} + +static void +uhso_ucom_stop_read(struct ucom_softc *ucom) +{ + + struct uhso_softc *sc = ucom->sc_parent; + + if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) { + sc->sc_tty[ucom->sc_subunit].ht_open = 0; + usbd_transfer_stop( + sc->sc_tty[ucom->sc_subunit].ht_xfer[UHSO_CTRL_READ]); + } + else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) { + sc->sc_tty[0].ht_open = 0; + usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_READ]); + if (sc->sc_xfer[UHSO_BULK_ENDPT_INTR] != NULL) + usbd_transfer_stop(sc->sc_xfer[UHSO_BULK_ENDPT_INTR]); + } +} + +static void +uhso_ucom_start_write(struct ucom_softc *ucom) +{ + struct uhso_softc *sc = ucom->sc_parent; + + if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) { + UHSO_DPRINTF(3, "local unit %d\n", ucom->sc_subunit); + + usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]); + + usbd_xfer_set_priv( + sc->sc_tty[ucom->sc_subunit].ht_xfer[UHSO_CTRL_WRITE], + &sc->sc_tty[ucom->sc_subunit]); + usbd_transfer_start( + sc->sc_tty[ucom->sc_subunit].ht_xfer[UHSO_CTRL_WRITE]); + + } + else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) { + usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_WRITE]); + } +} + +static void +uhso_ucom_stop_write(struct ucom_softc *ucom) +{ + struct uhso_softc *sc = ucom->sc_parent; + + if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) { + usbd_transfer_stop( + sc->sc_tty[ucom->sc_subunit].ht_xfer[UHSO_CTRL_WRITE]); + } + else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) { + usbd_transfer_stop(sc->sc_xfer[UHSO_BULK_ENDPT_WRITE]); + } +} + +static int +uhso_attach_ifnet(struct uhso_softc *sc, struct usb_interface *iface, int type) +{ + struct ifnet *ifp; + usb_error_t uerr; + struct sysctl_ctx_list *sctx; + struct sysctl_oid *soid; + unsigned int devunit; + + uerr = usbd_transfer_setup(sc->sc_udev, + &iface->idesc->bInterfaceNumber, sc->sc_if_xfer, + uhso_ifnet_config, UHSO_IFNET_MAX, sc, &sc->sc_mtx); + if (uerr) { + UHSO_DPRINTF(0, "usbd_transfer_setup failed: %s\n", + usbd_errstr(uerr)); + return (-1); + } + + sc->sc_ifp = ifp = if_alloc(IFT_OTHER); + if (sc->sc_ifp == NULL) { + device_printf(sc->sc_dev, "if_alloc() failed\n"); + return (-1); + } + + callout_init_mtx(&sc->sc_c, &sc->sc_mtx, 0); + mtx_lock(&sc->sc_mtx); + callout_reset(&sc->sc_c, 1, uhso_if_rxflush, sc); + mtx_unlock(&sc->sc_mtx); + + /* + * We create our own unit numbers for ifnet devices because the + * USB interface unit numbers can be at arbitrary positions yielding + * odd looking device names. + */ + devunit = alloc_unr(uhso_ifnet_unit); + + if_initname(ifp, device_get_name(sc->sc_dev), devunit); + ifp->if_mtu = UHSO_MAX_MTU; + ifp->if_ioctl = uhso_if_ioctl; + ifp->if_init = uhso_if_init; + ifp->if_start = uhso_if_start; + ifp->if_output = uhso_if_output; + ifp->if_flags = IFF_BROADCAST | IFF_MULTICAST | IFF_NOARP; + ifp->if_softc = sc; + IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); + ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; + IFQ_SET_READY(&ifp->if_snd); + + if_attach(ifp); + bpfattach(ifp, DLT_RAW, 0); + + sctx = device_get_sysctl_ctx(sc->sc_dev); + soid = device_get_sysctl_tree(sc->sc_dev); + /* Unlocked read... */ + SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "netif", + CTLFLAG_RD, ifp->if_xname, 0, "Attached network interface"); + + return (0); +} + +static void +uhso_ifnet_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uhso_softc *sc = usbd_xfer_softc(xfer); + struct mbuf *m; + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + UHSO_DPRINTF(3, "status=%d, actlen=%d\n", USB_GET_STATE(xfer), actlen); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen > 0 && (sc->sc_ifp->if_drv_flags & IFF_DRV_RUNNING)) { + pc = usbd_xfer_get_frame(xfer, 0); + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + usbd_copy_out(pc, 0, mtod(m, uint8_t *), actlen); + m->m_pkthdr.len = m->m_len = actlen; + /* Enqueue frame for further processing */ + _IF_ENQUEUE(&sc->sc_rxq, m); + if (!callout_pending(&sc->sc_c) || + !callout_active(&sc->sc_c)) { + callout_schedule(&sc->sc_c, 1); + } + } + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + default: + UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); + if (error == USB_ERR_CANCELLED) + break; + usbd_xfer_set_stall(xfer); + goto tr_setup; + } +} + +/* + * Deferred RX processing, called with mutex locked. + * + * Each frame we receive might contain several small ip-packets as well + * as partial ip-packets. We need to separate/assemble them into individual + * packets before sending them to the ip-layer. + */ +static void +uhso_if_rxflush(void *arg) +{ + struct uhso_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + uint8_t *cp; + struct mbuf *m, *m0, *mwait; + struct ip *ip; +#ifdef INET6 + struct ip6_hdr *ip6; +#endif + uint16_t iplen; + int len, isr; + + m = NULL; + mwait = sc->sc_mwait; + for (;;) { + if (m == NULL) { + _IF_DEQUEUE(&sc->sc_rxq, m); + if (m == NULL) + break; + UHSO_DPRINTF(3, "dequeue m=%p, len=%d\n", m, m->m_len); + } + mtx_unlock(&sc->sc_mtx); + + /* Do we have a partial packet waiting? */ + if (mwait != NULL) { + m0 = mwait; + mwait = NULL; + + UHSO_DPRINTF(3, "partial m0=%p(%d), concat w/ m=%p(%d)\n", + m0, m0->m_len, m, m->m_len); + len = m->m_len + m0->m_len; + + /* Concat mbufs and fix headers */ + m_cat(m0, m); + m0->m_pkthdr.len = len; + m->m_flags &= ~M_PKTHDR; + + m = m_pullup(m0, sizeof(struct ip)); + if (m == NULL) { + ifp->if_ierrors++; + UHSO_DPRINTF(0, "m_pullup failed\n"); + mtx_lock(&sc->sc_mtx); + continue; + } + UHSO_DPRINTF(3, "Constructed mbuf=%p, len=%d\n", + m, m->m_pkthdr.len); + } + + cp = mtod(m, uint8_t *); + ip = (struct ip *)cp; +#ifdef INET6 + ip6 = (struct ip6_hdr *)cp; +#endif + + /* Check for IPv4 */ + if (ip->ip_v == IPVERSION) { + iplen = htons(ip->ip_len); + isr = NETISR_IP; + } +#ifdef INET6 + /* Check for IPv6 */ + else if ((ip6->ip6_vfc & IPV6_VERSION_MASK) == IPV6_VERSION) { + iplen = htons(ip6->ip6_plen); + isr = NETISR_IPV6; + } +#endif + else { + UHSO_DPRINTF(0, "got unexpected ip version %d, " + "m=%p, len=%d\n", (*cp & 0xf0) >> 4, m, m->m_len); + ifp->if_ierrors++; + UHSO_HEXDUMP(cp, 4); + m_freem(m); + m = NULL; + mtx_lock(&sc->sc_mtx); + continue; + } + + if (iplen == 0) { + UHSO_DPRINTF(0, "Zero IP length\n"); + ifp->if_ierrors++; + m_freem(m); + m = NULL; + mtx_lock(&sc->sc_mtx); + continue; + } + + UHSO_DPRINTF(3, "m=%p, len=%d, cp=%p, iplen=%d\n", + m, m->m_pkthdr.len, cp, iplen); + + m0 = NULL; + + /* More IP packets in this mbuf */ + if (iplen < m->m_pkthdr.len) { + m0 = m; + + /* + * Allocate a new mbuf for this IP packet and + * copy the IP-packet into it. + */ + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + memcpy(mtod(m, uint8_t *), mtod(m0, uint8_t *), iplen); + m->m_pkthdr.len = m->m_len = iplen; + + /* Adjust the size of the original mbuf */ + m_adj(m0, iplen); + m0 = m_defrag(m0, M_WAIT); + + UHSO_DPRINTF(3, "New mbuf=%p, len=%d/%d, m0=%p, " + "m0_len=%d/%d\n", m, m->m_pkthdr.len, m->m_len, + m0, m0->m_pkthdr.len, m0->m_len); + } + else if (iplen > m->m_pkthdr.len) { + UHSO_DPRINTF(3, "Deferred mbuf=%p, len=%d\n", + m, m->m_pkthdr.len); + mwait = m; + m = NULL; + mtx_lock(&sc->sc_mtx); + continue; + } + + ifp->if_ipackets++; + m->m_pkthdr.rcvif = ifp; + + /* Dispatch to IP layer */ + BPF_MTAP(sc->sc_ifp, m); + M_SETFIB(m, ifp->if_fib); + netisr_dispatch(isr, m); + m = m0 != NULL ? m0 : NULL; + mtx_lock(&sc->sc_mtx); + } + sc->sc_mwait = mwait; +} + +static void +uhso_ifnet_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uhso_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct usb_page_cache *pc; + struct mbuf *m; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + ifp->if_opackets++; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + case USB_ST_SETUP: +tr_setup: + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + + if (m->m_pkthdr.len > MCLBYTES) + m->m_pkthdr.len = MCLBYTES; + + usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len); + pc = usbd_xfer_get_frame(xfer, 0); + usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); + usbd_transfer_submit(xfer); + + BPF_MTAP(ifp, m); + m_freem(m); + break; + default: + UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error)); + if (error == USB_ERR_CANCELLED) + break; + usbd_xfer_set_stall(xfer); + goto tr_setup; + } +} + +static int +uhso_if_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct uhso_softc *sc; + + sc = ifp->if_softc; + + switch (cmd) { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + uhso_if_init(sc); + } + } + else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + mtx_lock(&sc->sc_mtx); + uhso_if_stop(sc); + mtx_unlock(&sc->sc_mtx); + } + } + break; + case SIOCSIFADDR: + case SIOCSIFDSTADDR: + case SIOCADDMULTI: + case SIOCDELMULTI: + break; + default: + return (EINVAL); + } + return (0); +} + +static void +uhso_if_init(void *priv) +{ + struct uhso_softc *sc = priv; + struct ifnet *ifp = sc->sc_ifp; + + mtx_lock(&sc->sc_mtx); + uhso_if_stop(sc); + ifp = sc->sc_ifp; + ifp->if_flags |= IFF_UP; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + mtx_unlock(&sc->sc_mtx); + + UHSO_DPRINTF(2, "ifnet initialized\n"); +} + +static int +uhso_if_output(struct ifnet *ifp, struct mbuf *m0, struct sockaddr *dst, + struct route *ro) +{ + int error; + + /* Only IPv4/6 support */ + if (dst->sa_family != AF_INET +#ifdef INET6 + && dst->sa_family != AF_INET6 +#endif + ) { + return (EAFNOSUPPORT); + } + + error = (ifp->if_transmit)(ifp, m0); + if (error) { + ifp->if_oerrors++; + return (ENOBUFS); + } + ifp->if_opackets++; + return (0); +} + +static void +uhso_if_start(struct ifnet *ifp) +{ + struct uhso_softc *sc = ifp->if_softc; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + UHSO_DPRINTF(1, "Not running\n"); + return; + } + + mtx_lock(&sc->sc_mtx); + usbd_transfer_start(sc->sc_if_xfer[UHSO_IFNET_READ]); + usbd_transfer_start(sc->sc_if_xfer[UHSO_IFNET_WRITE]); + mtx_unlock(&sc->sc_mtx); + UHSO_DPRINTF(3, "interface started\n"); +} + +static void +uhso_if_stop(struct uhso_softc *sc) +{ + + usbd_transfer_stop(sc->sc_if_xfer[UHSO_IFNET_READ]); + usbd_transfer_stop(sc->sc_if_xfer[UHSO_IFNET_WRITE]); + sc->sc_ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); +} diff --git a/sys/bus/u4b/net/usb_ethernet.c b/sys/bus/u4b/net/usb_ethernet.c new file mode 100644 index 0000000000..2a7bddfdbe --- /dev/null +++ b/sys/bus/u4b/net/usb_ethernet.c @@ -0,0 +1,640 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2009 Andrew Thompson (thompsa@FreeBSD.org) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +static SYSCTL_NODE(_net, OID_AUTO, ue, CTLFLAG_RD, 0, + "USB Ethernet parameters"); + +#define UE_LOCK(_ue) mtx_lock((_ue)->ue_mtx) +#define UE_UNLOCK(_ue) mtx_unlock((_ue)->ue_mtx) +#define UE_LOCK_ASSERT(_ue, t) mtx_assert((_ue)->ue_mtx, t) + +MODULE_DEPEND(uether, usb, 1, 1, 1); +MODULE_DEPEND(uether, miibus, 1, 1, 1); + +static struct unrhdr *ueunit; + +static usb_proc_callback_t ue_attach_post_task; +static usb_proc_callback_t ue_promisc_task; +static usb_proc_callback_t ue_setmulti_task; +static usb_proc_callback_t ue_ifmedia_task; +static usb_proc_callback_t ue_tick_task; +static usb_proc_callback_t ue_start_task; +static usb_proc_callback_t ue_stop_task; + +static void ue_init(void *); +static void ue_start(struct ifnet *); +static int ue_ifmedia_upd(struct ifnet *); +static void ue_watchdog(void *); + +/* + * Return values: + * 0: success + * Else: device has been detached + */ +uint8_t +uether_pause(struct usb_ether *ue, unsigned int _ticks) +{ + if (usb_proc_is_gone(&ue->ue_tq)) { + /* nothing to do */ + return (1); + } + usb_pause_mtx(ue->ue_mtx, _ticks); + return (0); +} + +static void +ue_queue_command(struct usb_ether *ue, + usb_proc_callback_t *fn, + struct usb_proc_msg *t0, struct usb_proc_msg *t1) +{ + struct usb_ether_cfg_task *task; + + UE_LOCK_ASSERT(ue, MA_OWNED); + + if (usb_proc_is_gone(&ue->ue_tq)) { + return; /* nothing to do */ + } + /* + * NOTE: The task cannot get executed before we drop the + * "sc_mtx" mutex. It is safe to update fields in the message + * structure after that the message got queued. + */ + task = (struct usb_ether_cfg_task *) + usb_proc_msignal(&ue->ue_tq, t0, t1); + + /* Setup callback and self pointers */ + task->hdr.pm_callback = fn; + task->ue = ue; + + /* + * Start and stop must be synchronous! + */ + if ((fn == ue_start_task) || (fn == ue_stop_task)) + usb_proc_mwait(&ue->ue_tq, t0, t1); +} + +struct ifnet * +uether_getifp(struct usb_ether *ue) +{ + return (ue->ue_ifp); +} + +struct mii_data * +uether_getmii(struct usb_ether *ue) +{ + return (device_get_softc(ue->ue_miibus)); +} + +void * +uether_getsc(struct usb_ether *ue) +{ + return (ue->ue_sc); +} + +static int +ue_sysctl_parent(SYSCTL_HANDLER_ARGS) +{ + struct usb_ether *ue = arg1; + const char *name; + + name = device_get_nameunit(ue->ue_dev); + return SYSCTL_OUT(req, name, strlen(name)); +} + +int +uether_ifattach(struct usb_ether *ue) +{ + int error; + + /* check some critical parameters */ + if ((ue->ue_dev == NULL) || + (ue->ue_udev == NULL) || + (ue->ue_mtx == NULL) || + (ue->ue_methods == NULL)) + return (EINVAL); + + error = usb_proc_create(&ue->ue_tq, ue->ue_mtx, + device_get_nameunit(ue->ue_dev), USB_PRI_MED); + if (error) { + device_printf(ue->ue_dev, "could not setup taskqueue\n"); + goto error; + } + + /* fork rest of the attach code */ + UE_LOCK(ue); + ue_queue_command(ue, ue_attach_post_task, + &ue->ue_sync_task[0].hdr, + &ue->ue_sync_task[1].hdr); + UE_UNLOCK(ue); + +error: + return (error); +} + +static void +ue_attach_post_task(struct usb_proc_msg *_task) +{ + struct usb_ether_cfg_task *task = + (struct usb_ether_cfg_task *)_task; + struct usb_ether *ue = task->ue; + struct ifnet *ifp; + int error; + char num[14]; /* sufficient for 32 bits */ + + /* first call driver's post attach routine */ + ue->ue_methods->ue_attach_post(ue); + + UE_UNLOCK(ue); + + ue->ue_unit = alloc_unr(ueunit); + usb_callout_init_mtx(&ue->ue_watchdog, ue->ue_mtx, 0); + sysctl_ctx_init(&ue->ue_sysctl_ctx); + + error = 0; + ifp = if_alloc(IFT_ETHER); + if (ifp == NULL) { + device_printf(ue->ue_dev, "could not allocate ifnet\n"); + goto fail; + } + + ifp->if_softc = ue; + if_initname(ifp, "ue", ue->ue_unit); + if (ue->ue_methods->ue_attach_post_sub != NULL) { + ue->ue_ifp = ifp; + error = ue->ue_methods->ue_attach_post_sub(ue); + } else { + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + if (ue->ue_methods->ue_ioctl != NULL) + ifp->if_ioctl = ue->ue_methods->ue_ioctl; + else + ifp->if_ioctl = uether_ioctl; + ifp->if_start = ue_start; + ifp->if_init = ue_init; + IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); + ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; + IFQ_SET_READY(&ifp->if_snd); + ue->ue_ifp = ifp; + + if (ue->ue_methods->ue_mii_upd != NULL && + ue->ue_methods->ue_mii_sts != NULL) { + /* device_xxx() depends on this */ + mtx_lock(&Giant); + error = mii_attach(ue->ue_dev, &ue->ue_miibus, ifp, + ue_ifmedia_upd, ue->ue_methods->ue_mii_sts, + BMSR_DEFCAPMASK, MII_PHY_ANY, MII_OFFSET_ANY, 0); + mtx_unlock(&Giant); + } + } + + if (error) { + device_printf(ue->ue_dev, "attaching PHYs failed\n"); + goto fail; + } + + if_printf(ifp, " on %s\n", device_get_nameunit(ue->ue_dev)); + ether_ifattach(ifp, ue->ue_eaddr); + /* Tell upper layer we support VLAN oversized frames. */ + if (ifp->if_capabilities & IFCAP_VLAN_MTU) + ifp->if_hdrlen = sizeof(struct ether_vlan_header); + + snprintf(num, sizeof(num), "%u", ue->ue_unit); + ue->ue_sysctl_oid = SYSCTL_ADD_NODE(&ue->ue_sysctl_ctx, + &SYSCTL_NODE_CHILDREN(_net, ue), + OID_AUTO, num, CTLFLAG_RD, NULL, ""); + SYSCTL_ADD_PROC(&ue->ue_sysctl_ctx, + SYSCTL_CHILDREN(ue->ue_sysctl_oid), OID_AUTO, + "%parent", CTLTYPE_STRING | CTLFLAG_RD, ue, 0, + ue_sysctl_parent, "A", "parent device"); + + UE_LOCK(ue); + return; + +fail: + free_unr(ueunit, ue->ue_unit); + if (ue->ue_ifp != NULL) { + if_free(ue->ue_ifp); + ue->ue_ifp = NULL; + } + UE_LOCK(ue); + return; +} + +void +uether_ifdetach(struct usb_ether *ue) +{ + struct ifnet *ifp; + + /* wait for any post attach or other command to complete */ + usb_proc_drain(&ue->ue_tq); + + /* read "ifnet" pointer after taskqueue drain */ + ifp = ue->ue_ifp; + + if (ifp != NULL) { + + /* we are not running any more */ + UE_LOCK(ue); + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + UE_UNLOCK(ue); + + /* drain any callouts */ + usb_callout_drain(&ue->ue_watchdog); + + /* detach miibus */ + if (ue->ue_miibus != NULL) { + mtx_lock(&Giant); /* device_xxx() depends on this */ + device_delete_child(ue->ue_dev, ue->ue_miibus); + mtx_unlock(&Giant); + } + + /* detach ethernet */ + ether_ifdetach(ifp); + + /* free interface instance */ + if_free(ifp); + + /* free sysctl */ + sysctl_ctx_free(&ue->ue_sysctl_ctx); + + /* free unit */ + free_unr(ueunit, ue->ue_unit); + } + + /* free taskqueue, if any */ + usb_proc_free(&ue->ue_tq); +} + +uint8_t +uether_is_gone(struct usb_ether *ue) +{ + return (usb_proc_is_gone(&ue->ue_tq)); +} + +void +uether_init(void *arg) +{ + + ue_init(arg); +} + +static void +ue_init(void *arg) +{ + struct usb_ether *ue = arg; + + UE_LOCK(ue); + ue_queue_command(ue, ue_start_task, + &ue->ue_sync_task[0].hdr, + &ue->ue_sync_task[1].hdr); + UE_UNLOCK(ue); +} + +static void +ue_start_task(struct usb_proc_msg *_task) +{ + struct usb_ether_cfg_task *task = + (struct usb_ether_cfg_task *)_task; + struct usb_ether *ue = task->ue; + struct ifnet *ifp = ue->ue_ifp; + + UE_LOCK_ASSERT(ue, MA_OWNED); + + ue->ue_methods->ue_init(ue); + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + if (ue->ue_methods->ue_tick != NULL) + usb_callout_reset(&ue->ue_watchdog, hz, ue_watchdog, ue); +} + +static void +ue_stop_task(struct usb_proc_msg *_task) +{ + struct usb_ether_cfg_task *task = + (struct usb_ether_cfg_task *)_task; + struct usb_ether *ue = task->ue; + + UE_LOCK_ASSERT(ue, MA_OWNED); + + usb_callout_stop(&ue->ue_watchdog); + + ue->ue_methods->ue_stop(ue); +} + +void +uether_start(struct ifnet *ifp) +{ + + ue_start(ifp); +} + +static void +ue_start(struct ifnet *ifp) +{ + struct usb_ether *ue = ifp->if_softc; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + UE_LOCK(ue); + ue->ue_methods->ue_start(ue); + UE_UNLOCK(ue); +} + +static void +ue_promisc_task(struct usb_proc_msg *_task) +{ + struct usb_ether_cfg_task *task = + (struct usb_ether_cfg_task *)_task; + struct usb_ether *ue = task->ue; + + ue->ue_methods->ue_setpromisc(ue); +} + +static void +ue_setmulti_task(struct usb_proc_msg *_task) +{ + struct usb_ether_cfg_task *task = + (struct usb_ether_cfg_task *)_task; + struct usb_ether *ue = task->ue; + + ue->ue_methods->ue_setmulti(ue); +} + +int +uether_ifmedia_upd(struct ifnet *ifp) +{ + + return (ue_ifmedia_upd(ifp)); +} + +static int +ue_ifmedia_upd(struct ifnet *ifp) +{ + struct usb_ether *ue = ifp->if_softc; + + /* Defer to process context */ + UE_LOCK(ue); + ue_queue_command(ue, ue_ifmedia_task, + &ue->ue_media_task[0].hdr, + &ue->ue_media_task[1].hdr); + UE_UNLOCK(ue); + + return (0); +} + +static void +ue_ifmedia_task(struct usb_proc_msg *_task) +{ + struct usb_ether_cfg_task *task = + (struct usb_ether_cfg_task *)_task; + struct usb_ether *ue = task->ue; + struct ifnet *ifp = ue->ue_ifp; + + ue->ue_methods->ue_mii_upd(ifp); +} + +static void +ue_watchdog(void *arg) +{ + struct usb_ether *ue = arg; + struct ifnet *ifp = ue->ue_ifp; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + ue_queue_command(ue, ue_tick_task, + &ue->ue_tick_task[0].hdr, + &ue->ue_tick_task[1].hdr); + + usb_callout_reset(&ue->ue_watchdog, hz, ue_watchdog, ue); +} + +static void +ue_tick_task(struct usb_proc_msg *_task) +{ + struct usb_ether_cfg_task *task = + (struct usb_ether_cfg_task *)_task; + struct usb_ether *ue = task->ue; + struct ifnet *ifp = ue->ue_ifp; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + ue->ue_methods->ue_tick(ue); +} + +int +uether_ioctl(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct usb_ether *ue = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *)data; + struct mii_data *mii; + int error = 0; + + switch (command) { + case SIOCSIFFLAGS: + UE_LOCK(ue); + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ue_queue_command(ue, ue_promisc_task, + &ue->ue_promisc_task[0].hdr, + &ue->ue_promisc_task[1].hdr); + else + ue_queue_command(ue, ue_start_task, + &ue->ue_sync_task[0].hdr, + &ue->ue_sync_task[1].hdr); + } else { + ue_queue_command(ue, ue_stop_task, + &ue->ue_sync_task[0].hdr, + &ue->ue_sync_task[1].hdr); + } + UE_UNLOCK(ue); + break; + case SIOCADDMULTI: + case SIOCDELMULTI: + UE_LOCK(ue); + ue_queue_command(ue, ue_setmulti_task, + &ue->ue_multi_task[0].hdr, + &ue->ue_multi_task[1].hdr); + UE_UNLOCK(ue); + break; + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + if (ue->ue_miibus != NULL) { + mii = device_get_softc(ue->ue_miibus); + error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); + } else + error = ether_ioctl(ifp, command, data); + break; + default: + error = ether_ioctl(ifp, command, data); + break; + } + return (error); +} + +static int +uether_modevent(module_t mod, int type, void *data) +{ + + switch (type) { + case MOD_LOAD: + ueunit = new_unrhdr(0, INT_MAX, NULL); + break; + case MOD_UNLOAD: + break; + default: + return (EOPNOTSUPP); + } + return (0); +} +static moduledata_t uether_mod = { + "uether", + uether_modevent, + 0 +}; + +struct mbuf * +uether_newbuf(void) +{ + struct mbuf *m_new; + + m_new = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (m_new == NULL) + return (NULL); + m_new->m_len = m_new->m_pkthdr.len = MCLBYTES; + + m_adj(m_new, ETHER_ALIGN); + return (m_new); +} + +int +uether_rxmbuf(struct usb_ether *ue, struct mbuf *m, + unsigned int len) +{ + struct ifnet *ifp = ue->ue_ifp; + + UE_LOCK_ASSERT(ue, MA_OWNED); + + /* finalize mbuf */ + ifp->if_ipackets++; + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = len; + + /* enqueue for later when the lock can be released */ + _IF_ENQUEUE(&ue->ue_rxq, m); + return (0); +} + +int +uether_rxbuf(struct usb_ether *ue, struct usb_page_cache *pc, + unsigned int offset, unsigned int len) +{ + struct ifnet *ifp = ue->ue_ifp; + struct mbuf *m; + + UE_LOCK_ASSERT(ue, MA_OWNED); + + if (len < ETHER_HDR_LEN || len > MCLBYTES - ETHER_ALIGN) + return (1); + + m = uether_newbuf(); + if (m == NULL) { + ifp->if_iqdrops++; + return (ENOMEM); + } + + usbd_copy_out(pc, offset, mtod(m, uint8_t *), len); + + /* finalize mbuf */ + ifp->if_ipackets++; + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = len; + + /* enqueue for later when the lock can be released */ + _IF_ENQUEUE(&ue->ue_rxq, m); + return (0); +} + +void +uether_rxflush(struct usb_ether *ue) +{ + struct ifnet *ifp = ue->ue_ifp; + struct mbuf *m; + + UE_LOCK_ASSERT(ue, MA_OWNED); + + for (;;) { + _IF_DEQUEUE(&ue->ue_rxq, m); + if (m == NULL) + break; + + /* + * The USB xfer has been resubmitted so its safe to unlock now. + */ + UE_UNLOCK(ue); + ifp->if_input(ifp, m); + UE_LOCK(ue); + } +} + +DECLARE_MODULE(uether, uether_mod, SI_SUB_PSEUDO, SI_ORDER_ANY); +MODULE_VERSION(uether, 1); diff --git a/sys/bus/u4b/net/usb_ethernet.h b/sys/bus/u4b/net/usb_ethernet.h new file mode 100644 index 0000000000..af7cad437f --- /dev/null +++ b/sys/bus/u4b/net/usb_ethernet.h @@ -0,0 +1,127 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _USB_ETHERNET_H_ +#define _USB_ETHERNET_H_ + +#include "opt_inet.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "miibus_if.h" + +#include +#include + +struct usb_ether; +struct usb_device_request; + +typedef void (uether_fn_t)(struct usb_ether *); + +struct usb_ether_methods { + uether_fn_t *ue_attach_post; + uether_fn_t *ue_start; + uether_fn_t *ue_init; + uether_fn_t *ue_stop; + uether_fn_t *ue_setmulti; + uether_fn_t *ue_setpromisc; + uether_fn_t *ue_tick; + int (*ue_mii_upd)(struct ifnet *); + void (*ue_mii_sts)(struct ifnet *, + struct ifmediareq *); + int (*ue_ioctl)(struct ifnet *, u_long, caddr_t); + int (*ue_attach_post_sub)(struct usb_ether *); +}; + +struct usb_ether_cfg_task { + struct usb_proc_msg hdr; + struct usb_ether *ue; +}; + +struct usb_ether { + /* NOTE: the "ue_ifp" pointer must be first --hps */ + struct ifnet *ue_ifp; + struct mtx *ue_mtx; + const struct usb_ether_methods *ue_methods; + struct sysctl_oid *ue_sysctl_oid; + void *ue_sc; + struct usb_device *ue_udev; /* used by uether_do_request() */ + device_t ue_dev; + device_t ue_miibus; + + struct usb_process ue_tq; + struct sysctl_ctx_list ue_sysctl_ctx; + struct ifqueue ue_rxq; + struct usb_callout ue_watchdog; + struct usb_ether_cfg_task ue_sync_task[2]; + struct usb_ether_cfg_task ue_media_task[2]; + struct usb_ether_cfg_task ue_multi_task[2]; + struct usb_ether_cfg_task ue_promisc_task[2]; + struct usb_ether_cfg_task ue_tick_task[2]; + + int ue_unit; + + /* ethernet address from eeprom */ + uint8_t ue_eaddr[ETHER_ADDR_LEN]; +}; + +#define uether_do_request(ue,req,data,timo) \ + usbd_do_request_proc((ue)->ue_udev,&(ue)->ue_tq,req,data,0,NULL,timo) + +uint8_t uether_pause(struct usb_ether *, unsigned int); +struct ifnet *uether_getifp(struct usb_ether *); +struct mii_data *uether_getmii(struct usb_ether *); +void *uether_getsc(struct usb_ether *); +int uether_ifattach(struct usb_ether *); +void uether_ifdetach(struct usb_ether *); +int uether_ifmedia_upd(struct ifnet *); +void uether_init(void *); +int uether_ioctl(struct ifnet *, u_long, caddr_t); +struct mbuf *uether_newbuf(void); +int uether_rxmbuf(struct usb_ether *, struct mbuf *, + unsigned int); +int uether_rxbuf(struct usb_ether *, + struct usb_page_cache *, + unsigned int, unsigned int); +void uether_rxflush(struct usb_ether *); +uint8_t uether_is_gone(struct usb_ether *); +void uether_start(struct ifnet *); +#endif /* _USB_ETHERNET_H_ */ diff --git a/sys/bus/u4b/quirk/usb_quirk.c b/sys/bus/u4b/quirk/usb_quirk.c new file mode 100644 index 0000000000..e07aa2da35 --- /dev/null +++ b/sys/bus/u4b/quirk/usb_quirk.c @@ -0,0 +1,811 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR usb_debug +#include +#include + +#include + +MODULE_DEPEND(usb_quirk, usb, 1, 1, 1); +MODULE_VERSION(usb_quirk, 1); + +#define USB_DEV_QUIRKS_MAX 256 +#define USB_SUB_QUIRKS_MAX 8 + +struct usb_quirk_entry { + uint16_t vid; + uint16_t pid; + uint16_t lo_rev; + uint16_t hi_rev; + uint16_t quirks[USB_SUB_QUIRKS_MAX]; +}; + +static struct mtx usb_quirk_mtx; + +#define USB_QUIRK_VP(v,p,l,h,...) \ + { .vid = (v), .pid = (p), .lo_rev = (l), .hi_rev = (h), \ + .quirks = { __VA_ARGS__ } } +#define USB_QUIRK(v,p,l,h,...) \ + USB_QUIRK_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, l, h, __VA_ARGS__) + +static struct usb_quirk_entry usb_quirks[USB_DEV_QUIRKS_MAX] = { + USB_QUIRK(ASUS, LCM, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(INSIDEOUT, EDGEPORT4, 0x094, 0x094, UQ_SWAP_UNICODE), + USB_QUIRK(DALLAS, J6502, 0x0a2, 0x0a2, UQ_BAD_ADC), + USB_QUIRK(DALLAS, J6502, 0x0a2, 0x0a2, UQ_AU_NO_XU), + USB_QUIRK(ALTEC, ADA70, 0x103, 0x103, UQ_BAD_ADC), + USB_QUIRK(ALTEC, ASC495, 0x000, 0x000, UQ_BAD_AUDIO), + USB_QUIRK(QTRONIX, 980N, 0x110, 0x110, UQ_SPUR_BUT_UP), + USB_QUIRK(ALCOR2, KBD_HUB, 0x001, 0x001, UQ_SPUR_BUT_UP), + USB_QUIRK(MCT, HUB0100, 0x102, 0x102, UQ_BUS_POWERED), + USB_QUIRK(MCT, USB232, 0x102, 0x102, UQ_BUS_POWERED), + USB_QUIRK(TI, UTUSB41, 0x110, 0x110, UQ_POWER_CLAIM), + USB_QUIRK(TELEX, MIC1, 0x009, 0x009, UQ_AU_NO_FRAC), + USB_QUIRK(SILICONPORTALS, YAPPHONE, 0x100, 0x100, UQ_AU_INP_ASYNC), + USB_QUIRK(LOGITECH, UN53B, 0x0000, 0xffff, UQ_NO_STRINGS), + USB_QUIRK(ELSA, MODEM1, 0x0000, 0xffff, UQ_CFG_INDEX_1), + /* Quirks for printer devices */ + USB_QUIRK(HP, 895C, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + USB_QUIRK(HP, 880C, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + USB_QUIRK(HP, 815C, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + USB_QUIRK(HP, 810C, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + USB_QUIRK(HP, 830C, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + USB_QUIRK(HP, 1220C, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + USB_QUIRK(XEROX, WCM15, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + /* Devices which should be ignored by uhid */ + USB_QUIRK(APC, UPS, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(BELKIN, F6C550AVR, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(CYBERPOWER, 1500CAVRLCD, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(CYPRESS, SILVERSHIELD, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(DELORME, EARTHMATE, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(ITUNERNET, USBLCD2X20, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(ITUNERNET, USBLCD4X20, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(LIEBERT, POWERSURE_PXT, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(MGE, UPS1, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(MGE, UPS2, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(APPLE, IPHONE, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(APPLE, IPHONE_3G, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(MEGATEC, UPS, 0x0000, 0xffff, UQ_HID_IGNORE), + /* Devices which should be ignored by both ukbd and uhid */ + USB_QUIRK(CYPRESS, WISPY1A, 0x0000, 0xffff, UQ_KBD_IGNORE, UQ_HID_IGNORE), + USB_QUIRK(METAGEEK, WISPY1B, 0x0000, 0xffff, UQ_KBD_IGNORE, UQ_HID_IGNORE), + USB_QUIRK(METAGEEK, WISPY24X, 0x0000, 0xffff, UQ_KBD_IGNORE, UQ_HID_IGNORE), + USB_QUIRK(METAGEEK2, WISPYDBX, 0x0000, 0xffff, UQ_KBD_IGNORE, UQ_HID_IGNORE), + USB_QUIRK(TENX, UAUDIO0, 0x0101, 0x0101, UQ_AUDIO_SWAP_LR), + /* MS keyboards do weird things */ + USB_QUIRK(MICROSOFT, WLINTELLIMOUSE, 0x0000, 0xffff, UQ_MS_LEADING_BYTE), + /* umodem(4) device quirks */ + USB_QUIRK(METRICOM, RICOCHET_GS, 0x100, 0x100, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(SANYO, SCP4900, 0x000, 0x000, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(MOTOROLA2, T720C, 0x001, 0x001, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(EICON, DIVA852, 0x100, 0x100, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(SIEMENS2, ES75, 0x000, 0x000, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(QUALCOMM, CDMA_MSM, 0x0000, 0xffff, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(QUALCOMM2, CDMA_MSM, 0x0000, 0xffff, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(CURITEL, UM150, 0x0000, 0xffff, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(CURITEL, UM175, 0x0000, 0xffff, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(VERTEX, VW110L, 0x0000, 0xffff, UQ_ASSUME_CM_OVER_DATA), + + /* USB Mass Storage Class Quirks */ + USB_QUIRK_VP(USB_VENDOR_ASAHIOPTICAL, 0, UQ_MSC_NO_RS_CLEAR_UA, + UQ_MATCH_VENDOR_ONLY), + USB_QUIRK(ADDON, ATTACHE, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(ADDON, A256MB, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(ADDON, DISKPRO512, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(ADDONICS2, CABLE_205, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(AIPTEK, POCKETCAM3M, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(ALCOR, UMCR_9361, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(ALCOR, TRANSCEND, 0x0000, 0xffff, UQ_MSC_NO_GETMAXLUN, + UQ_MSC_NO_SYNC_CACHE, UQ_MSC_NO_TEST_UNIT_READY), + USB_QUIRK(APACER, HT202, 0x0000, 0xffff, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(ASAHIOPTICAL, OPTIO230, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(ASAHIOPTICAL, OPTIO330, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(BELKIN, USB2SCSI, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(CASIO, QV_DIGICAM, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(CCYU, ED1064, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(CENTURY, EX35QUAT, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(CYPRESS, XX6830XX, 0x0000, 0xffff, UQ_MSC_NO_GETMAXLUN, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(DESKNOTE, UCR_61S2B, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(DMI, CFSM_RW, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI, + UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(EPSON, STYLUS_875DC, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(EPSON, STYLUS_895, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(FEIYA, 5IN1, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(FREECOM, DVD, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(FUJIPHOTO, MASS0100, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_RS_CLEAR_UA, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(GENESYS, GL641USB2IDE, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(GENESYS, GL641USB2IDE_2, 0x0000, 0xffff, + UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_ATAPI, + UQ_MSC_FORCE_SHORT_INQ, UQ_MSC_NO_START_STOP, + UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(GENESYS, GL641USB, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(GENESYS, GL641USB_2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_WRONG_CSWSIG), + USB_QUIRK(HAGIWARA, FG, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(HAGIWARA, FGSM, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(HITACHI, DVDCAM_DZ_MV100A, 0x0000, 0xffff, + UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_SCSI, + UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(HITACHI, DVDCAM_USB, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(HP, CDW4E, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(HP, CDW8200, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_START_STOP), + USB_QUIRK(IMAGINATION, DBX1, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_WRONG_CSWSIG), + USB_QUIRK(INSYSTEM, USBCABLE, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_START_STOP, UQ_MSC_ALT_IFACE_1), + USB_QUIRK(INSYSTEM, ATAPI, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(INSYSTEM, STORAGE_V2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(IODATA, IU_CD2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(IODATA, DVR_UEH8, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(IOMEGA, ZIP100, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, + UQ_MSC_NO_TEST_UNIT_READY), /* XXX ZIP drives can also use ATAPI */ + USB_QUIRK(JMICRON, JM20337, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(KYOCERA, FINECAM_L3, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(KYOCERA, FINECAM_S3X, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(KYOCERA, FINECAM_S4, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(KYOCERA, FINECAM_S5, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(LACIE, HD, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(LEXAR, CF_READER, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(LEXAR, JUMPSHOT, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(LOGITEC, LDR_H443SU2, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(LOGITEC, LDR_H443U2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI,), + USB_QUIRK(MELCO, DUBPXXG, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(MICROTECH, DPCM, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_START_STOP), + USB_QUIRK(MICRON, REALSSD, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(MICROTECH, SCSIDB25, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(MICROTECH, SCSIHD50, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(MINOLTA, E223, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(MINOLTA, F300, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(MITSUMI, CDRRW, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI | + UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(MOTOROLA2, E398, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_INQUIRY_EVPD, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK_VP(USB_VENDOR_MPMAN, 0, UQ_MSC_NO_SYNC_CACHE, + UQ_MATCH_VENDOR_ONLY), + USB_QUIRK(MSYSTEMS, DISKONKEY, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE, UQ_MSC_NO_GETMAXLUN, + UQ_MSC_NO_RS_CLEAR_UA), + USB_QUIRK(MSYSTEMS, DISKONKEY2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(MYSON, HEDEN, 0x0000, 0xffff, UQ_MSC_IGNORE_RESIDUE, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(NEODIO, ND3260, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ), + USB_QUIRK(NETAC, CF_CARD, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(NETAC, ONLYDISK, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(NETCHIP, CLIK_40, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_ATAPI, + UQ_MSC_NO_INQUIRY), + USB_QUIRK(NIKON, D300, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(OLYMPUS, C1, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_WRONG_CSWSIG), + USB_QUIRK(OLYMPUS, C700, 0x0000, 0xffff, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(ONSPEC, SDS_HOTFIND_D, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(ONSPEC, CFMS_RW, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(ONSPEC, CFSM_COMBO, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(ONSPEC, CFSM_READER, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(ONSPEC, CFSM_READER2, 0x0000, 0xffff, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(ONSPEC, MDCFE_B_CF_READER, 0x0000, 0xffff, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(ONSPEC, MDSM_B_READER, 0x0000, 0xffff, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(ONSPEC, READER, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(ONSPEC, UCF100, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY | UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(ONSPEC2, IMAGEMATE_SDDR55, 0x0000, 0xffff, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(PANASONIC, KXL840AN, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(PANASONIC, KXLCB20AN, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(PANASONIC, KXLCB35AN, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(PANASONIC, LS120CAM, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_UFI), + USB_QUIRK(PLEXTOR, 40_12_40U, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_TEST_UNIT_READY), + USB_QUIRK(PNY, ATTACHE2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE, + UQ_MSC_NO_START_STOP), + USB_QUIRK(PROLIFIC, PL2506, 0x0000, 0xffff, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK_VP(USB_VENDOR_SAMSUNG_TECHWIN, + USB_PRODUCT_SAMSUNG_TECHWIN_DIGIMAX_410, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(SANDISK, SDDR05A, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_READ_CAP_OFFBY1, + UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SANDISK, SDDR09, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI, + UQ_MSC_READ_CAP_OFFBY1, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SANDISK, SDDR12, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_READ_CAP_OFFBY1, + UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SANDISK, SDCZ2_256, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(SANDISK, SDCZ4_128, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(SANDISK, SDCZ4_256, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(SANDISK, SDDR31, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_READ_CAP_OFFBY1), + USB_QUIRK(SCANLOGIC, SL11R, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(SHUTTLE, EUSB, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_START_STOP, UQ_MSC_SHUTTLE_INIT), + USB_QUIRK(SHUTTLE, CDRW, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(SHUTTLE, CF, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(SHUTTLE, EUSBATAPI, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(SHUTTLE, EUSBCFSM, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(SHUTTLE, EUSCSI, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(SHUTTLE, HIFD, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SHUTTLE, SDDR09, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI, + UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SHUTTLE, ZIOMMC, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SIGMATEL, I_BEAD100, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_SHUTTLE_INIT), + USB_QUIRK(SIIG, WINTERREADER, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(SKANHEX, MD_7425, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(SKANHEX, SX_520Z, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(SONY, HANDYCAM, 0x0500, 0x0500, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC, UQ_MSC_RBC_PAD_TO_12), + USB_QUIRK(SONY, CLIE_40_MS, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(SONY, DSC, 0x0500, 0x0500, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC, UQ_MSC_RBC_PAD_TO_12), + USB_QUIRK(SONY, DSC, 0x0600, 0x0600, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC, UQ_MSC_RBC_PAD_TO_12), + USB_QUIRK(SONY, DSC, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(SONY, HANDYCAM, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(SONY, MSC, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(SONY, MS_MSC_U03, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_UFI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SONY, MS_NW_MS7, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SONY, MS_PEG_N760C, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(SONY, MSACUS1, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SONY, PORTABLE_HDD_V2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(SUPERTOP, IDE, 0x0000, 0xffff, UQ_MSC_IGNORE_RESIDUE, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(TAUGA, CAMERAMATE, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(TEAC, FD05PUB, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_UFI), + USB_QUIRK(TECLAST, TLC300, 0x0000, 0xffff, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(TREK, MEMKEY, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(TREK, THUMBDRIVE_8MB, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(TRUMPION, C3310, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_UFI), + USB_QUIRK(TRUMPION, MP3, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(TRUMPION, T33520, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(TWINMOS, MDIV, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(VIA, USB2IDEBRIDGE, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(VIVITAR, 35XX, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(WESTERN, COMBO, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(WESTERN, EXTHDD, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(WESTERN, MYBOOK, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY_EVPD, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(WESTERN, MYPASSWORD, 0x0000, 0xffff, UQ_MSC_FORCE_SHORT_INQ), + USB_QUIRK(WINMAXGROUP, FLASH64MC, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(YANO, FW800HD, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(YANO, U640MO, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_FORCE_SHORT_INQ), + USB_QUIRK(YEDATA, FLASHBUSTERU, 0x0000, 0x007F, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_UFI, UQ_MSC_NO_RS_CLEAR_UA, UQ_MSC_FLOPPY_SPEED, + UQ_MSC_NO_TEST_UNIT_READY, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(YEDATA, FLASHBUSTERU, 0x0080, 0x0080, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_UFI, UQ_MSC_NO_RS_CLEAR_UA, UQ_MSC_FLOPPY_SPEED, + UQ_MSC_NO_TEST_UNIT_READY, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(YEDATA, FLASHBUSTERU, 0x0081, 0xFFFF, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_UFI, UQ_MSC_NO_RS_CLEAR_UA, UQ_MSC_FLOPPY_SPEED, + UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(ZORAN, EX20DSC, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(MEIZU, M6_SL, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY, UQ_MSC_NO_SYNC_CACHE), + + /* Non-standard USB MIDI devices */ + USB_QUIRK(ROLAND, UM1, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, SC8850, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, SD90, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, UM880N, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, UA100, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, UM4, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, U8, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, UM2, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, SC8820, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, PC300, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, SK500, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, SCD70, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, UM550, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, SD20, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, SD80, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(ROLAND, UA700, 0x0000, 0xffff, UQ_AU_VENDOR_CLASS), + USB_QUIRK(MEDELI, DD305, 0x0000, 0xffff, UQ_SINGLE_CMD_MIDI, UQ_MATCH_VENDOR_ONLY), + + /* + * Quirks for manufacturers which USB devices does not respond + * after issuing non-supported commands: + */ + USB_QUIRK(ALCOR, DUMMY, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE, UQ_MSC_NO_TEST_UNIT_READY, UQ_MATCH_VENDOR_ONLY), + USB_QUIRK(FEIYA, DUMMY, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE, UQ_MATCH_VENDOR_ONLY), + USB_QUIRK(REALTEK, DUMMY, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE, UQ_MATCH_VENDOR_ONLY), + USB_QUIRK(INITIO, DUMMY, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE, UQ_MATCH_VENDOR_ONLY), +}; +#undef USB_QUIRK_VP +#undef USB_QUIRK + +static const char *usb_quirk_str[USB_QUIRK_MAX] = { + [UQ_NONE] = "UQ_NONE", + [UQ_MATCH_VENDOR_ONLY] = "UQ_MATCH_VENDOR_ONLY", + [UQ_AUDIO_SWAP_LR] = "UQ_AUDIO_SWAP_LR", + [UQ_AU_INP_ASYNC] = "UQ_AU_INP_ASYNC", + [UQ_AU_NO_FRAC] = "UQ_AU_NO_FRAC", + [UQ_AU_NO_XU] = "UQ_AU_NO_XU", + [UQ_BAD_ADC] = "UQ_BAD_ADC", + [UQ_BAD_AUDIO] = "UQ_BAD_AUDIO", + [UQ_BROKEN_BIDIR] = "UQ_BROKEN_BIDIR", + [UQ_BUS_POWERED] = "UQ_BUS_POWERED", + [UQ_HID_IGNORE] = "UQ_HID_IGNORE", + [UQ_KBD_IGNORE] = "UQ_KBD_IGNORE", + [UQ_KBD_BOOTPROTO] = "UQ_KBD_BOOTPROTO", + [UQ_MS_BAD_CLASS] = "UQ_MS_BAD_CLASS", + [UQ_MS_LEADING_BYTE] = "UQ_MS_LEADING_BYTE", + [UQ_MS_REVZ] = "UQ_MS_REVZ", + [UQ_NO_STRINGS] = "UQ_NO_STRINGS", + [UQ_OPEN_CLEARSTALL] = "UQ_OPEN_CLEARSTALL", + [UQ_POWER_CLAIM] = "UQ_POWER_CLAIM", + [UQ_SPUR_BUT_UP] = "UQ_SPUR_BUT_UP", + [UQ_SWAP_UNICODE] = "UQ_SWAP_UNICODE", + [UQ_CFG_INDEX_1] = "UQ_CFG_INDEX_1", + [UQ_CFG_INDEX_2] = "UQ_CFG_INDEX_2", + [UQ_CFG_INDEX_3] = "UQ_CFG_INDEX_3", + [UQ_CFG_INDEX_4] = "UQ_CFG_INDEX_4", + [UQ_CFG_INDEX_0] = "UQ_CFG_INDEX_0", + [UQ_ASSUME_CM_OVER_DATA] = "UQ_ASSUME_CM_OVER_DATA", + [UQ_MSC_NO_TEST_UNIT_READY] = "UQ_MSC_NO_TEST_UNIT_READY", + [UQ_MSC_NO_RS_CLEAR_UA] = "UQ_MSC_NO_RS_CLEAR_UA", + [UQ_MSC_NO_START_STOP] = "UQ_MSC_NO_START_STOP", + [UQ_MSC_NO_GETMAXLUN] = "UQ_MSC_NO_GETMAXLUN", + [UQ_MSC_NO_INQUIRY] = "UQ_MSC_NO_INQUIRY", + [UQ_MSC_NO_INQUIRY_EVPD] = "UQ_MSC_NO_INQUIRY_EVPD", + [UQ_MSC_NO_SYNC_CACHE] = "UQ_MSC_NO_SYNC_CACHE", + [UQ_MSC_SHUTTLE_INIT] = "UQ_MSC_SHUTTLE_INIT", + [UQ_MSC_ALT_IFACE_1] = "UQ_MSC_ALT_IFACE_1", + [UQ_MSC_FLOPPY_SPEED] = "UQ_MSC_FLOPPY_SPEED", + [UQ_MSC_IGNORE_RESIDUE] = "UQ_MSC_IGNORE_RESIDUE", + [UQ_MSC_WRONG_CSWSIG] = "UQ_MSC_WRONG_CSWSIG", + [UQ_MSC_RBC_PAD_TO_12] = "UQ_MSC_RBC_PAD_TO_12", + [UQ_MSC_READ_CAP_OFFBY1] = "UQ_MSC_READ_CAP_OFFBY1", + [UQ_MSC_FORCE_SHORT_INQ] = "UQ_MSC_FORCE_SHORT_INQ", + [UQ_MSC_FORCE_WIRE_BBB] = "UQ_MSC_FORCE_WIRE_BBB", + [UQ_MSC_FORCE_WIRE_CBI] = "UQ_MSC_FORCE_WIRE_CBI", + [UQ_MSC_FORCE_WIRE_CBI_I] = "UQ_MSC_FORCE_WIRE_CBI_I", + [UQ_MSC_FORCE_PROTO_SCSI] = "UQ_MSC_FORCE_PROTO_SCSI", + [UQ_MSC_FORCE_PROTO_ATAPI] = "UQ_MSC_FORCE_PROTO_ATAPI", + [UQ_MSC_FORCE_PROTO_UFI] = "UQ_MSC_FORCE_PROTO_UFI", + [UQ_MSC_FORCE_PROTO_RBC] = "UQ_MSC_FORCE_PROTO_RBC", + [UQ_MSC_EJECT_HUAWEI] = "UQ_MSC_EJECT_HUAWEI", + [UQ_MSC_EJECT_SIERRA] = "UQ_MSC_EJECT_SIERRA", + [UQ_MSC_EJECT_SCSIEJECT] = "UQ_MSC_EJECT_SCSIEJECT", + [UQ_MSC_EJECT_REZERO] = "UQ_MSC_EJECT_REZERO", + [UQ_MSC_EJECT_ZTESTOR] = "UQ_MSC_EJECT_ZTESTOR", + [UQ_MSC_EJECT_CMOTECH] = "UQ_MSC_EJECT_CMOTECH", + [UQ_MSC_EJECT_WAIT] = "UQ_MSC_EJECT_WAIT", + [UQ_MSC_EJECT_SAEL_M460] = "UQ_MSC_EJECT_SAEL_M460", + [UQ_MSC_EJECT_HUAWEISCSI] = "UQ_MSC_EJECT_HUAWEISCSI", + [UQ_MSC_EJECT_TCT] = "UQ_MSC_EJECT_TCT", + [UQ_BAD_MIDI] = "UQ_BAD_MIDI", + [UQ_AU_VENDOR_CLASS] = "UQ_AU_VENDOR_CLASS", + [UQ_SINGLE_CMD_MIDI] = "UQ_SINGLE_CMD_MIDI", +}; + +/*------------------------------------------------------------------------* + * usb_quirkstr + * + * This function converts an USB quirk code into a string. + *------------------------------------------------------------------------*/ +static const char * +usb_quirkstr(uint16_t quirk) +{ + return ((quirk < USB_QUIRK_MAX) ? + usb_quirk_str[quirk] : "USB_QUIRK_UNKNOWN"); +} + +/*------------------------------------------------------------------------* + * usb_test_quirk_by_info + * + * Returns: + * 0: Quirk not found + * Else: Quirk found + *------------------------------------------------------------------------*/ +static uint8_t +usb_test_quirk_by_info(const struct usbd_lookup_info *info, uint16_t quirk) +{ + uint16_t x; + uint16_t y; + + if (quirk == UQ_NONE) + goto done; + + mtx_lock(&usb_quirk_mtx); + + for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) { + /* see if quirk information does not match */ + if ((usb_quirks[x].vid != info->idVendor) || + (usb_quirks[x].lo_rev > info->bcdDevice) || + (usb_quirks[x].hi_rev < info->bcdDevice)) { + continue; + } + /* see if quirk only should match vendor ID */ + if (usb_quirks[x].pid != info->idProduct) { + if (usb_quirks[x].pid != 0) + continue; + + for (y = 0; y != USB_SUB_QUIRKS_MAX; y++) { + if (usb_quirks[x].quirks[y] == UQ_MATCH_VENDOR_ONLY) + break; + } + if (y == USB_SUB_QUIRKS_MAX) + continue; + } + /* lookup quirk */ + for (y = 0; y != USB_SUB_QUIRKS_MAX; y++) { + if (usb_quirks[x].quirks[y] == quirk) { + mtx_unlock(&usb_quirk_mtx); + DPRINTF("Found quirk '%s'.\n", usb_quirkstr(quirk)); + return (1); + } + } + /* no quirk found */ + break; + } + mtx_unlock(&usb_quirk_mtx); +done: + return (0); /* no quirk match */ +} + +static struct usb_quirk_entry * +usb_quirk_get_entry(uint16_t vid, uint16_t pid, + uint16_t lo_rev, uint16_t hi_rev, uint8_t do_alloc) +{ + uint16_t x; + + mtx_assert(&usb_quirk_mtx, MA_OWNED); + + if ((vid | pid | lo_rev | hi_rev) == 0) { + /* all zero - special case */ + return (usb_quirks + USB_DEV_QUIRKS_MAX - 1); + } + /* search for an existing entry */ + for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) { + /* see if quirk information does not match */ + if ((usb_quirks[x].vid != vid) || + (usb_quirks[x].pid != pid) || + (usb_quirks[x].lo_rev != lo_rev) || + (usb_quirks[x].hi_rev != hi_rev)) { + continue; + } + return (usb_quirks + x); + } + + if (do_alloc == 0) { + /* no match */ + return (NULL); + } + /* search for a free entry */ + for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) { + /* see if quirk information does not match */ + if ((usb_quirks[x].vid | + usb_quirks[x].pid | + usb_quirks[x].lo_rev | + usb_quirks[x].hi_rev) != 0) { + continue; + } + usb_quirks[x].vid = vid; + usb_quirks[x].pid = pid; + usb_quirks[x].lo_rev = lo_rev; + usb_quirks[x].hi_rev = hi_rev; + + return (usb_quirks + x); + } + + /* no entry found */ + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_quirk_ioctl - handle quirk IOCTLs + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usb_quirk_ioctl(unsigned long cmd, caddr_t data, + int fflag, struct thread *td) +{ + struct usb_gen_quirk *pgq; + struct usb_quirk_entry *pqe; + uint32_t x; + uint32_t y; + int err; + + switch (cmd) { + case USB_DEV_QUIRK_GET: + pgq = (void *)data; + x = pgq->index % USB_SUB_QUIRKS_MAX; + y = pgq->index / USB_SUB_QUIRKS_MAX; + if (y >= USB_DEV_QUIRKS_MAX) { + return (EINVAL); + } + mtx_lock(&usb_quirk_mtx); + /* copy out data */ + pgq->vid = usb_quirks[y].vid; + pgq->pid = usb_quirks[y].pid; + pgq->bcdDeviceLow = usb_quirks[y].lo_rev; + pgq->bcdDeviceHigh = usb_quirks[y].hi_rev; + strlcpy(pgq->quirkname, + usb_quirkstr(usb_quirks[y].quirks[x]), + sizeof(pgq->quirkname)); + mtx_unlock(&usb_quirk_mtx); + return (0); /* success */ + + case USB_QUIRK_NAME_GET: + pgq = (void *)data; + x = pgq->index; + if (x >= USB_QUIRK_MAX) { + return (EINVAL); + } + strlcpy(pgq->quirkname, + usb_quirkstr(x), sizeof(pgq->quirkname)); + return (0); /* success */ + + case USB_DEV_QUIRK_ADD: + pgq = (void *)data; + + /* check privileges */ + err = priv_check(curthread, PRIV_DRIVER); + if (err) { + return (err); + } + /* convert quirk string into numerical */ + for (y = 0; y != USB_DEV_QUIRKS_MAX; y++) { + if (strcmp(pgq->quirkname, usb_quirkstr(y)) == 0) { + break; + } + } + if (y == USB_DEV_QUIRKS_MAX) { + return (EINVAL); + } + if (y == UQ_NONE) { + return (EINVAL); + } + mtx_lock(&usb_quirk_mtx); + pqe = usb_quirk_get_entry(pgq->vid, pgq->pid, + pgq->bcdDeviceLow, pgq->bcdDeviceHigh, 1); + if (pqe == NULL) { + mtx_unlock(&usb_quirk_mtx); + return (EINVAL); + } + for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) { + if (pqe->quirks[x] == UQ_NONE) { + pqe->quirks[x] = y; + break; + } + } + mtx_unlock(&usb_quirk_mtx); + if (x == USB_SUB_QUIRKS_MAX) { + return (ENOMEM); + } + return (0); /* success */ + + case USB_DEV_QUIRK_REMOVE: + pgq = (void *)data; + /* check privileges */ + err = priv_check(curthread, PRIV_DRIVER); + if (err) { + return (err); + } + /* convert quirk string into numerical */ + for (y = 0; y != USB_DEV_QUIRKS_MAX; y++) { + if (strcmp(pgq->quirkname, usb_quirkstr(y)) == 0) { + break; + } + } + if (y == USB_DEV_QUIRKS_MAX) { + return (EINVAL); + } + if (y == UQ_NONE) { + return (EINVAL); + } + mtx_lock(&usb_quirk_mtx); + pqe = usb_quirk_get_entry(pgq->vid, pgq->pid, + pgq->bcdDeviceLow, pgq->bcdDeviceHigh, 0); + if (pqe == NULL) { + mtx_unlock(&usb_quirk_mtx); + return (EINVAL); + } + for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) { + if (pqe->quirks[x] == y) { + pqe->quirks[x] = UQ_NONE; + break; + } + } + if (x == USB_SUB_QUIRKS_MAX) { + mtx_unlock(&usb_quirk_mtx); + return (ENOMEM); + } + for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) { + if (pqe->quirks[x] != UQ_NONE) { + break; + } + } + if (x == USB_SUB_QUIRKS_MAX) { + /* all quirk entries are unused - release */ + memset(pqe, 0, sizeof(pqe)); + } + mtx_unlock(&usb_quirk_mtx); + return (0); /* success */ + + default: + break; + } + return (ENOIOCTL); +} + +static void +usb_quirk_init(void *arg) +{ + /* initialize mutex */ + mtx_init(&usb_quirk_mtx, "USB quirk", NULL, MTX_DEF); + + /* register our function */ + usb_test_quirk_p = &usb_test_quirk_by_info; + usb_quirk_ioctl_p = &usb_quirk_ioctl; +} + +static void +usb_quirk_uninit(void *arg) +{ + usb_quirk_unload(arg); + + /* destroy mutex */ + mtx_destroy(&usb_quirk_mtx); +} + +SYSINIT(usb_quirk_init, SI_SUB_LOCK, SI_ORDER_FIRST, usb_quirk_init, NULL); +SYSUNINIT(usb_quirk_uninit, SI_SUB_LOCK, SI_ORDER_ANY, usb_quirk_uninit, NULL); diff --git a/sys/bus/u4b/quirk/usb_quirk.h b/sys/bus/u4b/quirk/usb_quirk.h new file mode 100644 index 0000000000..e012842282 --- /dev/null +++ b/sys/bus/u4b/quirk/usb_quirk.h @@ -0,0 +1,112 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_QUIRK_H_ +#define _USB_QUIRK_H_ + +enum { + /* + * Keep in sync with theusb_quirk_str usb_quirk.c, and with the + * share/man/man4/usb_quirk.4 + */ + UQ_NONE, /* not a valid quirk */ + + UQ_MATCH_VENDOR_ONLY, /* match quirk on vendor only */ + + /* Various quirks */ + + UQ_AUDIO_SWAP_LR, /* left and right sound channels are swapped */ + UQ_AU_INP_ASYNC, /* input is async despite claim of adaptive */ + UQ_AU_NO_FRAC, /* don't adjust for fractional samples */ + UQ_AU_NO_XU, /* audio device has broken extension unit */ + UQ_BAD_ADC, /* bad audio spec version number */ + UQ_BAD_AUDIO, /* device claims audio class, but isn't */ + UQ_BROKEN_BIDIR, /* printer has broken bidir mode */ + UQ_BUS_POWERED, /* device is bus powered, despite claim */ + UQ_HID_IGNORE, /* device should be ignored by hid class */ + UQ_KBD_IGNORE, /* device should be ignored by kbd class */ + UQ_KBD_BOOTPROTO, /* device should set the boot protocol */ + UQ_MS_BAD_CLASS, /* doesn't identify properly */ + UQ_MS_LEADING_BYTE, /* mouse sends an unknown leading byte */ + UQ_MS_REVZ, /* mouse has Z-axis reversed */ + UQ_NO_STRINGS, /* string descriptors are broken */ + UQ_OPEN_CLEARSTALL, /* device needs clear endpoint stall */ + UQ_POWER_CLAIM, /* hub lies about power status */ + UQ_SPUR_BUT_UP, /* spurious mouse button up events */ + UQ_SWAP_UNICODE, /* has some Unicode strings swapped */ + UQ_CFG_INDEX_1, /* select configuration index 1 by default */ + UQ_CFG_INDEX_2, /* select configuration index 2 by default */ + UQ_CFG_INDEX_3, /* select configuration index 3 by default */ + UQ_CFG_INDEX_4, /* select configuration index 4 by default */ + UQ_CFG_INDEX_0, /* select configuration index 0 by default */ + UQ_ASSUME_CM_OVER_DATA, /* assume cm over data feature */ + + /* USB Mass Storage Quirks. See "storage/umass.c" for a detailed description. */ + UQ_MSC_NO_TEST_UNIT_READY, /* send start/stop instead of TUR */ + UQ_MSC_NO_RS_CLEAR_UA, /* does not reset Unit Att. */ + UQ_MSC_NO_START_STOP, /* does not support start/stop */ + UQ_MSC_NO_GETMAXLUN, /* does not support get max LUN */ + UQ_MSC_NO_INQUIRY, /* fake generic inq response */ + UQ_MSC_NO_INQUIRY_EVPD, /* does not support inq EVPD */ + UQ_MSC_NO_SYNC_CACHE, /* does not support sync cache */ + UQ_MSC_SHUTTLE_INIT, /* requires Shuttle init sequence */ + UQ_MSC_ALT_IFACE_1, /* switch to alternate interface 1 */ + UQ_MSC_FLOPPY_SPEED, /* does floppy speeds (20kb/s) */ + UQ_MSC_IGNORE_RESIDUE, /* gets residue wrong */ + UQ_MSC_WRONG_CSWSIG, /* uses wrong CSW signature */ + UQ_MSC_RBC_PAD_TO_12, /* pad RBC requests to 12 bytes */ + UQ_MSC_READ_CAP_OFFBY1, /* reports sector count, not max sec. */ + UQ_MSC_FORCE_SHORT_INQ, /* does not support full inq. */ + UQ_MSC_FORCE_WIRE_BBB, /* force BBB wire protocol */ + UQ_MSC_FORCE_WIRE_CBI, /* force CBI wire protocol */ + UQ_MSC_FORCE_WIRE_CBI_I, /* force CBI with int. wire protocol */ + UQ_MSC_FORCE_PROTO_SCSI, /* force SCSI command protocol */ + UQ_MSC_FORCE_PROTO_ATAPI, /* force ATAPI command protocol */ + UQ_MSC_FORCE_PROTO_UFI, /* force UFI command protocol */ + UQ_MSC_FORCE_PROTO_RBC, /* force RBC command protocol */ + + /* Ejection of mass storage (driver disk) */ + UQ_MSC_EJECT_HUAWEI, /* ejects after Huawei USB command */ + UQ_MSC_EJECT_SIERRA, /* ejects after Sierra USB command */ + UQ_MSC_EJECT_SCSIEJECT, /* ejects after SCSI eject command */ + UQ_MSC_EJECT_REZERO, /* ejects after SCSI rezero command */ + UQ_MSC_EJECT_ZTESTOR, /* ejects after ZTE SCSI command */ + UQ_MSC_EJECT_CMOTECH, /* ejects after C-motech SCSI cmd */ + UQ_MSC_EJECT_WAIT, /* wait for the device to eject */ + UQ_MSC_EJECT_SAEL_M460, /* ejects after Sael USB commands */ + UQ_MSC_EJECT_HUAWEISCSI, /* ejects after Huawei SCSI command */ + UQ_MSC_EJECT_TCT, /* ejects after TCT SCSI command */ + + UQ_BAD_MIDI, /* device claims MIDI class, but isn't */ + UQ_AU_VENDOR_CLASS, /* audio device uses vendor and not audio class */ + UQ_SINGLE_CMD_MIDI, /* at most one command per USB packet */ + + USB_QUIRK_MAX +}; + +uint8_t usb_test_quirk(const struct usb_attach_arg *uaa, uint16_t quirk); + +#endif /* _USB_QUIRK_H_ */ diff --git a/sys/bus/u4b/serial/u3g.c b/sys/bus/u4b/serial/u3g.c new file mode 100644 index 0000000000..dfbbe26d5a --- /dev/null +++ b/sys/bus/u4b/serial/u3g.c @@ -0,0 +1,988 @@ +/* + * Copyright (c) 2008 AnyWi Technologies + * Author: Andrea Guzzo + * * based on uark.c 1.1 2006/08/14 08:30:22 jsg * + * * parts from ubsa.c 183348 2008-09-25 12:00:56Z phk * + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $FreeBSD$ + */ + +/* + * NOTE: + * + * - The detour through the tty layer is ridiculously expensive wrt + * buffering due to the high speeds. + * + * We should consider adding a simple r/w device which allows + * attaching of PPP in a more efficient way. + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR u3g_debug +#include +#include +#include + +#include +#include + +#ifdef USB_DEBUG +static int u3g_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, u3g, CTLFLAG_RW, 0, "USB 3g"); +SYSCTL_INT(_hw_usb_u3g, OID_AUTO, debug, CTLFLAG_RW, + &u3g_debug, 0, "Debug level"); +#endif + +#define U3G_MAXPORTS 12 +#define U3G_CONFIG_INDEX 0 +#define U3G_BSIZE 2048 + +#define U3GSP_GPRS 0 +#define U3GSP_EDGE 1 +#define U3GSP_CDMA 2 +#define U3GSP_UMTS 3 +#define U3GSP_HSDPA 4 +#define U3GSP_HSUPA 5 +#define U3GSP_HSPA 6 +#define U3GSP_MAX 7 + +/* Eject methods; See also usb_quirks.h:UQ_MSC_EJECT_* */ +#define U3GINIT_HUAWEI 1 /* Requires Huawei init command */ +#define U3GINIT_SIERRA 2 /* Requires Sierra init command */ +#define U3GINIT_SCSIEJECT 3 /* Requires SCSI eject command */ +#define U3GINIT_REZERO 4 /* Requires SCSI rezero command */ +#define U3GINIT_ZTESTOR 5 /* Requires ZTE SCSI command */ +#define U3GINIT_CMOTECH 6 /* Requires CMOTECH SCSI command */ +#define U3GINIT_WAIT 7 /* Device reappears after a delay */ +#define U3GINIT_SAEL_M460 8 /* Requires vendor init */ +#define U3GINIT_HUAWEISCSI 9 /* Requires Huawei SCSI init command */ +#define U3GINIT_TCT 10 /* Requires TCT Mobile init command */ + +enum { + U3G_BULK_WR, + U3G_BULK_RD, + U3G_N_TRANSFER, +}; + +struct u3g_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom[U3G_MAXPORTS]; + + struct usb_xfer *sc_xfer[U3G_MAXPORTS][U3G_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* U3G status register */ + uint8_t sc_numports; +}; + +static device_probe_t u3g_probe; +static device_attach_t u3g_attach; +static device_detach_t u3g_detach; + +static usb_callback_t u3g_write_callback; +static usb_callback_t u3g_read_callback; + +static void u3g_start_read(struct ucom_softc *ucom); +static void u3g_stop_read(struct ucom_softc *ucom); +static void u3g_start_write(struct ucom_softc *ucom); +static void u3g_stop_write(struct ucom_softc *ucom); + + +static void u3g_test_autoinst(void *, struct usb_device *, + struct usb_attach_arg *); +static int u3g_driver_loaded(struct module *mod, int what, void *arg); + +static eventhandler_tag u3g_etag; + +static const struct usb_config u3g_config[U3G_N_TRANSFER] = { + + [U3G_BULK_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = U3G_BSIZE,/* bytes */ + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &u3g_write_callback, + }, + + [U3G_BULK_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = U3G_BSIZE,/* bytes */ + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &u3g_read_callback, + }, +}; + +static const struct ucom_callback u3g_callback = { + .ucom_start_read = &u3g_start_read, + .ucom_stop_read = &u3g_stop_read, + .ucom_start_write = &u3g_start_write, + .ucom_stop_write = &u3g_stop_write, +}; + +static device_method_t u3g_methods[] = { + DEVMETHOD(device_probe, u3g_probe), + DEVMETHOD(device_attach, u3g_attach), + DEVMETHOD(device_detach, u3g_detach), + {0, 0} +}; + +static devclass_t u3g_devclass; + +static driver_t u3g_driver = { + .name = "u3g", + .methods = u3g_methods, + .size = sizeof(struct u3g_softc), +}; + +DRIVER_MODULE(u3g, uhub, u3g_driver, u3g_devclass, u3g_driver_loaded, 0); +MODULE_DEPEND(u3g, ucom, 1, 1, 1); +MODULE_DEPEND(u3g, usb, 1, 1, 1); +MODULE_VERSION(u3g, 1); + +static const STRUCT_USB_HOST_ID u3g_devs[] = { +#define U3G_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } + U3G_DEV(ACERP, H10, 0), + U3G_DEV(AIRPLUS, MCD650, 0), + U3G_DEV(AIRPRIME, PC5220, 0), + U3G_DEV(ALINK, 3G, 0), + U3G_DEV(ALINK, 3GU, 0), + U3G_DEV(ALINK, DWM652U5, 0), + U3G_DEV(AMOI, H01, 0), + U3G_DEV(AMOI, H01A, 0), + U3G_DEV(AMOI, H02, 0), + U3G_DEV(ANYDATA, ADU_500A, 0), + U3G_DEV(ANYDATA, ADU_620UW, 0), + U3G_DEV(ANYDATA, ADU_E100X, 0), + U3G_DEV(AXESSTEL, DATAMODEM, 0), + U3G_DEV(CMOTECH, CDMA_MODEM1, 0), + U3G_DEV(CMOTECH, CGU628, U3GINIT_CMOTECH), + U3G_DEV(DELL, U5500, 0), + U3G_DEV(DELL, U5505, 0), + U3G_DEV(DELL, U5510, 0), + U3G_DEV(DELL, U5520, 0), + U3G_DEV(DELL, U5520_2, 0), + U3G_DEV(DELL, U5520_3, 0), + U3G_DEV(DELL, U5700, 0), + U3G_DEV(DELL, U5700_2, 0), + U3G_DEV(DELL, U5700_3, 0), + U3G_DEV(DELL, U5700_4, 0), + U3G_DEV(DELL, U5720, 0), + U3G_DEV(DELL, U5720_2, 0), + U3G_DEV(DELL, U5730, 0), + U3G_DEV(DELL, U5730_2, 0), + U3G_DEV(DELL, U5730_3, 0), + U3G_DEV(DELL, U740, 0), + U3G_DEV(DLINK3, DWM652, 0), + U3G_DEV(HP, EV2200, 0), + U3G_DEV(HP, HS2300, 0), + U3G_DEV(HUAWEI, E1401, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1402, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1403, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1404, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1405, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1406, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1407, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1408, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1409, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E140A, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E140B, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E140D, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E140E, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E140F, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1410, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1411, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1412, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1413, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1414, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1415, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1416, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1417, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1418, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1419, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E141A, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E141B, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E141C, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E141D, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E141E, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E141F, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1420, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1421, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1422, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1423, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1424, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1425, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1426, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1427, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1428, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1429, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E142A, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E142B, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E142C, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E142D, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E142E, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E142F, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1430, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1431, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1432, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1433, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1434, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1435, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1436, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1437, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1438, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1439, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E143A, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E143B, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E143C, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E143D, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E143E, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E143F, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E173, 0), + U3G_DEV(HUAWEI, E173_INIT, U3GINIT_HUAWEISCSI), + U3G_DEV(HUAWEI, E180V, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E220, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E220BIS, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, MOBILE, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1752, U3GINIT_HUAWEISCSI), + U3G_DEV(HUAWEI, E1820, U3GINIT_HUAWEISCSI), + U3G_DEV(HUAWEI, K3765, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, K3765_INIT, U3GINIT_HUAWEISCSI), + U3G_DEV(KYOCERA2, CDMA_MSM_K, 0), + U3G_DEV(KYOCERA2, KPC680, 0), + U3G_DEV(LONGCHEER, WM66, U3GINIT_HUAWEI), + U3G_DEV(LONGCHEER, DISK, U3GINIT_TCT), + U3G_DEV(LONGCHEER, W14, 0), + U3G_DEV(LONGCHEER, XSSTICK, 0), + U3G_DEV(MERLIN, V620, 0), + U3G_DEV(NEOTEL, PRIME, 0), + U3G_DEV(NOVATEL, E725, 0), + U3G_DEV(NOVATEL, ES620, 0), + U3G_DEV(NOVATEL, ES620_2, 0), + U3G_DEV(NOVATEL, EU730, 0), + U3G_DEV(NOVATEL, EU740, 0), + U3G_DEV(NOVATEL, EU870D, 0), + U3G_DEV(NOVATEL, MC760, 0), + U3G_DEV(NOVATEL, MC547, 0), + U3G_DEV(NOVATEL, MC950D, 0), + U3G_DEV(NOVATEL, U720, 0), + U3G_DEV(NOVATEL, U727, 0), + U3G_DEV(NOVATEL, U727_2, 0), + U3G_DEV(NOVATEL, U740, 0), + U3G_DEV(NOVATEL, U740_2, 0), + U3G_DEV(NOVATEL, U760, U3GINIT_SCSIEJECT), + U3G_DEV(NOVATEL, U870, 0), + U3G_DEV(NOVATEL, V620, 0), + U3G_DEV(NOVATEL, V640, 0), + U3G_DEV(NOVATEL, V720, 0), + U3G_DEV(NOVATEL, V740, 0), + U3G_DEV(NOVATEL, X950D, 0), + U3G_DEV(NOVATEL, XU870, 0), + U3G_DEV(OPTION, E6500, 0), + U3G_DEV(OPTION, E6501, 0), + U3G_DEV(OPTION, E6601, 0), + U3G_DEV(OPTION, E6721, 0), + U3G_DEV(OPTION, E6741, 0), + U3G_DEV(OPTION, E6761, 0), + U3G_DEV(OPTION, E6800, 0), + U3G_DEV(OPTION, E7021, 0), + U3G_DEV(OPTION, E7041, 0), + U3G_DEV(OPTION, E7061, 0), + U3G_DEV(OPTION, E7100, 0), + U3G_DEV(OPTION, GE40X, 0), + U3G_DEV(OPTION, GT3G, 0), + U3G_DEV(OPTION, GT3GPLUS, 0), + U3G_DEV(OPTION, GT3GQUAD, 0), + U3G_DEV(OPTION, GT3G_1, 0), + U3G_DEV(OPTION, GT3G_2, 0), + U3G_DEV(OPTION, GT3G_3, 0), + U3G_DEV(OPTION, GT3G_4, 0), + U3G_DEV(OPTION, GT3G_5, 0), + U3G_DEV(OPTION, GT3G_6, 0), + U3G_DEV(OPTION, GTHSDPA, 0), + U3G_DEV(OPTION, GTM380, 0), + U3G_DEV(OPTION, GTMAX36, 0), + U3G_DEV(OPTION, GTMAX380HSUPAE, 0), + U3G_DEV(OPTION, GTMAXHSUPA, 0), + U3G_DEV(OPTION, GTMAXHSUPAE, 0), + U3G_DEV(OPTION, VODAFONEMC3G, 0), + U3G_DEV(QISDA, H20_1, 0), + U3G_DEV(QISDA, H20_2, 0), + U3G_DEV(QISDA, H21_1, 0), + U3G_DEV(QISDA, H21_2, 0), + U3G_DEV(QUALCOMM2, AC8700, 0), + U3G_DEV(QUALCOMM2, MF330, 0), + U3G_DEV(QUALCOMM2, VW110L, U3GINIT_SCSIEJECT), + U3G_DEV(QUALCOMMINC, AC2726, 0), + U3G_DEV(QUALCOMMINC, AC8700, 0), + U3G_DEV(QUALCOMMINC, AC8710, 0), + U3G_DEV(QUALCOMMINC, CDMA_MSM, U3GINIT_SCSIEJECT), + U3G_DEV(QUALCOMMINC, E0002, 0), + U3G_DEV(QUALCOMMINC, E0003, 0), + U3G_DEV(QUALCOMMINC, E0004, 0), + U3G_DEV(QUALCOMMINC, E0005, 0), + U3G_DEV(QUALCOMMINC, E0006, 0), + U3G_DEV(QUALCOMMINC, E0007, 0), + U3G_DEV(QUALCOMMINC, E0008, 0), + U3G_DEV(QUALCOMMINC, E0009, 0), + U3G_DEV(QUALCOMMINC, E000A, 0), + U3G_DEV(QUALCOMMINC, E000B, 0), + U3G_DEV(QUALCOMMINC, E000C, 0), + U3G_DEV(QUALCOMMINC, E000D, 0), + U3G_DEV(QUALCOMMINC, E000E, 0), + U3G_DEV(QUALCOMMINC, E000F, 0), + U3G_DEV(QUALCOMMINC, E0010, 0), + U3G_DEV(QUALCOMMINC, E0011, 0), + U3G_DEV(QUALCOMMINC, E0012, 0), + U3G_DEV(QUALCOMMINC, E0013, 0), + U3G_DEV(QUALCOMMINC, E0014, 0), + U3G_DEV(QUALCOMMINC, E0017, 0), + U3G_DEV(QUALCOMMINC, E0018, 0), + U3G_DEV(QUALCOMMINC, E0019, 0), + U3G_DEV(QUALCOMMINC, E0020, 0), + U3G_DEV(QUALCOMMINC, E0021, 0), + U3G_DEV(QUALCOMMINC, E0022, 0), + U3G_DEV(QUALCOMMINC, E0023, 0), + U3G_DEV(QUALCOMMINC, E0024, 0), + U3G_DEV(QUALCOMMINC, E0025, 0), + U3G_DEV(QUALCOMMINC, E0026, 0), + U3G_DEV(QUALCOMMINC, E0027, 0), + U3G_DEV(QUALCOMMINC, E0028, 0), + U3G_DEV(QUALCOMMINC, E0029, 0), + U3G_DEV(QUALCOMMINC, E0030, 0), + U3G_DEV(QUALCOMMINC, E0032, 0), + U3G_DEV(QUALCOMMINC, E0033, 0), + U3G_DEV(QUALCOMMINC, E0037, 0), + U3G_DEV(QUALCOMMINC, E0039, 0), + U3G_DEV(QUALCOMMINC, E0042, 0), + U3G_DEV(QUALCOMMINC, E0043, 0), + U3G_DEV(QUALCOMMINC, E0048, 0), + U3G_DEV(QUALCOMMINC, E0049, 0), + U3G_DEV(QUALCOMMINC, E0051, 0), + U3G_DEV(QUALCOMMINC, E0052, 0), + U3G_DEV(QUALCOMMINC, E0054, 0), + U3G_DEV(QUALCOMMINC, E0055, 0), + U3G_DEV(QUALCOMMINC, E0057, 0), + U3G_DEV(QUALCOMMINC, E0058, 0), + U3G_DEV(QUALCOMMINC, E0059, 0), + U3G_DEV(QUALCOMMINC, E0060, 0), + U3G_DEV(QUALCOMMINC, E0061, 0), + U3G_DEV(QUALCOMMINC, E0062, 0), + U3G_DEV(QUALCOMMINC, E0063, 0), + U3G_DEV(QUALCOMMINC, E0064, 0), + U3G_DEV(QUALCOMMINC, E0066, 0), + U3G_DEV(QUALCOMMINC, E0069, 0), + U3G_DEV(QUALCOMMINC, E0070, 0), + U3G_DEV(QUALCOMMINC, E0073, 0), + U3G_DEV(QUALCOMMINC, E0076, 0), + U3G_DEV(QUALCOMMINC, E0078, 0), + U3G_DEV(QUALCOMMINC, E0082, 0), + U3G_DEV(QUALCOMMINC, E0086, 0), + U3G_DEV(QUALCOMMINC, SURFSTICK, 0), + U3G_DEV(QUALCOMMINC, E2002, 0), + U3G_DEV(QUALCOMMINC, E2003, 0), + U3G_DEV(QUALCOMMINC, MF626, 0), + U3G_DEV(QUALCOMMINC, MF628, 0), + U3G_DEV(QUALCOMMINC, MF633R, 0), + U3G_DEV(QUANTA, GKE, 0), + U3G_DEV(QUANTA, GLE, 0), + U3G_DEV(QUANTA, GLX, 0), + U3G_DEV(QUANTA, Q101, 0), + U3G_DEV(QUANTA, Q111, 0), + U3G_DEV(SIERRA, AC402, 0), + U3G_DEV(SIERRA, AC595U, 0), + U3G_DEV(SIERRA, AC313U, 0), + U3G_DEV(SIERRA, AC597E, 0), + U3G_DEV(SIERRA, AC875E, 0), + U3G_DEV(SIERRA, AC875U, 0), + U3G_DEV(SIERRA, AC875U_2, 0), + U3G_DEV(SIERRA, AC880, 0), + U3G_DEV(SIERRA, AC880E, 0), + U3G_DEV(SIERRA, AC880U, 0), + U3G_DEV(SIERRA, AC881, 0), + U3G_DEV(SIERRA, AC881E, 0), + U3G_DEV(SIERRA, AC881U, 0), + U3G_DEV(SIERRA, AC885E, 0), + U3G_DEV(SIERRA, AC885E_2, 0), + U3G_DEV(SIERRA, AC885U, 0), + U3G_DEV(SIERRA, AIRCARD580, 0), + U3G_DEV(SIERRA, AIRCARD595, 0), + U3G_DEV(SIERRA, AIRCARD875, 0), + U3G_DEV(SIERRA, C22, 0), + U3G_DEV(SIERRA, C597, 0), + U3G_DEV(SIERRA, C888, 0), + U3G_DEV(SIERRA, E0029, 0), + U3G_DEV(SIERRA, E6892, 0), + U3G_DEV(SIERRA, E6893, 0), + U3G_DEV(SIERRA, EM5625, 0), + U3G_DEV(SIERRA, EM5725, 0), + U3G_DEV(SIERRA, MC5720, 0), + U3G_DEV(SIERRA, MC5720_2, 0), + U3G_DEV(SIERRA, MC5725, 0), + U3G_DEV(SIERRA, MC5727, 0), + U3G_DEV(SIERRA, MC5727_2, 0), + U3G_DEV(SIERRA, MC5728, 0), + U3G_DEV(SIERRA, MC8700, 0), + U3G_DEV(SIERRA, MC8755, 0), + U3G_DEV(SIERRA, MC8755_2, 0), + U3G_DEV(SIERRA, MC8755_3, 0), + U3G_DEV(SIERRA, MC8755_4, 0), + U3G_DEV(SIERRA, MC8765, 0), + U3G_DEV(SIERRA, MC8765_2, 0), + U3G_DEV(SIERRA, MC8765_3, 0), + U3G_DEV(SIERRA, MC8775, 0), + U3G_DEV(SIERRA, MC8775_2, 0), + U3G_DEV(SIERRA, MC8780, 0), + U3G_DEV(SIERRA, MC8780_2, 0), + U3G_DEV(SIERRA, MC8780_3, 0), + U3G_DEV(SIERRA, MC8781, 0), + U3G_DEV(SIERRA, MC8781_2, 0), + U3G_DEV(SIERRA, MC8781_3, 0), + U3G_DEV(SIERRA, MC8785, 0), + U3G_DEV(SIERRA, MC8785_2, 0), + U3G_DEV(SIERRA, MC8790, 0), + U3G_DEV(SIERRA, MC8791, 0), + U3G_DEV(SIERRA, MC8792, 0), + U3G_DEV(SIERRA, MINI5725, 0), + U3G_DEV(SIERRA, T11, 0), + U3G_DEV(SIERRA, T598, 0), + U3G_DEV(SILABS, SAEL, U3GINIT_SAEL_M460), + U3G_DEV(STELERA, C105, 0), + U3G_DEV(STELERA, E1003, 0), + U3G_DEV(STELERA, E1004, 0), + U3G_DEV(STELERA, E1005, 0), + U3G_DEV(STELERA, E1006, 0), + U3G_DEV(STELERA, E1007, 0), + U3G_DEV(STELERA, E1008, 0), + U3G_DEV(STELERA, E1009, 0), + U3G_DEV(STELERA, E100A, 0), + U3G_DEV(STELERA, E100B, 0), + U3G_DEV(STELERA, E100C, 0), + U3G_DEV(STELERA, E100D, 0), + U3G_DEV(STELERA, E100E, 0), + U3G_DEV(STELERA, E100F, 0), + U3G_DEV(STELERA, E1010, 0), + U3G_DEV(STELERA, E1011, 0), + U3G_DEV(STELERA, E1012, 0), + U3G_DEV(TCTMOBILE, X060S, 0), + U3G_DEV(TCTMOBILE, X080S, U3GINIT_TCT), + U3G_DEV(TELIT, UC864E, 0), + U3G_DEV(TELIT, UC864G, 0), + U3G_DEV(TLAYTECH, TEU800, 0), + U3G_DEV(TOSHIBA, G450, 0), + U3G_DEV(TOSHIBA, HSDPA, 0), + U3G_DEV(YISO, C893, 0), + /* Autoinstallers */ + U3G_DEV(NOVATEL, ZEROCD, U3GINIT_SCSIEJECT), + U3G_DEV(OPTION, GTICON322, U3GINIT_REZERO), + U3G_DEV(QUALCOMMINC, ZTE_STOR, U3GINIT_ZTESTOR), + U3G_DEV(QUALCOMMINC, ZTE_STOR2, U3GINIT_SCSIEJECT), + U3G_DEV(QUANTA, Q101_STOR, U3GINIT_SCSIEJECT), + U3G_DEV(SIERRA, TRUINSTALL, U3GINIT_SIERRA), +#undef U3G_DEV +}; + +static int +u3g_sierra_init(struct usb_device *udev) +{ + struct usb_device_request req; + + req.bmRequestType = UT_VENDOR; + req.bRequest = UR_SET_INTERFACE; + USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP); + USETW(req.wIndex, UHF_PORT_CONNECTION); + USETW(req.wLength, 0); + + if (usbd_do_request_flags(udev, NULL, &req, + NULL, 0, NULL, USB_MS_HZ)) { + /* ignore any errors */ + } + return (0); +} + +static int +u3g_huawei_init(struct usb_device *udev) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP); + USETW(req.wIndex, UHF_PORT_SUSPEND); + USETW(req.wLength, 0); + + if (usbd_do_request_flags(udev, NULL, &req, + NULL, 0, NULL, USB_MS_HZ)) { + /* ignore any errors */ + } + return (0); +} + +static void +u3g_sael_m460_init(struct usb_device *udev) +{ + static const uint8_t setup[][24] = { + { 0x41, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0xc1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02 }, + { 0xc1, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }, + { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 }, + { 0xc1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }, + { 0x41, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x19, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x13 }, + { 0x41, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00 }, + { 0x41, 0x12, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x19, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x13 }, + { 0x41, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00 }, + { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 }, + }; + + struct usb_device_request req; + usb_error_t err; + uint16_t len; + uint8_t buf[0x300]; + uint8_t n; + + DPRINTFN(1, "\n"); + + if (usbd_req_set_alt_interface_no(udev, NULL, 0, 0)) { + DPRINTFN(0, "Alt setting 0 failed\n"); + return; + } + + for (n = 0; n != (sizeof(setup)/sizeof(setup[0])); n++) { + + memcpy(&req, setup[n], sizeof(req)); + + len = UGETW(req.wLength); + if (req.bmRequestType & UE_DIR_IN) { + if (len > sizeof(buf)) { + DPRINTFN(0, "too small buffer\n"); + continue; + } + err = usbd_do_request(udev, NULL, &req, buf); + } else { + if (len > (sizeof(setup[0]) - 8)) { + DPRINTFN(0, "too small buffer\n"); + continue; + } + err = usbd_do_request(udev, NULL, &req, + __DECONST(uint8_t *, &setup[n][8])); + } + if (err) { + DPRINTFN(1, "request %u failed\n", + (unsigned int)n); + /* + * Some of the requests will fail. Stop doing + * requests when we are getting timeouts so + * that we don't block the explore/attach + * thread forever. + */ + if (err == USB_ERR_TIMEOUT) + break; + } + } +} + +/* + * The following function handles 3G modem devices (E220, Mobile, + * etc.) with auto-install flash disks for Windows/MacOSX on the first + * interface. After some command or some delay they change appearance + * to a modem. + */ +static void +u3g_test_autoinst(void *arg, struct usb_device *udev, + struct usb_attach_arg *uaa) +{ + struct usb_interface *iface; + struct usb_interface_descriptor *id; + int error; + unsigned long method; + + if (uaa->dev_state != UAA_DEV_READY) + return; + + iface = usbd_get_iface(udev, 0); + if (iface == NULL) + return; + id = iface->idesc; + if (id == NULL || id->bInterfaceClass != UICLASS_MASS) + return; + + if (usb_test_quirk(uaa, UQ_MSC_EJECT_HUAWEI)) + method = U3GINIT_HUAWEI; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_SIERRA)) + method = U3GINIT_SIERRA; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_SCSIEJECT)) + method = U3GINIT_SCSIEJECT; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_REZERO)) + method = U3GINIT_REZERO; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_ZTESTOR)) + method = U3GINIT_ZTESTOR; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_CMOTECH)) + method = U3GINIT_CMOTECH; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_WAIT)) + method = U3GINIT_WAIT; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_HUAWEISCSI)) + method = U3GINIT_HUAWEISCSI; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_TCT)) + method = U3GINIT_TCT; + else if (usbd_lookup_id_by_uaa(u3g_devs, sizeof(u3g_devs), uaa) == 0) + method = USB_GET_DRIVER_INFO(uaa); + else + return; /* no device match */ + + if (bootverbose) { + printf("Ejecting %s %s using method %ld\n", + usb_get_manufacturer(udev), + usb_get_product(udev), method); + } + + switch (method) { + case U3GINIT_HUAWEI: + error = u3g_huawei_init(udev); + break; + case U3GINIT_HUAWEISCSI: + error = usb_msc_eject(udev, 0, MSC_EJECT_HUAWEI); + break; + case U3GINIT_SCSIEJECT: + error = usb_msc_eject(udev, 0, MSC_EJECT_STOPUNIT); + break; + case U3GINIT_REZERO: + error = usb_msc_eject(udev, 0, MSC_EJECT_REZERO); + break; + case U3GINIT_ZTESTOR: + error = usb_msc_eject(udev, 0, MSC_EJECT_STOPUNIT); + error |= usb_msc_eject(udev, 0, MSC_EJECT_ZTESTOR); + break; + case U3GINIT_CMOTECH: + error = usb_msc_eject(udev, 0, MSC_EJECT_CMOTECH); + break; + case U3GINIT_TCT: + error = usb_msc_eject(udev, 0, MSC_EJECT_TCT); + break; + case U3GINIT_SIERRA: + error = u3g_sierra_init(udev); + break; + case U3GINIT_WAIT: + /* Just pretend we ejected, the card will timeout */ + error = 0; + break; + default: + /* no 3G eject quirks */ + error = EOPNOTSUPP; + break; + } + if (error == 0) { + /* success, mark the udev as disappearing */ + uaa->dev_state = UAA_DEV_EJECTING; + } +} + +static int +u3g_driver_loaded(struct module *mod, int what, void *arg) +{ + switch (what) { + case MOD_LOAD: + /* register our autoinstall handler */ + u3g_etag = EVENTHANDLER_REGISTER(usb_dev_configured, + u3g_test_autoinst, NULL, EVENTHANDLER_PRI_ANY); + break; + case MOD_UNLOAD: + EVENTHANDLER_DEREGISTER(usb_dev_configured, u3g_etag); + break; + default: + return (EOPNOTSUPP); + } + return (0); +} + +static int +u3g_probe(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != U3G_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bInterfaceClass != UICLASS_VENDOR) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(u3g_devs, sizeof(u3g_devs), uaa)); +} + +static int +u3g_attach(device_t dev) +{ + struct usb_config u3g_config_tmp[U3G_N_TRANSFER]; + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct u3g_softc *sc = device_get_softc(dev); + struct usb_interface *iface; + struct usb_interface_descriptor *id; + uint32_t iface_valid; + int error, type, nports; + int ep, n; + uint8_t i; + + DPRINTF("sc=%p\n", sc); + + type = USB_GET_DRIVER_INFO(uaa); + if (type == U3GINIT_SAEL_M460 + || usb_test_quirk(uaa, UQ_MSC_EJECT_SAEL_M460)) { + u3g_sael_m460_init(uaa->device); + } + + /* copy in USB config */ + for (n = 0; n != U3G_N_TRANSFER; n++) + u3g_config_tmp[n] = u3g_config[n]; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "u3g", NULL, MTX_DEF); + + sc->sc_udev = uaa->device; + + /* Claim all interfaces on the device */ + iface_valid = 0; + for (i = uaa->info.bIfaceIndex; i < USB_IFACE_MAX; i++) { + iface = usbd_get_iface(uaa->device, i); + if (iface == NULL) + break; + id = usbd_get_interface_descriptor(iface); + if (id == NULL || id->bInterfaceClass != UICLASS_VENDOR) + continue; + usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); + iface_valid |= (1<device, &i, + sc->sc_xfer[nports], u3g_config_tmp, U3G_N_TRANSFER, + &sc->sc_ucom[nports], &sc->sc_mtx); + if (error) { + /* next interface */ + i++; + ep = 0; + continue; + } + + /* set stall by default */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[nports][U3G_BULK_WR]); + usbd_xfer_set_stall(sc->sc_xfer[nports][U3G_BULK_RD]); + mtx_unlock(&sc->sc_mtx); + + nports++; /* found one port */ + ep++; + if (nports == U3G_MAXPORTS) + break; + } + if (nports == 0) { + device_printf(dev, "no ports found\n"); + goto detach; + } + sc->sc_numports = nports; + + error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, + sc->sc_numports, sc, &u3g_callback, &sc->sc_mtx); + if (error) { + DPRINTF("ucom_attach failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + device_printf(dev, "Found %u port%s.\n", sc->sc_numports, + sc->sc_numports > 1 ? "s":""); + + return (0); + +detach: + u3g_detach(dev); + return (ENXIO); +} + +static int +u3g_detach(device_t dev) +{ + struct u3g_softc *sc = device_get_softc(dev); + uint8_t subunit; + + DPRINTF("sc=%p\n", sc); + + /* NOTE: It is not dangerous to detach more ports than attached! */ + ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); + + for (subunit = 0; subunit != U3G_MAXPORTS; subunit++) + usbd_transfer_unsetup(sc->sc_xfer[subunit], U3G_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +u3g_start_read(struct ucom_softc *ucom) +{ + struct u3g_softc *sc = ucom->sc_parent; + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_RD]); + return; +} + +static void +u3g_stop_read(struct ucom_softc *ucom) +{ + struct u3g_softc *sc = ucom->sc_parent; + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_RD]); + return; +} + +static void +u3g_start_write(struct ucom_softc *ucom) +{ + struct u3g_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_WR]); + return; +} + +static void +u3g_stop_write(struct ucom_softc *ucom) +{ + struct u3g_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_WR]); + return; +} + +static void +u3g_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ucom_softc *ucom = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(ucom, pc, 0, U3G_BSIZE, &actlen)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* do a builtin clear-stall */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } + return; +} + +static void +u3g_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ucom_softc *ucom = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* do a builtin clear-stall */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } + return; +} diff --git a/sys/bus/u4b/serial/uark.c b/sys/bus/u4b/serial/uark.c new file mode 100644 index 0000000000..2c3943d70d --- /dev/null +++ b/sys/bus/u4b/serial/uark.c @@ -0,0 +1,444 @@ +/* $OpenBSD: uark.c,v 1.1 2006/08/14 08:30:22 jsg Exp $ */ + +/* + * Copyright (c) 2006 Jonathan Gray + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $FreeBSD$ + */ + +/* + * NOTE: all function names beginning like "uark_cfg_" can only + * be called from within the config thread function ! + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR usb_debug +#include +#include + +#include + +#define UARK_BUF_SIZE 1024 /* bytes */ + +#define UARK_SET_DATA_BITS(x) ((x) - 5) + +#define UARK_PARITY_NONE 0x00 +#define UARK_PARITY_ODD 0x08 +#define UARK_PARITY_EVEN 0x18 + +#define UARK_STOP_BITS_1 0x00 +#define UARK_STOP_BITS_2 0x04 + +#define UARK_BAUD_REF 3000000 + +#define UARK_WRITE 0x40 +#define UARK_READ 0xc0 + +#define UARK_REQUEST 0xfe + +#define UARK_CONFIG_INDEX 0 +#define UARK_IFACE_INDEX 0 + +enum { + UARK_BULK_DT_WR, + UARK_BULK_DT_RD, + UARK_N_TRANSFER, +}; + +struct uark_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UARK_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint8_t sc_msr; + uint8_t sc_lsr; +}; + +/* prototypes */ + +static device_probe_t uark_probe; +static device_attach_t uark_attach; +static device_detach_t uark_detach; + +static usb_callback_t uark_bulk_write_callback; +static usb_callback_t uark_bulk_read_callback; + +static void uark_start_read(struct ucom_softc *); +static void uark_stop_read(struct ucom_softc *); +static void uark_start_write(struct ucom_softc *); +static void uark_stop_write(struct ucom_softc *); +static int uark_pre_param(struct ucom_softc *, struct termios *); +static void uark_cfg_param(struct ucom_softc *, struct termios *); +static void uark_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void uark_cfg_set_break(struct ucom_softc *, uint8_t); +static void uark_cfg_write(struct uark_softc *, uint16_t, uint16_t); +static void uark_poll(struct ucom_softc *ucom); + +static const struct usb_config + uark_xfer_config[UARK_N_TRANSFER] = { + + [UARK_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UARK_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &uark_bulk_write_callback, + }, + + [UARK_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UARK_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uark_bulk_read_callback, + }, +}; + +static const struct ucom_callback uark_callback = { + .ucom_cfg_get_status = &uark_cfg_get_status, + .ucom_cfg_set_break = &uark_cfg_set_break, + .ucom_cfg_param = &uark_cfg_param, + .ucom_pre_param = &uark_pre_param, + .ucom_start_read = &uark_start_read, + .ucom_stop_read = &uark_stop_read, + .ucom_start_write = &uark_start_write, + .ucom_stop_write = &uark_stop_write, + .ucom_poll = &uark_poll, +}; + +static device_method_t uark_methods[] = { + /* Device methods */ + DEVMETHOD(device_probe, uark_probe), + DEVMETHOD(device_attach, uark_attach), + DEVMETHOD(device_detach, uark_detach), + {0, 0} +}; + +static devclass_t uark_devclass; + +static driver_t uark_driver = { + .name = "uark", + .methods = uark_methods, + .size = sizeof(struct uark_softc), +}; + +DRIVER_MODULE(uark, uhub, uark_driver, uark_devclass, NULL, 0); +MODULE_DEPEND(uark, ucom, 1, 1, 1); +MODULE_DEPEND(uark, usb, 1, 1, 1); +MODULE_VERSION(uark, 1); + +static const STRUCT_USB_HOST_ID uark_devs[] = { + {USB_VPI(USB_VENDOR_ARKMICRO, USB_PRODUCT_ARKMICRO_ARK3116, 0)}, +}; + +static int +uark_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != 0) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UARK_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uark_devs, sizeof(uark_devs), uaa)); +} + +static int +uark_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uark_softc *sc = device_get_softc(dev); + int32_t error; + uint8_t iface_index; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uark", NULL, MTX_DEF); + + sc->sc_udev = uaa->device; + + iface_index = UARK_IFACE_INDEX; + error = usbd_transfer_setup + (uaa->device, &iface_index, sc->sc_xfer, + uark_xfer_config, UARK_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating control USB " + "transfers failed\n"); + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UARK_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UARK_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uark_callback, &sc->sc_mtx); + if (error) { + DPRINTF("ucom_attach failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); /* success */ + +detach: + uark_detach(dev); + return (ENXIO); /* failure */ +} + +static int +uark_detach(device_t dev) +{ + struct uark_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UARK_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +uark_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uark_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UARK_BUF_SIZE, &actlen)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +uark_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uark_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uark_start_read(struct ucom_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UARK_BULK_DT_RD]); +} + +static void +uark_stop_read(struct ucom_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UARK_BULK_DT_RD]); +} + +static void +uark_start_write(struct ucom_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UARK_BULK_DT_WR]); +} + +static void +uark_stop_write(struct ucom_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UARK_BULK_DT_WR]); +} + +static int +uark_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + if ((t->c_ospeed < 300) || (t->c_ospeed > 115200)) + return (EINVAL); + return (0); +} + +static void +uark_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uark_softc *sc = ucom->sc_parent; + uint32_t speed = t->c_ospeed; + uint16_t data; + + /* + * NOTE: When reverse computing the baud rate from the "data" all + * allowed baud rates are within 3% of the initial baud rate. + */ + data = (UARK_BAUD_REF + (speed / 2)) / speed; + + uark_cfg_write(sc, 3, 0x83); + uark_cfg_write(sc, 0, data & 0xFF); + uark_cfg_write(sc, 1, data >> 8); + uark_cfg_write(sc, 3, 0x03); + + if (t->c_cflag & CSTOPB) + data = UARK_STOP_BITS_2; + else + data = UARK_STOP_BITS_1; + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) + data |= UARK_PARITY_ODD; + else + data |= UARK_PARITY_EVEN; + } else + data |= UARK_PARITY_NONE; + + switch (t->c_cflag & CSIZE) { + case CS5: + data |= UARK_SET_DATA_BITS(5); + break; + case CS6: + data |= UARK_SET_DATA_BITS(6); + break; + case CS7: + data |= UARK_SET_DATA_BITS(7); + break; + default: + case CS8: + data |= UARK_SET_DATA_BITS(8); + break; + } + uark_cfg_write(sc, 3, 0x00); + uark_cfg_write(sc, 3, data); +} + +static void +uark_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uark_softc *sc = ucom->sc_parent; + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +uark_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uark_softc *sc = ucom->sc_parent; + + DPRINTF("onoff=%d\n", onoff); + + uark_cfg_write(sc, 4, onoff ? 0x01 : 0x00); +} + +static void +uark_cfg_write(struct uark_softc *sc, uint16_t index, uint16_t value) +{ + struct usb_device_request req; + usb_error_t err; + + req.bmRequestType = UARK_WRITE; + req.bRequest = UARK_REQUEST; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, 0); + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usbd_errstr(err)); + } +} + +static void +uark_poll(struct ucom_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UARK_N_TRANSFER); +} diff --git a/sys/bus/u4b/serial/ubsa.c b/sys/bus/u4b/serial/ubsa.c new file mode 100644 index 0000000000..c29d75db1a --- /dev/null +++ b/sys/bus/u4b/serial/ubsa.c @@ -0,0 +1,672 @@ +/*- + * Copyright (c) 2002, Alexander Kabaev . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ichiro FUKUHARA (ichiro@ichiro.org). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR ubsa_debug +#include +#include + +#include + +#ifdef USB_DEBUG +static int ubsa_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ubsa, CTLFLAG_RW, 0, "USB ubsa"); +SYSCTL_INT(_hw_usb_ubsa, OID_AUTO, debug, CTLFLAG_RW, + &ubsa_debug, 0, "ubsa debug level"); +#endif + +#define UBSA_BSIZE 1024 /* bytes */ + +#define UBSA_CONFIG_INDEX 0 +#define UBSA_IFACE_INDEX 0 + +#define UBSA_REG_BAUDRATE 0x00 +#define UBSA_REG_STOP_BITS 0x01 +#define UBSA_REG_DATA_BITS 0x02 +#define UBSA_REG_PARITY 0x03 +#define UBSA_REG_DTR 0x0A +#define UBSA_REG_RTS 0x0B +#define UBSA_REG_BREAK 0x0C +#define UBSA_REG_FLOW_CTRL 0x10 + +#define UBSA_PARITY_NONE 0x00 +#define UBSA_PARITY_EVEN 0x01 +#define UBSA_PARITY_ODD 0x02 +#define UBSA_PARITY_MARK 0x03 +#define UBSA_PARITY_SPACE 0x04 + +#define UBSA_FLOW_NONE 0x0000 +#define UBSA_FLOW_OCTS 0x0001 +#define UBSA_FLOW_ODSR 0x0002 +#define UBSA_FLOW_IDSR 0x0004 +#define UBSA_FLOW_IDTR 0x0008 +#define UBSA_FLOW_IRTS 0x0010 +#define UBSA_FLOW_ORTS 0x0020 +#define UBSA_FLOW_UNKNOWN 0x0040 +#define UBSA_FLOW_OXON 0x0080 +#define UBSA_FLOW_IXON 0x0100 + +/* line status register */ +#define UBSA_LSR_TSRE 0x40 /* Transmitter empty: byte sent */ +#define UBSA_LSR_TXRDY 0x20 /* Transmitter buffer empty */ +#define UBSA_LSR_BI 0x10 /* Break detected */ +#define UBSA_LSR_FE 0x08 /* Framing error: bad stop bit */ +#define UBSA_LSR_PE 0x04 /* Parity error */ +#define UBSA_LSR_OE 0x02 /* Overrun, lost incoming byte */ +#define UBSA_LSR_RXRDY 0x01 /* Byte ready in Receive Buffer */ +#define UBSA_LSR_RCV_MASK 0x1f /* Mask for incoming data or error */ + +/* modem status register */ +/* All deltas are from the last read of the MSR. */ +#define UBSA_MSR_DCD 0x80 /* Current Data Carrier Detect */ +#define UBSA_MSR_RI 0x40 /* Current Ring Indicator */ +#define UBSA_MSR_DSR 0x20 /* Current Data Set Ready */ +#define UBSA_MSR_CTS 0x10 /* Current Clear to Send */ +#define UBSA_MSR_DDCD 0x08 /* DCD has changed state */ +#define UBSA_MSR_TERI 0x04 /* RI has toggled low to high */ +#define UBSA_MSR_DDSR 0x02 /* DSR has changed state */ +#define UBSA_MSR_DCTS 0x01 /* CTS has changed state */ + +enum { + UBSA_BULK_DT_WR, + UBSA_BULK_DT_RD, + UBSA_INTR_DT_RD, + UBSA_N_TRANSFER, +}; + +struct ubsa_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UBSA_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint8_t sc_iface_no; /* interface number */ + uint8_t sc_iface_index; /* interface index */ + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* UBSA status register */ +}; + +static device_probe_t ubsa_probe; +static device_attach_t ubsa_attach; +static device_detach_t ubsa_detach; + +static usb_callback_t ubsa_write_callback; +static usb_callback_t ubsa_read_callback; +static usb_callback_t ubsa_intr_callback; + +static void ubsa_cfg_request(struct ubsa_softc *, uint8_t, uint16_t); +static void ubsa_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void ubsa_cfg_set_rts(struct ucom_softc *, uint8_t); +static void ubsa_cfg_set_break(struct ucom_softc *, uint8_t); +static int ubsa_pre_param(struct ucom_softc *, struct termios *); +static void ubsa_cfg_param(struct ucom_softc *, struct termios *); +static void ubsa_start_read(struct ucom_softc *); +static void ubsa_stop_read(struct ucom_softc *); +static void ubsa_start_write(struct ucom_softc *); +static void ubsa_stop_write(struct ucom_softc *); +static void ubsa_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void ubsa_poll(struct ucom_softc *ucom); + +static const struct usb_config ubsa_config[UBSA_N_TRANSFER] = { + + [UBSA_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UBSA_BSIZE, /* bytes */ + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &ubsa_write_callback, + }, + + [UBSA_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UBSA_BSIZE, /* bytes */ + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &ubsa_read_callback, + }, + + [UBSA_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &ubsa_intr_callback, + }, +}; + +static const struct ucom_callback ubsa_callback = { + .ucom_cfg_get_status = &ubsa_cfg_get_status, + .ucom_cfg_set_dtr = &ubsa_cfg_set_dtr, + .ucom_cfg_set_rts = &ubsa_cfg_set_rts, + .ucom_cfg_set_break = &ubsa_cfg_set_break, + .ucom_cfg_param = &ubsa_cfg_param, + .ucom_pre_param = &ubsa_pre_param, + .ucom_start_read = &ubsa_start_read, + .ucom_stop_read = &ubsa_stop_read, + .ucom_start_write = &ubsa_start_write, + .ucom_stop_write = &ubsa_stop_write, + .ucom_poll = &ubsa_poll, +}; + +static const STRUCT_USB_HOST_ID ubsa_devs[] = { + /* AnyData ADU-500A */ + {USB_VPI(USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_500A, 0)}, + /* AnyData ADU-E100A/H */ + {USB_VPI(USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_E100X, 0)}, + /* Axesstel MV100H */ + {USB_VPI(USB_VENDOR_AXESSTEL, USB_PRODUCT_AXESSTEL_DATAMODEM, 0)}, + /* BELKIN F5U103 */ + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U103, 0)}, + /* BELKIN F5U120 */ + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U120, 0)}, + /* GoHubs GO-COM232 */ + {USB_VPI(USB_VENDOR_ETEK, USB_PRODUCT_ETEK_1COM, 0)}, + /* GoHubs GO-COM232 */ + {USB_VPI(USB_VENDOR_GOHUBS, USB_PRODUCT_GOHUBS_GOCOM232, 0)}, + /* Peracom */ + {USB_VPI(USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_SERIAL1, 0)}, +}; + +static device_method_t ubsa_methods[] = { + DEVMETHOD(device_probe, ubsa_probe), + DEVMETHOD(device_attach, ubsa_attach), + DEVMETHOD(device_detach, ubsa_detach), + {0, 0} +}; + +static devclass_t ubsa_devclass; + +static driver_t ubsa_driver = { + .name = "ubsa", + .methods = ubsa_methods, + .size = sizeof(struct ubsa_softc), +}; + +DRIVER_MODULE(ubsa, uhub, ubsa_driver, ubsa_devclass, NULL, 0); +MODULE_DEPEND(ubsa, ucom, 1, 1, 1); +MODULE_DEPEND(ubsa, usb, 1, 1, 1); +MODULE_VERSION(ubsa, 1); + +static int +ubsa_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UBSA_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UBSA_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(ubsa_devs, sizeof(ubsa_devs), uaa)); +} + +static int +ubsa_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ubsa_softc *sc = device_get_softc(dev); + int error; + + DPRINTF("sc=%p\n", sc); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "ubsa", NULL, MTX_DEF); + + sc->sc_udev = uaa->device; + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = UBSA_IFACE_INDEX; + + error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, + sc->sc_xfer, ubsa_config, UBSA_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + DPRINTF("could not allocate all pipes\n"); + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UBSA_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UBSA_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &ubsa_callback, &sc->sc_mtx); + if (error) { + DPRINTF("ucom_attach failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + ubsa_detach(dev); + return (ENXIO); +} + +static int +ubsa_detach(device_t dev) +{ + struct ubsa_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UBSA_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +ubsa_cfg_request(struct ubsa_softc *sc, uint8_t index, uint16_t value) +{ + struct usb_device_request req; + usb_error_t err; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = index; + USETW(req.wValue, value); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usbd_errstr(err)); + } +} + +static void +ubsa_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + ubsa_cfg_request(sc, UBSA_REG_DTR, onoff ? 1 : 0); +} + +static void +ubsa_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + ubsa_cfg_request(sc, UBSA_REG_RTS, onoff ? 1 : 0); +} + +static void +ubsa_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + ubsa_cfg_request(sc, UBSA_REG_BREAK, onoff ? 1 : 0); +} + +static int +ubsa_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + + DPRINTF("sc = %p\n", ucom->sc_parent); + + switch (t->c_ospeed) { + case B0: + case B300: + case B600: + case B1200: + case B2400: + case B4800: + case B9600: + case B19200: + case B38400: + case B57600: + case B115200: + case B230400: + break; + default: + return (EINVAL); + } + return (0); +} + +static void +ubsa_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct ubsa_softc *sc = ucom->sc_parent; + uint16_t value = 0; + + DPRINTF("sc = %p\n", sc); + + switch (t->c_ospeed) { + case B0: + ubsa_cfg_request(sc, UBSA_REG_FLOW_CTRL, 0); + ubsa_cfg_set_dtr(&sc->sc_ucom, 0); + ubsa_cfg_set_rts(&sc->sc_ucom, 0); + break; + case B300: + case B600: + case B1200: + case B2400: + case B4800: + case B9600: + case B19200: + case B38400: + case B57600: + case B115200: + case B230400: + value = B230400 / t->c_ospeed; + ubsa_cfg_request(sc, UBSA_REG_BAUDRATE, value); + break; + default: + return; + } + + if (t->c_cflag & PARENB) + value = (t->c_cflag & PARODD) ? UBSA_PARITY_ODD : UBSA_PARITY_EVEN; + else + value = UBSA_PARITY_NONE; + + ubsa_cfg_request(sc, UBSA_REG_PARITY, value); + + switch (t->c_cflag & CSIZE) { + case CS5: + value = 0; + break; + case CS6: + value = 1; + break; + case CS7: + value = 2; + break; + default: + case CS8: + value = 3; + break; + } + + ubsa_cfg_request(sc, UBSA_REG_DATA_BITS, value); + + value = (t->c_cflag & CSTOPB) ? 1 : 0; + + ubsa_cfg_request(sc, UBSA_REG_STOP_BITS, value); + + value = 0; + if (t->c_cflag & CRTSCTS) + value |= UBSA_FLOW_OCTS | UBSA_FLOW_IRTS; + + if (t->c_iflag & (IXON | IXOFF)) + value |= UBSA_FLOW_OXON | UBSA_FLOW_IXON; + + ubsa_cfg_request(sc, UBSA_REG_FLOW_CTRL, value); +} + +static void +ubsa_start_read(struct ucom_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint */ + usbd_transfer_start(sc->sc_xfer[UBSA_INTR_DT_RD]); + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UBSA_BULK_DT_RD]); +} + +static void +ubsa_stop_read(struct ucom_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint */ + usbd_transfer_stop(sc->sc_xfer[UBSA_INTR_DT_RD]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UBSA_BULK_DT_RD]); +} + +static void +ubsa_start_write(struct ucom_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UBSA_BULK_DT_WR]); +} + +static void +ubsa_stop_write(struct ucom_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UBSA_BULK_DT_WR]); +} + +static void +ubsa_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +ubsa_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ubsa_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UBSA_BSIZE, &actlen)) { + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +ubsa_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ubsa_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +ubsa_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ubsa_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[4]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen >= sizeof(buf)) { + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, sizeof(buf)); + + /* + * incidentally, Belkin adapter status bits match + * UART 16550 bits + */ + sc->sc_lsr = buf[2]; + sc->sc_msr = buf[3]; + + DPRINTF("lsr = 0x%02x, msr = 0x%02x\n", + sc->sc_lsr, sc->sc_msr); + + ucom_status_change(&sc->sc_ucom); + } else { + DPRINTF("ignoring short packet, %d bytes\n", actlen); + } + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +ubsa_poll(struct ucom_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UBSA_N_TRANSFER); + +} diff --git a/sys/bus/u4b/serial/ubser.c b/sys/bus/u4b/serial/ubser.c new file mode 100644 index 0000000000..86278cc129 --- /dev/null +++ b/sys/bus/u4b/serial/ubser.c @@ -0,0 +1,546 @@ +/*- + * Copyright (c) 2004 Bernd Walter + * + * $URL: https://devel.bwct.de/svn/projects/ubser/ubser.c $ + * $Date: 2004-02-29 01:53:10 +0100 (Sun, 29 Feb 2004) $ + * $Author: ticso $ + * $Rev: 1127 $ + */ + +/*- + * Copyright (c) 2001-2002, Shunsuke Akiyama . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * BWCT serial adapter driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR ubser_debug +#include +#include + +#include + +#define UBSER_UNIT_MAX 32 + +/* Vendor Interface Requests */ +#define VENDOR_GET_NUMSER 0x01 +#define VENDOR_SET_BREAK 0x02 +#define VENDOR_CLEAR_BREAK 0x03 + +#ifdef USB_DEBUG +static int ubser_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ubser, CTLFLAG_RW, 0, "USB ubser"); +SYSCTL_INT(_hw_usb_ubser, OID_AUTO, debug, CTLFLAG_RW, + &ubser_debug, 0, "ubser debug level"); +#endif + +enum { + UBSER_BULK_DT_WR, + UBSER_BULK_DT_RD, + UBSER_N_TRANSFER, +}; + +struct ubser_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom[UBSER_UNIT_MAX]; + + struct usb_xfer *sc_xfer[UBSER_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint16_t sc_tx_size; + + uint8_t sc_numser; + uint8_t sc_iface_no; + uint8_t sc_iface_index; + uint8_t sc_curr_tx_unit; + uint8_t sc_name[16]; +}; + +/* prototypes */ + +static device_probe_t ubser_probe; +static device_attach_t ubser_attach; +static device_detach_t ubser_detach; + +static usb_callback_t ubser_write_callback; +static usb_callback_t ubser_read_callback; + +static int ubser_pre_param(struct ucom_softc *, struct termios *); +static void ubser_cfg_set_break(struct ucom_softc *, uint8_t); +static void ubser_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void ubser_start_read(struct ucom_softc *); +static void ubser_stop_read(struct ucom_softc *); +static void ubser_start_write(struct ucom_softc *); +static void ubser_stop_write(struct ucom_softc *); +static void ubser_poll(struct ucom_softc *ucom); + +static const struct usb_config ubser_config[UBSER_N_TRANSFER] = { + + [UBSER_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = 0, /* use wMaxPacketSize */ + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &ubser_write_callback, + }, + + [UBSER_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = 0, /* use wMaxPacketSize */ + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &ubser_read_callback, + }, +}; + +static const struct ucom_callback ubser_callback = { + .ucom_cfg_set_break = &ubser_cfg_set_break, + .ucom_cfg_get_status = &ubser_cfg_get_status, + .ucom_pre_param = &ubser_pre_param, + .ucom_start_read = &ubser_start_read, + .ucom_stop_read = &ubser_stop_read, + .ucom_start_write = &ubser_start_write, + .ucom_stop_write = &ubser_stop_write, + .ucom_poll = &ubser_poll, +}; + +static device_method_t ubser_methods[] = { + DEVMETHOD(device_probe, ubser_probe), + DEVMETHOD(device_attach, ubser_attach), + DEVMETHOD(device_detach, ubser_detach), + {0, 0} +}; + +static devclass_t ubser_devclass; + +static driver_t ubser_driver = { + .name = "ubser", + .methods = ubser_methods, + .size = sizeof(struct ubser_softc), +}; + +DRIVER_MODULE(ubser, uhub, ubser_driver, ubser_devclass, NULL, 0); +MODULE_DEPEND(ubser, ucom, 1, 1, 1); +MODULE_DEPEND(ubser, usb, 1, 1, 1); +MODULE_VERSION(ubser, 1); + +static int +ubser_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + /* check if this is a BWCT vendor specific ubser interface */ + if ((strcmp(usb_get_manufacturer(uaa->device), "BWCT") == 0) && + (uaa->info.bInterfaceClass == 0xff) && + (uaa->info.bInterfaceSubClass == 0x00)) + return (0); + + return (ENXIO); +} + +static int +ubser_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ubser_softc *sc = device_get_softc(dev); + struct usb_device_request req; + uint8_t n; + int error; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "ubser", NULL, MTX_DEF); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = uaa->info.bIfaceIndex; + sc->sc_udev = uaa->device; + + /* get number of serials */ + req.bmRequestType = UT_READ_VENDOR_INTERFACE; + req.bRequest = VENDOR_GET_NUMSER; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + error = usbd_do_request_flags(uaa->device, NULL, + &req, &sc->sc_numser, + 0, NULL, USB_DEFAULT_TIMEOUT); + + if (error || (sc->sc_numser == 0)) { + device_printf(dev, "failed to get number " + "of serial ports: %s\n", + usbd_errstr(error)); + goto detach; + } + if (sc->sc_numser > UBSER_UNIT_MAX) + sc->sc_numser = UBSER_UNIT_MAX; + + device_printf(dev, "found %i serials\n", sc->sc_numser); + + error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, + sc->sc_xfer, ubser_config, UBSER_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + goto detach; + } + sc->sc_tx_size = usbd_xfer_max_len(sc->sc_xfer[UBSER_BULK_DT_WR]); + + if (sc->sc_tx_size == 0) { + DPRINTFN(0, "invalid tx_size\n"); + goto detach; + } + /* initialize port numbers */ + + for (n = 0; n < sc->sc_numser; n++) { + sc->sc_ucom[n].sc_portno = n; + } + + error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, + sc->sc_numser, sc, &ubser_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UBSER_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UBSER_BULK_DT_RD]); + usbd_transfer_start(sc->sc_xfer[UBSER_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + return (0); /* success */ + +detach: + ubser_detach(dev); + return (ENXIO); /* failure */ +} + +static int +ubser_detach(device_t dev) +{ + struct ubser_softc *sc = device_get_softc(dev); + + DPRINTF("\n"); + + ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UBSER_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static int +ubser_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + DPRINTF("\n"); + + /* + * The firmware on our devices can only do 8n1@9600bps + * without handshake. + * We refuse to accept other configurations. + */ + + /* ensure 9600bps */ + switch (t->c_ospeed) { + case 9600: + break; + default: + return (EINVAL); + } + + /* 2 stop bits not possible */ + if (t->c_cflag & CSTOPB) + return (EINVAL); + + /* XXX parity handling not possible with current firmware */ + if (t->c_cflag & PARENB) + return (EINVAL); + + /* we can only do 8 data bits */ + switch (t->c_cflag & CSIZE) { + case CS8: + break; + default: + return (EINVAL); + } + + /* we can't do any kind of hardware handshaking */ + if ((t->c_cflag & + (CRTS_IFLOW | CDTR_IFLOW | CDSR_OFLOW | CCAR_OFLOW)) != 0) + return (EINVAL); + + /* + * XXX xon/xoff not supported by the firmware! + * This is handled within FreeBSD only and may overflow buffers + * because of delayed reaction due to device buffering. + */ + + return (0); +} + +static __inline void +ubser_inc_tx_unit(struct ubser_softc *sc) +{ + sc->sc_curr_tx_unit++; + if (sc->sc_curr_tx_unit >= sc->sc_numser) { + sc->sc_curr_tx_unit = 0; + } +} + +static void +ubser_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ubser_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[1]; + uint8_t first_unit = sc->sc_curr_tx_unit; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + do { + if (ucom_get_data(sc->sc_ucom + sc->sc_curr_tx_unit, + pc, 1, sc->sc_tx_size - 1, + &actlen)) { + + buf[0] = sc->sc_curr_tx_unit; + + usbd_copy_in(pc, 0, buf, 1); + + usbd_xfer_set_frame_len(xfer, 0, actlen + 1); + usbd_transfer_submit(xfer); + + ubser_inc_tx_unit(sc); /* round robin */ + + break; + } + ubser_inc_tx_unit(sc); + + } while (sc->sc_curr_tx_unit != first_unit); + + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +ubser_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ubser_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[1]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen < 1) { + DPRINTF("invalid actlen=0!\n"); + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, 1); + + if (buf[0] >= sc->sc_numser) { + DPRINTF("invalid serial number!\n"); + goto tr_setup; + } + ucom_put_data(sc->sc_ucom + buf[0], pc, 1, actlen - 1); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +ubser_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ubser_softc *sc = ucom->sc_parent; + uint8_t x = ucom->sc_portno; + struct usb_device_request req; + usb_error_t err; + + if (onoff) { + + req.bmRequestType = UT_READ_VENDOR_INTERFACE; + req.bRequest = VENDOR_SET_BREAK; + req.wValue[0] = x; + req.wValue[1] = 0; + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + err = ucom_cfg_do_request(sc->sc_udev, ucom, + &req, NULL, 0, 1000); + if (err) { + DPRINTFN(0, "send break failed, error=%s\n", + usbd_errstr(err)); + } + } +} + +static void +ubser_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + /* fake status bits */ + *lsr = 0; + *msr = SER_DCD; +} + +static void +ubser_start_read(struct ucom_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UBSER_BULK_DT_RD]); +} + +static void +ubser_stop_read(struct ucom_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UBSER_BULK_DT_RD]); +} + +static void +ubser_start_write(struct ucom_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UBSER_BULK_DT_WR]); +} + +static void +ubser_stop_write(struct ucom_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UBSER_BULK_DT_WR]); +} + +static void +ubser_poll(struct ucom_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UBSER_N_TRANSFER); +} diff --git a/sys/bus/u4b/serial/uchcom.c b/sys/bus/u4b/serial/uchcom.c new file mode 100644 index 0000000000..2b12029e57 --- /dev/null +++ b/sys/bus/u4b/serial/uchcom.c @@ -0,0 +1,859 @@ +/* $NetBSD: uchcom.c,v 1.1 2007/09/03 17:57:37 tshiozak Exp $ */ + +/*- + * Copyright (c) 2007, Takanori Watanabe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 2007 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Takuya SHIOZAKI (tshiozak@netbsd.org). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Driver for WinChipHead CH341/340, the worst USB-serial chip in the + * world. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR uchcom_debug +#include +#include + +#include + +#ifdef USB_DEBUG +static int uchcom_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uchcom, CTLFLAG_RW, 0, "USB uchcom"); +SYSCTL_INT(_hw_usb_uchcom, OID_AUTO, debug, CTLFLAG_RW, + &uchcom_debug, 0, "uchcom debug level"); +#endif + +#define UCHCOM_IFACE_INDEX 0 +#define UCHCOM_CONFIG_INDEX 0 + +#define UCHCOM_REV_CH340 0x0250 +#define UCHCOM_INPUT_BUF_SIZE 8 + +#define UCHCOM_REQ_GET_VERSION 0x5F +#define UCHCOM_REQ_READ_REG 0x95 +#define UCHCOM_REQ_WRITE_REG 0x9A +#define UCHCOM_REQ_RESET 0xA1 +#define UCHCOM_REQ_SET_DTRRTS 0xA4 + +#define UCHCOM_REG_STAT1 0x06 +#define UCHCOM_REG_STAT2 0x07 +#define UCHCOM_REG_BPS_PRE 0x12 +#define UCHCOM_REG_BPS_DIV 0x13 +#define UCHCOM_REG_BPS_MOD 0x14 +#define UCHCOM_REG_BPS_PAD 0x0F +#define UCHCOM_REG_BREAK1 0x05 +#define UCHCOM_REG_BREAK2 0x18 +#define UCHCOM_REG_LCR1 0x18 +#define UCHCOM_REG_LCR2 0x25 + +#define UCHCOM_VER_20 0x20 + +#define UCHCOM_BASE_UNKNOWN 0 +#define UCHCOM_BPS_MOD_BASE 20000000 +#define UCHCOM_BPS_MOD_BASE_OFS 1100 + +#define UCHCOM_DTR_MASK 0x20 +#define UCHCOM_RTS_MASK 0x40 + +#define UCHCOM_BRK1_MASK 0x01 +#define UCHCOM_BRK2_MASK 0x40 + +#define UCHCOM_LCR1_MASK 0xAF +#define UCHCOM_LCR2_MASK 0x07 +#define UCHCOM_LCR1_PARENB 0x80 +#define UCHCOM_LCR2_PAREVEN 0x07 +#define UCHCOM_LCR2_PARODD 0x06 +#define UCHCOM_LCR2_PARMARK 0x05 +#define UCHCOM_LCR2_PARSPACE 0x04 + +#define UCHCOM_INTR_STAT1 0x02 +#define UCHCOM_INTR_STAT2 0x03 +#define UCHCOM_INTR_LEAST 4 + +#define UCHCOM_BULK_BUF_SIZE 1024 /* bytes */ + +enum { + UCHCOM_BULK_DT_WR, + UCHCOM_BULK_DT_RD, + UCHCOM_INTR_DT_RD, + UCHCOM_N_TRANSFER, +}; + +struct uchcom_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UCHCOM_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint8_t sc_dtr; /* local copy */ + uint8_t sc_rts; /* local copy */ + uint8_t sc_version; + uint8_t sc_msr; + uint8_t sc_lsr; /* local status register */ +}; + +struct uchcom_divider { + uint8_t dv_prescaler; + uint8_t dv_div; + uint8_t dv_mod; +}; + +struct uchcom_divider_record { + uint32_t dvr_high; + uint32_t dvr_low; + uint32_t dvr_base_clock; + struct uchcom_divider dvr_divider; +}; + +static const struct uchcom_divider_record dividers[] = +{ + {307200, 307200, UCHCOM_BASE_UNKNOWN, {7, 0xD9, 0}}, + {921600, 921600, UCHCOM_BASE_UNKNOWN, {7, 0xF3, 0}}, + {2999999, 23530, 6000000, {3, 0, 0}}, + {23529, 2942, 750000, {2, 0, 0}}, + {2941, 368, 93750, {1, 0, 0}}, + {367, 1, 11719, {0, 0, 0}}, +}; + +#define NUM_DIVIDERS (sizeof (dividers) / sizeof (dividers[0])) + +static const STRUCT_USB_HOST_ID uchcom_devs[] = { + {USB_VPI(USB_VENDOR_WCH, USB_PRODUCT_WCH_CH341SER, 0)}, + {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH341SER, 0)}, +}; + +/* protypes */ + +static int uchcom_pre_param(struct ucom_softc *, struct termios *); +static void uchcom_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void uchcom_cfg_open(struct ucom_softc *ucom); +static void uchcom_cfg_param(struct ucom_softc *, struct termios *); +static void uchcom_cfg_set_break(struct ucom_softc *, uint8_t); +static void uchcom_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void uchcom_cfg_set_rts(struct ucom_softc *, uint8_t); +static void uchcom_start_read(struct ucom_softc *); +static void uchcom_start_write(struct ucom_softc *); +static void uchcom_stop_read(struct ucom_softc *); +static void uchcom_stop_write(struct ucom_softc *); +static void uchcom_update_version(struct uchcom_softc *); +static void uchcom_convert_status(struct uchcom_softc *, uint8_t); +static void uchcom_update_status(struct uchcom_softc *); +static void uchcom_set_dtr_rts(struct uchcom_softc *); +static int uchcom_calc_divider_settings(struct uchcom_divider *, uint32_t); +static void uchcom_set_baudrate(struct uchcom_softc *, uint32_t); +static void uchcom_poll(struct ucom_softc *ucom); + +static device_probe_t uchcom_probe; +static device_attach_t uchcom_attach; +static device_detach_t uchcom_detach; + +static usb_callback_t uchcom_intr_callback; +static usb_callback_t uchcom_write_callback; +static usb_callback_t uchcom_read_callback; + +static const struct usb_config uchcom_config_data[UCHCOM_N_TRANSFER] = { + + [UCHCOM_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UCHCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &uchcom_write_callback, + }, + + [UCHCOM_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UCHCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uchcom_read_callback, + }, + + [UCHCOM_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &uchcom_intr_callback, + }, +}; + +static struct ucom_callback uchcom_callback = { + .ucom_cfg_get_status = &uchcom_cfg_get_status, + .ucom_cfg_set_dtr = &uchcom_cfg_set_dtr, + .ucom_cfg_set_rts = &uchcom_cfg_set_rts, + .ucom_cfg_set_break = &uchcom_cfg_set_break, + .ucom_cfg_open = &uchcom_cfg_open, + .ucom_cfg_param = &uchcom_cfg_param, + .ucom_pre_param = &uchcom_pre_param, + .ucom_start_read = &uchcom_start_read, + .ucom_stop_read = &uchcom_stop_read, + .ucom_start_write = &uchcom_start_write, + .ucom_stop_write = &uchcom_stop_write, + .ucom_poll = &uchcom_poll, +}; + +/* ---------------------------------------------------------------------- + * driver entry points + */ + +static int +uchcom_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + DPRINTFN(11, "\n"); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UCHCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UCHCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uchcom_devs, sizeof(uchcom_devs), uaa)); +} + +static int +uchcom_attach(device_t dev) +{ + struct uchcom_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + int error; + uint8_t iface_index; + + DPRINTFN(11, "\n"); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uchcom", NULL, MTX_DEF); + + sc->sc_udev = uaa->device; + + switch (uaa->info.bcdDevice) { + case UCHCOM_REV_CH340: + device_printf(dev, "CH340 detected\n"); + break; + default: + device_printf(dev, "CH341 detected\n"); + break; + } + + iface_index = UCHCOM_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, + &iface_index, sc->sc_xfer, uchcom_config_data, + UCHCOM_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + DPRINTF("one or more missing USB endpoints, " + "error=%s\n", usbd_errstr(error)); + goto detach; + } + + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UCHCOM_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UCHCOM_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uchcom_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + uchcom_detach(dev); + return (ENXIO); +} + +static int +uchcom_detach(device_t dev) +{ + struct uchcom_softc *sc = device_get_softc(dev); + + DPRINTFN(11, "\n"); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UCHCOM_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +/* ---------------------------------------------------------------------- + * low level i/o + */ + +static void +uchcom_ctrl_write(struct uchcom_softc *sc, uint8_t reqno, + uint16_t value, uint16_t index) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = reqno; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, + &sc->sc_ucom, &req, NULL, 0, 1000); +} + +static void +uchcom_ctrl_read(struct uchcom_softc *sc, uint8_t reqno, + uint16_t value, uint16_t index, void *buf, uint16_t buflen) +{ + struct usb_device_request req; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = reqno; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, buflen); + + ucom_cfg_do_request(sc->sc_udev, + &sc->sc_ucom, &req, buf, USB_SHORT_XFER_OK, 1000); +} + +static void +uchcom_write_reg(struct uchcom_softc *sc, + uint8_t reg1, uint8_t val1, uint8_t reg2, uint8_t val2) +{ + DPRINTF("0x%02X<-0x%02X, 0x%02X<-0x%02X\n", + (unsigned)reg1, (unsigned)val1, + (unsigned)reg2, (unsigned)val2); + uchcom_ctrl_write( + sc, UCHCOM_REQ_WRITE_REG, + reg1 | ((uint16_t)reg2 << 8), val1 | ((uint16_t)val2 << 8)); +} + +static void +uchcom_read_reg(struct uchcom_softc *sc, + uint8_t reg1, uint8_t *rval1, uint8_t reg2, uint8_t *rval2) +{ + uint8_t buf[UCHCOM_INPUT_BUF_SIZE]; + + uchcom_ctrl_read( + sc, UCHCOM_REQ_READ_REG, + reg1 | ((uint16_t)reg2 << 8), 0, buf, sizeof(buf)); + + DPRINTF("0x%02X->0x%02X, 0x%02X->0x%02X\n", + (unsigned)reg1, (unsigned)buf[0], + (unsigned)reg2, (unsigned)buf[1]); + + if (rval1) + *rval1 = buf[0]; + if (rval2) + *rval2 = buf[1]; +} + +static void +uchcom_get_version(struct uchcom_softc *sc, uint8_t *rver) +{ + uint8_t buf[UCHCOM_INPUT_BUF_SIZE]; + + uchcom_ctrl_read(sc, UCHCOM_REQ_GET_VERSION, 0, 0, buf, sizeof(buf)); + + if (rver) + *rver = buf[0]; +} + +static void +uchcom_get_status(struct uchcom_softc *sc, uint8_t *rval) +{ + uchcom_read_reg(sc, UCHCOM_REG_STAT1, rval, UCHCOM_REG_STAT2, NULL); +} + +static void +uchcom_set_dtr_rts_10(struct uchcom_softc *sc, uint8_t val) +{ + uchcom_write_reg(sc, UCHCOM_REG_STAT1, val, UCHCOM_REG_STAT1, val); +} + +static void +uchcom_set_dtr_rts_20(struct uchcom_softc *sc, uint8_t val) +{ + uchcom_ctrl_write(sc, UCHCOM_REQ_SET_DTRRTS, val, 0); +} + + +/* ---------------------------------------------------------------------- + * middle layer + */ + +static void +uchcom_update_version(struct uchcom_softc *sc) +{ + uchcom_get_version(sc, &sc->sc_version); +} + +static void +uchcom_convert_status(struct uchcom_softc *sc, uint8_t cur) +{ + sc->sc_dtr = !(cur & UCHCOM_DTR_MASK); + sc->sc_rts = !(cur & UCHCOM_RTS_MASK); + + cur = ~cur & 0x0F; + sc->sc_msr = (cur << 4) | ((sc->sc_msr >> 4) ^ cur); +} + +static void +uchcom_update_status(struct uchcom_softc *sc) +{ + uint8_t cur; + + uchcom_get_status(sc, &cur); + uchcom_convert_status(sc, cur); +} + + +static void +uchcom_set_dtr_rts(struct uchcom_softc *sc) +{ + uint8_t val = 0; + + if (sc->sc_dtr) + val |= UCHCOM_DTR_MASK; + if (sc->sc_rts) + val |= UCHCOM_RTS_MASK; + + if (sc->sc_version < UCHCOM_VER_20) + uchcom_set_dtr_rts_10(sc, ~val); + else + uchcom_set_dtr_rts_20(sc, ~val); +} + +static void +uchcom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uchcom_softc *sc = ucom->sc_parent; + uint8_t brk1; + uint8_t brk2; + + uchcom_read_reg(sc, UCHCOM_REG_BREAK1, &brk1, UCHCOM_REG_BREAK2, &brk2); + if (onoff) { + /* on - clear bits */ + brk1 &= ~UCHCOM_BRK1_MASK; + brk2 &= ~UCHCOM_BRK2_MASK; + } else { + /* off - set bits */ + brk1 |= UCHCOM_BRK1_MASK; + brk2 |= UCHCOM_BRK2_MASK; + } + uchcom_write_reg(sc, UCHCOM_REG_BREAK1, brk1, UCHCOM_REG_BREAK2, brk2); +} + +static int +uchcom_calc_divider_settings(struct uchcom_divider *dp, uint32_t rate) +{ + const struct uchcom_divider_record *rp; + uint32_t div; + uint32_t rem; + uint32_t mod; + uint8_t i; + + /* find record */ + for (i = 0; i != NUM_DIVIDERS; i++) { + if (dividers[i].dvr_high >= rate && + dividers[i].dvr_low <= rate) { + rp = ÷rs[i]; + goto found; + } + } + return (-1); + +found: + dp->dv_prescaler = rp->dvr_divider.dv_prescaler; + if (rp->dvr_base_clock == UCHCOM_BASE_UNKNOWN) + dp->dv_div = rp->dvr_divider.dv_div; + else { + div = rp->dvr_base_clock / rate; + rem = rp->dvr_base_clock % rate; + if (div == 0 || div >= 0xFF) + return (-1); + if ((rem << 1) >= rate) + div += 1; + dp->dv_div = (uint8_t)-div; + } + + mod = (UCHCOM_BPS_MOD_BASE / rate) + UCHCOM_BPS_MOD_BASE_OFS; + mod = mod + (mod / 2); + + dp->dv_mod = (mod + 0xFF) / 0x100; + + return (0); +} + +static void +uchcom_set_baudrate(struct uchcom_softc *sc, uint32_t rate) +{ + struct uchcom_divider dv; + + if (uchcom_calc_divider_settings(&dv, rate)) + return; + + uchcom_write_reg(sc, + UCHCOM_REG_BPS_PRE, dv.dv_prescaler, + UCHCOM_REG_BPS_DIV, dv.dv_div); + uchcom_write_reg(sc, + UCHCOM_REG_BPS_MOD, dv.dv_mod, + UCHCOM_REG_BPS_PAD, 0); +} + +/* ---------------------------------------------------------------------- + * methods for ucom + */ +static void +uchcom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +uchcom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + sc->sc_dtr = onoff; + uchcom_set_dtr_rts(sc); +} + +static void +uchcom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + sc->sc_rts = onoff; + uchcom_set_dtr_rts(sc); +} + +static void +uchcom_cfg_open(struct ucom_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + uchcom_update_version(sc); + uchcom_update_status(sc); +} + +static int +uchcom_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uchcom_divider dv; + + switch (t->c_cflag & CSIZE) { + case CS8: + break; + default: + return (EIO); + } + + if (uchcom_calc_divider_settings(&dv, t->c_ospeed)) { + return (EIO); + } + return (0); /* success */ +} + +static void +uchcom_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + uchcom_get_version(sc, 0); + uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, 0, 0); + uchcom_set_baudrate(sc, t->c_ospeed); + uchcom_read_reg(sc, 0x18, 0, 0x25, 0); + uchcom_write_reg(sc, 0x18, 0x50, 0x25, 0x00); + uchcom_update_status(sc); + uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, 0x501f, 0xd90a); + uchcom_set_baudrate(sc, t->c_ospeed); + uchcom_set_dtr_rts(sc); + uchcom_update_status(sc); +} + +static void +uchcom_start_read(struct ucom_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint */ + usbd_transfer_start(sc->sc_xfer[UCHCOM_INTR_DT_RD]); + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UCHCOM_BULK_DT_RD]); +} + +static void +uchcom_stop_read(struct ucom_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint */ + usbd_transfer_stop(sc->sc_xfer[UCHCOM_INTR_DT_RD]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UCHCOM_BULK_DT_RD]); +} + +static void +uchcom_start_write(struct ucom_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UCHCOM_BULK_DT_WR]); +} + +static void +uchcom_stop_write(struct ucom_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UCHCOM_BULK_DT_WR]); +} + +/* ---------------------------------------------------------------------- + * callback when the modem status is changed. + */ +static void +uchcom_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uchcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[UCHCOM_INTR_LEAST]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("actlen = %u\n", actlen); + + if (actlen >= UCHCOM_INTR_LEAST) { + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, UCHCOM_INTR_LEAST); + + DPRINTF("data = 0x%02X 0x%02X 0x%02X 0x%02X\n", + (unsigned)buf[0], (unsigned)buf[1], + (unsigned)buf[2], (unsigned)buf[3]); + + uchcom_convert_status(sc, buf[UCHCOM_INTR_STAT1]); + ucom_status_change(&sc->sc_ucom); + } + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +uchcom_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uchcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + usbd_xfer_max_len(xfer), &actlen)) { + + DPRINTF("actlen = %d\n", actlen); + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +uchcom_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uchcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen > 0) { + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + } + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +uchcom_poll(struct ucom_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UCHCOM_N_TRANSFER); +} + +static device_method_t uchcom_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uchcom_probe), + DEVMETHOD(device_attach, uchcom_attach), + DEVMETHOD(device_detach, uchcom_detach), + + {0, 0} +}; + +static driver_t uchcom_driver = { + "ucom", + uchcom_methods, + sizeof(struct uchcom_softc) +}; + +static devclass_t uchcom_devclass; + +DRIVER_MODULE(uchcom, uhub, uchcom_driver, uchcom_devclass, NULL, 0); +MODULE_DEPEND(uchcom, ucom, 1, 1, 1); +MODULE_DEPEND(uchcom, usb, 1, 1, 1); +MODULE_VERSION(uchcom, 1); diff --git a/sys/bus/u4b/serial/ucycom.c b/sys/bus/u4b/serial/ucycom.c new file mode 100644 index 0000000000..4a35bf539a --- /dev/null +++ b/sys/bus/u4b/serial/ucycom.c @@ -0,0 +1,591 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2004 Dag-Erling Coïdan Smørgrav + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Device driver for Cypress CY7C637xx and CY7C640/1xx series USB to + * RS232 bridges. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR usb_debug +#include +#include + +#include + +#define UCYCOM_MAX_IOLEN (1024 + 2) /* bytes */ + +#define UCYCOM_IFACE_INDEX 0 + +enum { + UCYCOM_CTRL_RD, + UCYCOM_INTR_RD, + UCYCOM_N_TRANSFER, +}; + +struct ucycom_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_device *sc_udev; + struct usb_xfer *sc_xfer[UCYCOM_N_TRANSFER]; + struct mtx sc_mtx; + + uint32_t sc_model; +#define MODEL_CY7C63743 0x63743 +#define MODEL_CY7C64013 0x64013 + + uint16_t sc_flen; /* feature report length */ + uint16_t sc_ilen; /* input report length */ + uint16_t sc_olen; /* output report length */ + + uint8_t sc_fid; /* feature report id */ + uint8_t sc_iid; /* input report id */ + uint8_t sc_oid; /* output report id */ + uint8_t sc_cfg; +#define UCYCOM_CFG_RESET 0x80 +#define UCYCOM_CFG_PARODD 0x20 +#define UCYCOM_CFG_PAREN 0x10 +#define UCYCOM_CFG_STOPB 0x08 +#define UCYCOM_CFG_DATAB 0x03 + uint8_t sc_ist; /* status flags from last input */ + uint8_t sc_name[16]; + uint8_t sc_iface_no; + uint8_t sc_temp_cfg[32]; +}; + +/* prototypes */ + +static device_probe_t ucycom_probe; +static device_attach_t ucycom_attach; +static device_detach_t ucycom_detach; + +static usb_callback_t ucycom_ctrl_write_callback; +static usb_callback_t ucycom_intr_read_callback; + +static void ucycom_cfg_open(struct ucom_softc *); +static void ucycom_start_read(struct ucom_softc *); +static void ucycom_stop_read(struct ucom_softc *); +static void ucycom_start_write(struct ucom_softc *); +static void ucycom_stop_write(struct ucom_softc *); +static void ucycom_cfg_write(struct ucycom_softc *, uint32_t, uint8_t); +static int ucycom_pre_param(struct ucom_softc *, struct termios *); +static void ucycom_cfg_param(struct ucom_softc *, struct termios *); +static void ucycom_poll(struct ucom_softc *ucom); + +static const struct usb_config ucycom_config[UCYCOM_N_TRANSFER] = { + + [UCYCOM_CTRL_RD] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = (sizeof(struct usb_device_request) + UCYCOM_MAX_IOLEN), + .callback = &ucycom_ctrl_write_callback, + .timeout = 1000, /* 1 second */ + }, + + [UCYCOM_INTR_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = UCYCOM_MAX_IOLEN, + .callback = &ucycom_intr_read_callback, + }, +}; + +static const struct ucom_callback ucycom_callback = { + .ucom_cfg_param = &ucycom_cfg_param, + .ucom_cfg_open = &ucycom_cfg_open, + .ucom_pre_param = &ucycom_pre_param, + .ucom_start_read = &ucycom_start_read, + .ucom_stop_read = &ucycom_stop_read, + .ucom_start_write = &ucycom_start_write, + .ucom_stop_write = &ucycom_stop_write, + .ucom_poll = &ucycom_poll, +}; + +static device_method_t ucycom_methods[] = { + DEVMETHOD(device_probe, ucycom_probe), + DEVMETHOD(device_attach, ucycom_attach), + DEVMETHOD(device_detach, ucycom_detach), + {0, 0} +}; + +static devclass_t ucycom_devclass; + +static driver_t ucycom_driver = { + .name = "ucycom", + .methods = ucycom_methods, + .size = sizeof(struct ucycom_softc), +}; + +DRIVER_MODULE(ucycom, uhub, ucycom_driver, ucycom_devclass, NULL, 0); +MODULE_DEPEND(ucycom, ucom, 1, 1, 1); +MODULE_DEPEND(ucycom, usb, 1, 1, 1); +MODULE_VERSION(ucycom, 1); + +/* + * Supported devices + */ +static const STRUCT_USB_HOST_ID ucycom_devs[] = { + {USB_VPI(USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013)}, +}; + +#define UCYCOM_DEFAULT_RATE 4800 +#define UCYCOM_DEFAULT_CFG 0x03 /* N-8-1 */ + +static int +ucycom_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != 0) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UCYCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(ucycom_devs, sizeof(ucycom_devs), uaa)); +} + +static int +ucycom_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ucycom_softc *sc = device_get_softc(dev); + void *urd_ptr = NULL; + int32_t error; + uint16_t urd_len; + uint8_t iface_index; + + sc->sc_udev = uaa->device; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "ucycom", NULL, MTX_DEF); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + DPRINTF("\n"); + + /* get chip model */ + sc->sc_model = USB_GET_DRIVER_INFO(uaa); + if (sc->sc_model == 0) { + device_printf(dev, "unsupported device\n"); + goto detach; + } + device_printf(dev, "Cypress CY7C%X USB to RS232 bridge\n", sc->sc_model); + + /* get report descriptor */ + + error = usbd_req_get_hid_desc(uaa->device, NULL, + &urd_ptr, &urd_len, M_USBDEV, + UCYCOM_IFACE_INDEX); + + if (error) { + device_printf(dev, "failed to get report " + "descriptor: %s\n", + usbd_errstr(error)); + goto detach; + } + /* get report sizes */ + + sc->sc_flen = hid_report_size(urd_ptr, urd_len, hid_feature, &sc->sc_fid); + sc->sc_ilen = hid_report_size(urd_ptr, urd_len, hid_input, &sc->sc_iid); + sc->sc_olen = hid_report_size(urd_ptr, urd_len, hid_output, &sc->sc_oid); + + if ((sc->sc_ilen > UCYCOM_MAX_IOLEN) || (sc->sc_ilen < 1) || + (sc->sc_olen > UCYCOM_MAX_IOLEN) || (sc->sc_olen < 2) || + (sc->sc_flen > UCYCOM_MAX_IOLEN) || (sc->sc_flen < 5)) { + device_printf(dev, "invalid report size i=%d, o=%d, f=%d, max=%d\n", + sc->sc_ilen, sc->sc_olen, sc->sc_flen, + UCYCOM_MAX_IOLEN); + goto detach; + } + sc->sc_iface_no = uaa->info.bIfaceNum; + + iface_index = UCYCOM_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, ucycom_config, UCYCOM_N_TRANSFER, + sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB " + "transfers failed\n"); + goto detach; + } + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &ucycom_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + if (urd_ptr) { + free(urd_ptr, M_USBDEV); + } + + return (0); /* success */ + +detach: + if (urd_ptr) { + free(urd_ptr, M_USBDEV); + } + ucycom_detach(dev); + return (ENXIO); +} + +static int +ucycom_detach(device_t dev) +{ + struct ucycom_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UCYCOM_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +ucycom_cfg_open(struct ucom_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + /* set default configuration */ + ucycom_cfg_write(sc, UCYCOM_DEFAULT_RATE, UCYCOM_DEFAULT_CFG); +} + +static void +ucycom_start_read(struct ucom_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UCYCOM_INTR_RD]); +} + +static void +ucycom_stop_read(struct ucom_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UCYCOM_INTR_RD]); +} + +static void +ucycom_start_write(struct ucom_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UCYCOM_CTRL_RD]); +} + +static void +ucycom_stop_write(struct ucom_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UCYCOM_CTRL_RD]); +} + +static void +ucycom_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ucycom_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc0, *pc1; + uint8_t data[2]; + uint8_t offset; + uint32_t actlen; + + pc0 = usbd_xfer_get_frame(xfer, 0); + pc1 = usbd_xfer_get_frame(xfer, 1); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + case USB_ST_SETUP: + + switch (sc->sc_model) { + case MODEL_CY7C63743: + offset = 1; + break; + case MODEL_CY7C64013: + offset = 2; + break; + default: + offset = 0; + break; + } + + if (ucom_get_data(&sc->sc_ucom, pc1, offset, + sc->sc_olen - offset, &actlen)) { + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, UHID_OUTPUT_REPORT, sc->sc_oid); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, sc->sc_olen); + + switch (sc->sc_model) { + case MODEL_CY7C63743: + data[0] = actlen; + break; + case MODEL_CY7C64013: + data[0] = 0; + data[1] = actlen; + break; + default: + break; + } + + usbd_copy_in(pc0, 0, &req, sizeof(req)); + usbd_copy_in(pc1, 0, data, offset); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, sc->sc_olen); + usbd_xfer_set_frames(xfer, sc->sc_olen ? 2 : 1); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) { + return; + } + DPRINTF("error=%s\n", + usbd_errstr(error)); + goto tr_transferred; + } +} + +static void +ucycom_cfg_write(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg) +{ + struct usb_device_request req; + uint16_t len; + usb_error_t err; + + len = sc->sc_flen; + if (len > sizeof(sc->sc_temp_cfg)) { + len = sizeof(sc->sc_temp_cfg); + } + sc->sc_cfg = cfg; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, UHID_FEATURE_REPORT, sc->sc_fid); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, len); + + sc->sc_temp_cfg[0] = (baud & 0xff); + sc->sc_temp_cfg[1] = (baud >> 8) & 0xff; + sc->sc_temp_cfg[2] = (baud >> 16) & 0xff; + sc->sc_temp_cfg[3] = (baud >> 24) & 0xff; + sc->sc_temp_cfg[4] = cfg; + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, sc->sc_temp_cfg, 0, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usbd_errstr(err)); + } +} + +static int +ucycom_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + switch (t->c_ospeed) { + case 600: + case 1200: + case 2400: + case 4800: + case 9600: + case 19200: + case 38400: + case 57600: +#if 0 + /* + * Stock chips only support standard baud rates in the 600 - 57600 + * range, but higher rates can be achieved using custom firmware. + */ + case 115200: + case 153600: + case 192000: +#endif + break; + default: + return (EINVAL); + } + return (0); +} + +static void +ucycom_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct ucycom_softc *sc = ucom->sc_parent; + uint8_t cfg; + + DPRINTF("\n"); + + if (t->c_cflag & CIGNORE) { + cfg = sc->sc_cfg; + } else { + cfg = 0; + switch (t->c_cflag & CSIZE) { + default: + case CS8: + ++cfg; + case CS7: + ++cfg; + case CS6: + ++cfg; + case CS5: + break; + } + + if (t->c_cflag & CSTOPB) + cfg |= UCYCOM_CFG_STOPB; + if (t->c_cflag & PARENB) + cfg |= UCYCOM_CFG_PAREN; + if (t->c_cflag & PARODD) + cfg |= UCYCOM_CFG_PARODD; + } + + ucycom_cfg_write(sc, t->c_ospeed, cfg); +} + +static void +ucycom_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ucycom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[2]; + uint32_t offset; + uint32_t len; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + pc = usbd_xfer_get_frame(xfer, 0); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + switch (sc->sc_model) { + case MODEL_CY7C63743: + if (actlen < 1) { + goto tr_setup; + } + usbd_copy_out(pc, 0, buf, 1); + + sc->sc_ist = buf[0] & ~0x07; + len = buf[0] & 0x07; + + actlen--; + offset = 1; + + break; + + case MODEL_CY7C64013: + if (actlen < 2) { + goto tr_setup; + } + usbd_copy_out(pc, 0, buf, 2); + + sc->sc_ist = buf[0] & ~0x07; + len = buf[1]; + + actlen -= 2; + offset = 2; + + break; + + default: + DPRINTFN(0, "unsupported model number\n"); + goto tr_setup; + } + + if (len > actlen) + len = actlen; + if (len) + ucom_put_data(&sc->sc_ucom, pc, offset, len); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, sc->sc_ilen); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +ucycom_poll(struct ucom_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UCYCOM_N_TRANSFER); +} diff --git a/sys/bus/u4b/serial/ufoma.c b/sys/bus/u4b/serial/ufoma.c new file mode 100644 index 0000000000..7233f901c1 --- /dev/null +++ b/sys/bus/u4b/serial/ufoma.c @@ -0,0 +1,1260 @@ +/* $NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $ */ + +#include +__FBSDID("$FreeBSD$"); +#define UFOMA_HANDSFREE +/*- + * Copyright (c) 2005, Takanori Watanabe + * Copyright (c) 2003, M. Warner Losh . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf + * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf + */ + +/* + * TODO: + * - Implement a Call Device for modems without multiplexed commands. + */ + +/* + * NOTE: all function names beginning like "ufoma_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR usb_debug +#include +#include + +#include + +typedef struct ufoma_mobile_acm_descriptor { + uint8_t bFunctionLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bType; + uint8_t bMode[1]; +} __packed usb_mcpc_acm_descriptor; + +#define UISUBCLASS_MCPC 0x88 + +#define UDESC_VS_INTERFACE 0x44 +#define UDESCSUB_MCPC_ACM 0x11 + +#define UMCPC_ACM_TYPE_AB1 0x1 +#define UMCPC_ACM_TYPE_AB2 0x2 +#define UMCPC_ACM_TYPE_AB5 0x5 +#define UMCPC_ACM_TYPE_AB6 0x6 + +#define UMCPC_ACM_MODE_DEACTIVATED 0x0 +#define UMCPC_ACM_MODE_MODEM 0x1 +#define UMCPC_ACM_MODE_ATCOMMAND 0x2 +#define UMCPC_ACM_MODE_OBEX 0x60 +#define UMCPC_ACM_MODE_VENDOR1 0xc0 +#define UMCPC_ACM_MODE_VENDOR2 0xfe +#define UMCPC_ACM_MODE_UNLINKED 0xff + +#define UMCPC_CM_MOBILE_ACM 0x0 + +#define UMCPC_ACTIVATE_MODE 0x60 +#define UMCPC_GET_MODETABLE 0x61 +#define UMCPC_SET_LINK 0x62 +#define UMCPC_CLEAR_LINK 0x63 + +#define UMCPC_REQUEST_ACKNOWLEDGE 0x31 + +#define UFOMA_MAX_TIMEOUT 15 /* standard says 10 seconds */ +#define UFOMA_CMD_BUF_SIZE 64 /* bytes */ + +#define UFOMA_BULK_BUF_SIZE 1024 /* bytes */ + +enum { + UFOMA_CTRL_ENDPT_INTR, + UFOMA_CTRL_ENDPT_READ, + UFOMA_CTRL_ENDPT_WRITE, + UFOMA_CTRL_ENDPT_MAX, +}; + +enum { + UFOMA_BULK_ENDPT_WRITE, + UFOMA_BULK_ENDPT_READ, + UFOMA_BULK_ENDPT_MAX, +}; + +struct ufoma_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + struct cv sc_cv; + struct mtx sc_mtx; + + struct usb_xfer *sc_ctrl_xfer[UFOMA_CTRL_ENDPT_MAX]; + struct usb_xfer *sc_bulk_xfer[UFOMA_BULK_ENDPT_MAX]; + uint8_t *sc_modetable; + device_t sc_dev; + struct usb_device *sc_udev; + + uint32_t sc_unit; + + uint16_t sc_line; + + uint8_t sc_num_msg; + uint8_t sc_nobulk; + uint8_t sc_ctrl_iface_no; + uint8_t sc_ctrl_iface_index; + uint8_t sc_data_iface_no; + uint8_t sc_data_iface_index; + uint8_t sc_cm_cap; + uint8_t sc_acm_cap; + uint8_t sc_lsr; + uint8_t sc_msr; + uint8_t sc_modetoactivate; + uint8_t sc_currentmode; + uint8_t sc_name[16]; +}; + +/* prototypes */ + +static device_probe_t ufoma_probe; +static device_attach_t ufoma_attach; +static device_detach_t ufoma_detach; + +static usb_callback_t ufoma_ctrl_read_callback; +static usb_callback_t ufoma_ctrl_write_callback; +static usb_callback_t ufoma_intr_callback; +static usb_callback_t ufoma_bulk_write_callback; +static usb_callback_t ufoma_bulk_read_callback; + +static void *ufoma_get_intconf(struct usb_config_descriptor *, + struct usb_interface_descriptor *, uint8_t, uint8_t); +static void ufoma_cfg_link_state(struct ufoma_softc *); +static void ufoma_cfg_activate_state(struct ufoma_softc *, uint16_t); +static void ufoma_cfg_open(struct ucom_softc *); +static void ufoma_cfg_close(struct ucom_softc *); +static void ufoma_cfg_set_break(struct ucom_softc *, uint8_t); +static void ufoma_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void ufoma_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void ufoma_cfg_set_rts(struct ucom_softc *, uint8_t); +static int ufoma_pre_param(struct ucom_softc *, struct termios *); +static void ufoma_cfg_param(struct ucom_softc *, struct termios *); +static int ufoma_modem_setup(device_t, struct ufoma_softc *, + struct usb_attach_arg *); +static void ufoma_start_read(struct ucom_softc *); +static void ufoma_stop_read(struct ucom_softc *); +static void ufoma_start_write(struct ucom_softc *); +static void ufoma_stop_write(struct ucom_softc *); +static void ufoma_poll(struct ucom_softc *ucom); + +/*sysctl stuff*/ +static int ufoma_sysctl_support(SYSCTL_HANDLER_ARGS); +static int ufoma_sysctl_current(SYSCTL_HANDLER_ARGS); +static int ufoma_sysctl_open(SYSCTL_HANDLER_ARGS); + +static const struct usb_config + ufoma_ctrl_config[UFOMA_CTRL_ENDPT_MAX] = { + + [UFOMA_CTRL_ENDPT_INTR] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = sizeof(struct usb_cdc_notification), + .callback = &ufoma_intr_callback, + }, + + [UFOMA_CTRL_ENDPT_READ] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = (sizeof(struct usb_device_request) + UFOMA_CMD_BUF_SIZE), + .flags = {.short_xfer_ok = 1,}, + .callback = &ufoma_ctrl_read_callback, + .timeout = 1000, /* 1 second */ + }, + + [UFOMA_CTRL_ENDPT_WRITE] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = (sizeof(struct usb_device_request) + 1), + .callback = &ufoma_ctrl_write_callback, + .timeout = 1000, /* 1 second */ + }, +}; + +static const struct usb_config + ufoma_bulk_config[UFOMA_BULK_ENDPT_MAX] = { + + [UFOMA_BULK_ENDPT_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UFOMA_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &ufoma_bulk_write_callback, + }, + + [UFOMA_BULK_ENDPT_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UFOMA_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &ufoma_bulk_read_callback, + }, +}; + +static const struct ucom_callback ufoma_callback = { + .ucom_cfg_get_status = &ufoma_cfg_get_status, + .ucom_cfg_set_dtr = &ufoma_cfg_set_dtr, + .ucom_cfg_set_rts = &ufoma_cfg_set_rts, + .ucom_cfg_set_break = &ufoma_cfg_set_break, + .ucom_cfg_param = &ufoma_cfg_param, + .ucom_cfg_open = &ufoma_cfg_open, + .ucom_cfg_close = &ufoma_cfg_close, + .ucom_pre_param = &ufoma_pre_param, + .ucom_start_read = &ufoma_start_read, + .ucom_stop_read = &ufoma_stop_read, + .ucom_start_write = &ufoma_start_write, + .ucom_stop_write = &ufoma_stop_write, + .ucom_poll = &ufoma_poll, +}; + +static device_method_t ufoma_methods[] = { + /* Device methods */ + DEVMETHOD(device_probe, ufoma_probe), + DEVMETHOD(device_attach, ufoma_attach), + DEVMETHOD(device_detach, ufoma_detach), + {0, 0} +}; + +static devclass_t ufoma_devclass; + +static driver_t ufoma_driver = { + .name = "ufoma", + .methods = ufoma_methods, + .size = sizeof(struct ufoma_softc), +}; + +DRIVER_MODULE(ufoma, uhub, ufoma_driver, ufoma_devclass, NULL, 0); +MODULE_DEPEND(ufoma, ucom, 1, 1, 1); +MODULE_DEPEND(ufoma, usb, 1, 1, 1); +MODULE_VERSION(ufoma, 1); + +static const STRUCT_USB_HOST_ID ufoma_devs[] = { + {USB_IFACE_CLASS(UICLASS_CDC), + USB_IFACE_SUBCLASS(UISUBCLASS_MCPC),}, +}; + +static int +ufoma_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct usb_interface_descriptor *id; + struct usb_config_descriptor *cd; + usb_mcpc_acm_descriptor *mad; + int error; + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + + error = usbd_lookup_id_by_uaa(ufoma_devs, sizeof(ufoma_devs), uaa); + if (error) + return (error); + + id = usbd_get_interface_descriptor(uaa->iface); + cd = usbd_get_config_descriptor(uaa->device); + + if (id == NULL || cd == NULL) + return (ENXIO); + + mad = ufoma_get_intconf(cd, id, UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM); + if (mad == NULL) + return (ENXIO); + +#ifndef UFOMA_HANDSFREE + if ((mad->bType == UMCPC_ACM_TYPE_AB5) || + (mad->bType == UMCPC_ACM_TYPE_AB6)) + return (ENXIO); +#endif + return (BUS_PROBE_GENERIC); +} + +static int +ufoma_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ufoma_softc *sc = device_get_softc(dev); + struct usb_config_descriptor *cd; + struct usb_interface_descriptor *id; + struct sysctl_ctx_list *sctx; + struct sysctl_oid *soid; + + usb_mcpc_acm_descriptor *mad; + uint8_t elements; + int32_t error; + + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + + mtx_init(&sc->sc_mtx, "ufoma", NULL, MTX_DEF); + cv_init(&sc->sc_cv, "CWAIT"); + + device_set_usb_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + DPRINTF("\n"); + + /* setup control transfers */ + + cd = usbd_get_config_descriptor(uaa->device); + id = usbd_get_interface_descriptor(uaa->iface); + sc->sc_ctrl_iface_no = id->bInterfaceNumber; + sc->sc_ctrl_iface_index = uaa->info.bIfaceIndex; + + error = usbd_transfer_setup(uaa->device, + &sc->sc_ctrl_iface_index, sc->sc_ctrl_xfer, + ufoma_ctrl_config, UFOMA_CTRL_ENDPT_MAX, sc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating control USB " + "transfers failed\n"); + goto detach; + } + mad = ufoma_get_intconf(cd, id, UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM); + if (mad == NULL) { + goto detach; + } + if (mad->bFunctionLength < sizeof(*mad)) { + device_printf(dev, "invalid MAD descriptor\n"); + goto detach; + } + if ((mad->bType == UMCPC_ACM_TYPE_AB5) || + (mad->bType == UMCPC_ACM_TYPE_AB6)) { + sc->sc_nobulk = 1; + } else { + sc->sc_nobulk = 0; + if (ufoma_modem_setup(dev, sc, uaa)) { + goto detach; + } + } + + elements = (mad->bFunctionLength - sizeof(*mad) + 1); + + /* initialize mode variables */ + + sc->sc_modetable = malloc(elements + 1, M_USBDEV, M_WAITOK); + + if (sc->sc_modetable == NULL) { + goto detach; + } + sc->sc_modetable[0] = (elements + 1); + memcpy(&sc->sc_modetable[1], mad->bMode, elements); + + sc->sc_currentmode = UMCPC_ACM_MODE_UNLINKED; + sc->sc_modetoactivate = mad->bMode[0]; + + /* clear stall at first run, if any */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]); + usbd_xfer_set_stall(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &ufoma_callback, &sc->sc_mtx); + if (error) { + DPRINTF("ucom_attach failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + /*Sysctls*/ + sctx = device_get_sysctl_ctx(dev); + soid = device_get_sysctl_tree(dev); + + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "supportmode", + CTLFLAG_RD|CTLTYPE_STRING, sc, 0, ufoma_sysctl_support, + "A", "Supporting port role"); + + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "currentmode", + CTLFLAG_RD|CTLTYPE_STRING, sc, 0, ufoma_sysctl_current, + "A", "Current port role"); + + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "openmode", + CTLFLAG_RW|CTLTYPE_STRING, sc, 0, ufoma_sysctl_open, + "A", "Mode to transit when port is opened"); + SYSCTL_ADD_UINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "comunit", + CTLFLAG_RD, &(sc->sc_super_ucom.sc_unit), 0, + "Unit number as USB serial"); + + return (0); /* success */ + +detach: + ufoma_detach(dev); + return (ENXIO); /* failure */ +} + +static int +ufoma_detach(device_t dev) +{ + struct ufoma_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_ctrl_xfer, UFOMA_CTRL_ENDPT_MAX); + usbd_transfer_unsetup(sc->sc_bulk_xfer, UFOMA_BULK_ENDPT_MAX); + + if (sc->sc_modetable) { + free(sc->sc_modetable, M_USBDEV); + } + mtx_destroy(&sc->sc_mtx); + cv_destroy(&sc->sc_cv); + + return (0); +} + +static void * +ufoma_get_intconf(struct usb_config_descriptor *cd, struct usb_interface_descriptor *id, + uint8_t type, uint8_t subtype) +{ + struct usb_descriptor *desc = (void *)id; + + while ((desc = usb_desc_foreach(cd, desc))) { + + if (desc->bDescriptorType == UDESC_INTERFACE) { + return (NULL); + } + if ((desc->bDescriptorType == type) && + (desc->bDescriptorSubtype == subtype)) { + break; + } + } + return (desc); +} + +static void +ufoma_cfg_link_state(struct ufoma_softc *sc) +{ + struct usb_device_request req; + int32_t error; + + req.bmRequestType = UT_WRITE_VENDOR_INTERFACE; + req.bRequest = UMCPC_SET_LINK; + USETW(req.wValue, UMCPC_CM_MOBILE_ACM); + USETW(req.wIndex, sc->sc_ctrl_iface_no); + USETW(req.wLength, sc->sc_modetable[0]); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, sc->sc_modetable, 0, 1000); + + error = cv_timedwait(&sc->sc_cv, &sc->sc_mtx, hz); + + if (error) { + DPRINTF("NO response\n"); + } +} + +static void +ufoma_cfg_activate_state(struct ufoma_softc *sc, uint16_t state) +{ + struct usb_device_request req; + int32_t error; + + req.bmRequestType = UT_WRITE_VENDOR_INTERFACE; + req.bRequest = UMCPC_ACTIVATE_MODE; + USETW(req.wValue, state); + USETW(req.wIndex, sc->sc_ctrl_iface_no); + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + + error = cv_timedwait(&sc->sc_cv, &sc->sc_mtx, + (UFOMA_MAX_TIMEOUT * hz)); + if (error) { + DPRINTF("No response\n"); + } +} + +static void +ufoma_ctrl_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ufoma_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc0, *pc1; + int len, aframes, nframes; + + usbd_xfer_status(xfer, NULL, NULL, &aframes, &nframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + if (aframes != nframes) + goto tr_setup; + pc1 = usbd_xfer_get_frame(xfer, 1); + len = usbd_xfer_frame_len(xfer, 1); + if (len > 0) + ucom_put_data(&sc->sc_ucom, pc1, 0, len); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + if (sc->sc_num_msg) { + sc->sc_num_msg--; + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE; + USETW(req.wIndex, sc->sc_ctrl_iface_no); + USETW(req.wValue, 0); + USETW(req.wLength, UFOMA_CMD_BUF_SIZE); + + pc0 = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc0, 0, &req, sizeof(req)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, UFOMA_CMD_BUF_SIZE); + usbd_xfer_set_frames(xfer, 2); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + DPRINTF("error = %s\n", + usbd_errstr(error)); + + if (error == USB_ERR_CANCELLED) { + return; + } else { + goto tr_setup; + } + + goto tr_transferred; + } +} + +static void +ufoma_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ufoma_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + case USB_ST_SETUP: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 1); + if (ucom_get_data(&sc->sc_ucom, pc, 0, 1, &actlen)) { + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND; + USETW(req.wIndex, sc->sc_ctrl_iface_no); + USETW(req.wValue, 0); + USETW(req.wLength, 1); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, 1); + usbd_xfer_set_frames(xfer, 2); + + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + DPRINTF("error = %s\n", usbd_errstr(error)); + + if (error == USB_ERR_CANCELLED) { + return; + } else { + goto tr_setup; + } + + goto tr_transferred; + } +} + +static void +ufoma_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ufoma_softc *sc = usbd_xfer_softc(xfer); + struct usb_cdc_notification pkt; + struct usb_page_cache *pc; + uint16_t wLen; + uint16_t temp; + uint8_t mstatus; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen < 8) { + DPRINTF("too short message\n"); + goto tr_setup; + } + if (actlen > sizeof(pkt)) { + DPRINTF("truncating message\n"); + actlen = sizeof(pkt); + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &pkt, actlen); + + actlen -= 8; + + wLen = UGETW(pkt.wLength); + if (actlen > wLen) { + actlen = wLen; + } + if ((pkt.bmRequestType == UT_READ_VENDOR_INTERFACE) && + (pkt.bNotification == UMCPC_REQUEST_ACKNOWLEDGE)) { + temp = UGETW(pkt.wValue); + sc->sc_currentmode = (temp >> 8); + if (!(temp & 0xff)) { + DPRINTF("Mode change failed!\n"); + } + cv_signal(&sc->sc_cv); + } + if (pkt.bmRequestType != UCDC_NOTIFICATION) { + goto tr_setup; + } + switch (pkt.bNotification) { + case UCDC_N_RESPONSE_AVAILABLE: + if (!(sc->sc_nobulk)) { + DPRINTF("Wrong serial state!\n"); + break; + } + if (sc->sc_num_msg != 0xFF) { + sc->sc_num_msg++; + } + usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]); + break; + + case UCDC_N_SERIAL_STATE: + if (sc->sc_nobulk) { + DPRINTF("Wrong serial state!\n"); + break; + } + /* + * Set the serial state in ucom driver based on + * the bits from the notify message + */ + if (actlen < 2) { + DPRINTF("invalid notification " + "length, %d bytes!\n", actlen); + break; + } + DPRINTF("notify bytes = 0x%02x, 0x%02x\n", + pkt.data[0], pkt.data[1]); + + /* currently, lsr is always zero. */ + sc->sc_lsr = 0; + sc->sc_msr = 0; + + mstatus = pkt.data[0]; + + if (mstatus & UCDC_N_SERIAL_RI) { + sc->sc_msr |= SER_RI; + } + if (mstatus & UCDC_N_SERIAL_DSR) { + sc->sc_msr |= SER_DSR; + } + if (mstatus & UCDC_N_SERIAL_DCD) { + sc->sc_msr |= SER_DCD; + } + ucom_status_change(&sc->sc_ucom); + break; + + default: + break; + } + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +ufoma_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ufoma_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UFOMA_BULK_BUF_SIZE, &actlen)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +ufoma_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ufoma_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +ufoma_cfg_open(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + /* empty input queue */ + + if (sc->sc_num_msg != 0xFF) { + sc->sc_num_msg++; + } + if (sc->sc_currentmode == UMCPC_ACM_MODE_UNLINKED) { + ufoma_cfg_link_state(sc); + } + if (sc->sc_currentmode == UMCPC_ACM_MODE_DEACTIVATED) { + ufoma_cfg_activate_state(sc, sc->sc_modetoactivate); + } +} + +static void +ufoma_cfg_close(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + ufoma_cfg_activate_state(sc, UMCPC_ACM_MODE_DEACTIVATED); +} + +static void +ufoma_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ufoma_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t wValue; + + if (sc->sc_nobulk || + (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX)) { + return; + } + if (!(sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK)) { + return; + } + wValue = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, wValue); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +ufoma_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +ufoma_cfg_set_line_state(struct ufoma_softc *sc) +{ + struct usb_device_request req; + + /* Don't send line state emulation request for OBEX port */ + if (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX) { + return; + } + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +ufoma_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + if (sc->sc_nobulk) { + return; + } + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + ufoma_cfg_set_line_state(sc); +} + +static void +ufoma_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + if (sc->sc_nobulk) { + return; + } + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + ufoma_cfg_set_line_state(sc); +} + +static int +ufoma_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + return (0); /* we accept anything */ +} + +static void +ufoma_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct ufoma_softc *sc = ucom->sc_parent; + struct usb_device_request req; + struct usb_cdc_line_state ls; + + if (sc->sc_nobulk || + (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX)) { + return; + } + DPRINTF("\n"); + + memset(&ls, 0, sizeof(ls)); + + USETDW(ls.dwDTERate, t->c_ospeed); + + if (t->c_cflag & CSTOPB) { + ls.bCharFormat = UCDC_STOP_BIT_2; + } else { + ls.bCharFormat = UCDC_STOP_BIT_1; + } + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) { + ls.bParityType = UCDC_PARITY_ODD; + } else { + ls.bParityType = UCDC_PARITY_EVEN; + } + } else { + ls.bParityType = UCDC_PARITY_NONE; + } + + switch (t->c_cflag & CSIZE) { + case CS5: + ls.bDataBits = 5; + break; + case CS6: + ls.bDataBits = 6; + break; + case CS7: + ls.bDataBits = 7; + break; + case CS8: + ls.bDataBits = 8; + break; + } + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_LINE_CODING; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, UCDC_LINE_STATE_LENGTH); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, &ls, 0, 1000); +} + +static int +ufoma_modem_setup(device_t dev, struct ufoma_softc *sc, + struct usb_attach_arg *uaa) +{ + struct usb_config_descriptor *cd; + struct usb_cdc_acm_descriptor *acm; + struct usb_cdc_cm_descriptor *cmd; + struct usb_interface_descriptor *id; + struct usb_interface *iface; + uint8_t i; + int32_t error; + + cd = usbd_get_config_descriptor(uaa->device); + id = usbd_get_interface_descriptor(uaa->iface); + + cmd = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); + + if ((cmd == NULL) || + (cmd->bLength < sizeof(*cmd))) { + return (EINVAL); + } + sc->sc_cm_cap = cmd->bmCapabilities; + sc->sc_data_iface_no = cmd->bDataInterface; + + acm = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM); + + if ((acm == NULL) || + (acm->bLength < sizeof(*acm))) { + return (EINVAL); + } + sc->sc_acm_cap = acm->bmCapabilities; + + device_printf(dev, "data interface %d, has %sCM over data, " + "has %sbreak\n", + sc->sc_data_iface_no, + sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ", + sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no "); + + /* get the data interface too */ + + for (i = 0;; i++) { + + iface = usbd_get_iface(uaa->device, i); + + if (iface) { + + id = usbd_get_interface_descriptor(iface); + + if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) { + sc->sc_data_iface_index = i; + usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); + break; + } + } else { + device_printf(dev, "no data interface\n"); + return (EINVAL); + } + } + + error = usbd_transfer_setup(uaa->device, + &sc->sc_data_iface_index, sc->sc_bulk_xfer, + ufoma_bulk_config, UFOMA_BULK_ENDPT_MAX, sc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating BULK USB " + "transfers failed\n"); + return (EINVAL); + } + return (0); +} + +static void +ufoma_start_read(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + /* start interrupt transfer */ + usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_INTR]); + + /* start data transfer */ + if (sc->sc_nobulk) { + usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]); + } else { + usbd_transfer_start(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]); + } +} + +static void +ufoma_stop_read(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + /* stop interrupt transfer */ + usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_INTR]); + + /* stop data transfer */ + if (sc->sc_nobulk) { + usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]); + } else { + usbd_transfer_stop(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]); + } +} + +static void +ufoma_start_write(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + if (sc->sc_nobulk) { + usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_WRITE]); + } else { + usbd_transfer_start(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]); + } +} + +static void +ufoma_stop_write(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + if (sc->sc_nobulk) { + usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_WRITE]); + } else { + usbd_transfer_stop(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]); + } +} + +static struct umcpc_modetostr_tab{ + int mode; + char *str; +}umcpc_modetostr_tab[]={ + {UMCPC_ACM_MODE_DEACTIVATED, "deactivated"}, + {UMCPC_ACM_MODE_MODEM, "modem"}, + {UMCPC_ACM_MODE_ATCOMMAND, "handsfree"}, + {UMCPC_ACM_MODE_OBEX, "obex"}, + {UMCPC_ACM_MODE_VENDOR1, "vendor1"}, + {UMCPC_ACM_MODE_VENDOR2, "vendor2"}, + {UMCPC_ACM_MODE_UNLINKED, "unlinked"}, + {0, NULL} +}; + +static char *ufoma_mode_to_str(int mode) +{ + int i; + for(i = 0 ;umcpc_modetostr_tab[i].str != NULL; i++){ + if(umcpc_modetostr_tab[i].mode == mode){ + return umcpc_modetostr_tab[i].str; + } + } + return NULL; +} + +static int ufoma_str_to_mode(char *str) +{ + int i; + for(i = 0 ;umcpc_modetostr_tab[i].str != NULL; i++){ + if(strcmp(str, umcpc_modetostr_tab[i].str)==0){ + return umcpc_modetostr_tab[i].mode; + } + } + return -1; +} + +static int ufoma_sysctl_support(SYSCTL_HANDLER_ARGS) +{ + struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1; + struct sbuf sb; + int i; + char *mode; + + sbuf_new(&sb, NULL, 1, SBUF_AUTOEXTEND); + for(i = 1; i < sc->sc_modetable[0]; i++){ + mode = ufoma_mode_to_str(sc->sc_modetable[i]); + if(mode !=NULL){ + sbuf_cat(&sb, mode); + }else{ + sbuf_printf(&sb, "(%02x)", sc->sc_modetable[i]); + } + if(i < (sc->sc_modetable[0]-1)) + sbuf_cat(&sb, ","); + } + sbuf_trim(&sb); + sbuf_finish(&sb); + sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); + sbuf_delete(&sb); + + return 0; +} +static int ufoma_sysctl_current(SYSCTL_HANDLER_ARGS) +{ + struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1; + char *mode; + char subbuf[]="(XXX)"; + mode = ufoma_mode_to_str(sc->sc_currentmode); + if(!mode){ + mode = subbuf; + snprintf(subbuf, sizeof(subbuf), "(%02x)", sc->sc_currentmode); + } + sysctl_handle_string(oidp, mode, strlen(mode), req); + + return 0; + +} +static int ufoma_sysctl_open(SYSCTL_HANDLER_ARGS) +{ + struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1; + char *mode; + char subbuf[40]; + int newmode; + int error; + int i; + + mode = ufoma_mode_to_str(sc->sc_modetoactivate); + if(mode){ + strncpy(subbuf, mode, sizeof(subbuf)); + }else{ + snprintf(subbuf, sizeof(subbuf), "(%02x)", sc->sc_modetoactivate); + } + error = sysctl_handle_string(oidp, subbuf, sizeof(subbuf), req); + if(error != 0 || req->newptr == NULL){ + return error; + } + + if((newmode = ufoma_str_to_mode(subbuf)) == -1){ + return EINVAL; + } + + for(i = 1 ; i < sc->sc_modetable[0] ; i++){ + if(sc->sc_modetable[i] == newmode){ + sc->sc_modetoactivate = newmode; + return 0; + } + } + + return EINVAL; +} + +static void +ufoma_poll(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_ctrl_xfer, UFOMA_CTRL_ENDPT_MAX); + usbd_transfer_poll(sc->sc_bulk_xfer, UFOMA_BULK_ENDPT_MAX); +} diff --git a/sys/bus/u4b/serial/uftdi.c b/sys/bus/u4b/serial/uftdi.c new file mode 100644 index 0000000000..85b06ff72d --- /dev/null +++ b/sys/bus/u4b/serial/uftdi.c @@ -0,0 +1,828 @@ +/* $NetBSD: uftdi.c,v 1.13 2002/09/23 05:51:23 simonb Exp $ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * NOTE: all function names beginning like "uftdi_cfg_" can only + * be called from within the config thread function ! + */ + +/* + * FTDI FT8U100AX serial adapter driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR uftdi_debug +#include +#include + +#include +#include + +#ifdef USB_DEBUG +static int uftdi_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uftdi, CTLFLAG_RW, 0, "USB uftdi"); +SYSCTL_INT(_hw_usb_uftdi, OID_AUTO, debug, CTLFLAG_RW, + &uftdi_debug, 0, "Debug level"); +#endif + +#define UFTDI_CONFIG_INDEX 0 +#define UFTDI_IFACE_INDEX 0 + +#define UFTDI_OBUFSIZE 64 /* bytes, cannot be increased due to + * do size encoding */ + +enum { + UFTDI_BULK_DT_WR, + UFTDI_BULK_DT_RD, + UFTDI_N_TRANSFER, +}; + +struct uftdi_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_device *sc_udev; + struct usb_xfer *sc_xfer[UFTDI_N_TRANSFER]; + device_t sc_dev; + struct mtx sc_mtx; + + uint32_t sc_unit; + enum uftdi_type sc_type; + + uint16_t sc_last_lcr; + + uint8_t sc_iface_index; + uint8_t sc_hdrlen; + uint8_t sc_msr; + uint8_t sc_lsr; + + uint8_t sc_name[16]; +}; + +struct uftdi_param_config { + uint16_t rate; + uint16_t lcr; + uint8_t v_start; + uint8_t v_stop; + uint8_t v_flow; +}; + +/* prototypes */ + +static device_probe_t uftdi_probe; +static device_attach_t uftdi_attach; +static device_detach_t uftdi_detach; + +static usb_callback_t uftdi_write_callback; +static usb_callback_t uftdi_read_callback; + +static void uftdi_cfg_open(struct ucom_softc *); +static void uftdi_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void uftdi_cfg_set_rts(struct ucom_softc *, uint8_t); +static void uftdi_cfg_set_break(struct ucom_softc *, uint8_t); +static int uftdi_set_parm_soft(struct termios *, + struct uftdi_param_config *, uint8_t); +static int uftdi_pre_param(struct ucom_softc *, struct termios *); +static void uftdi_cfg_param(struct ucom_softc *, struct termios *); +static void uftdi_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void uftdi_start_read(struct ucom_softc *); +static void uftdi_stop_read(struct ucom_softc *); +static void uftdi_start_write(struct ucom_softc *); +static void uftdi_stop_write(struct ucom_softc *); +static uint8_t uftdi_8u232am_getrate(uint32_t, uint16_t *); +static void uftdi_poll(struct ucom_softc *ucom); + +static const struct usb_config uftdi_config[UFTDI_N_TRANSFER] = { + + [UFTDI_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UFTDI_OBUFSIZE, + .flags = {.pipe_bof = 1,}, + .callback = &uftdi_write_callback, + }, + + [UFTDI_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = 0, /* use wMaxPacketSize */ + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uftdi_read_callback, + }, +}; + +static const struct ucom_callback uftdi_callback = { + .ucom_cfg_get_status = &uftdi_cfg_get_status, + .ucom_cfg_set_dtr = &uftdi_cfg_set_dtr, + .ucom_cfg_set_rts = &uftdi_cfg_set_rts, + .ucom_cfg_set_break = &uftdi_cfg_set_break, + .ucom_cfg_param = &uftdi_cfg_param, + .ucom_cfg_open = &uftdi_cfg_open, + .ucom_pre_param = &uftdi_pre_param, + .ucom_start_read = &uftdi_start_read, + .ucom_stop_read = &uftdi_stop_read, + .ucom_start_write = &uftdi_start_write, + .ucom_stop_write = &uftdi_stop_write, + .ucom_poll = &uftdi_poll, +}; + +static device_method_t uftdi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uftdi_probe), + DEVMETHOD(device_attach, uftdi_attach), + DEVMETHOD(device_detach, uftdi_detach), + + {0, 0} +}; + +static devclass_t uftdi_devclass; + +static driver_t uftdi_driver = { + .name = "uftdi", + .methods = uftdi_methods, + .size = sizeof(struct uftdi_softc), +}; + +DRIVER_MODULE(uftdi, uhub, uftdi_driver, uftdi_devclass, NULL, 0); +MODULE_DEPEND(uftdi, ucom, 1, 1, 1); +MODULE_DEPEND(uftdi, usb, 1, 1, 1); +MODULE_VERSION(uftdi, 1); + +static STRUCT_USB_HOST_ID uftdi_devs[] = { +#define UFTDI_DEV(v,p,t) \ + { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, UFTDI_TYPE_##t) } + UFTDI_DEV(ATMEL, STK541, 8U232AM), + UFTDI_DEV(DRESDENELEKTRONIK, SENSORTERMINALBOARD, 8U232AM), + UFTDI_DEV(DRESDENELEKTRONIK, WIRELESSHANDHELDTERMINAL, 8U232AM), + UFTDI_DEV(FALCOM, TWIST, 8U232AM), + UFTDI_DEV(FTDI, GAMMASCOUT, 8U232AM), + UFTDI_DEV(FTDI, SERIAL_8U100AX, SIO), + UFTDI_DEV(FTDI, SERIAL_2232C, 8U232AM), + UFTDI_DEV(FTDI, SERIAL_2232D, 8U232AM), + UFTDI_DEV(FTDI, BEAGLEBONE, 8U232AM), + UFTDI_DEV(FTDI, SERIAL_4232H, 8U232AM), + UFTDI_DEV(FTDI, SERIAL_8U232AM, 8U232AM), + UFTDI_DEV(FTDI, SERIAL_8U232AM4, 8U232AM), + UFTDI_DEV(FTDI, SERIAL_BEAGLEBONE, 8U232AM), + UFTDI_DEV(FTDI, SEMC_DSS20, 8U232AM), + UFTDI_DEV(FTDI, CFA_631, 8U232AM), + UFTDI_DEV(FTDI, CFA_632, 8U232AM), + UFTDI_DEV(FTDI, CFA_633, 8U232AM), + UFTDI_DEV(FTDI, CFA_634, 8U232AM), + UFTDI_DEV(FTDI, CFA_635, 8U232AM), + UFTDI_DEV(FTDI, USB_UIRT, 8U232AM), + UFTDI_DEV(FTDI, USBSERIAL, 8U232AM), + UFTDI_DEV(FTDI, KBS, 8U232AM), + UFTDI_DEV(FTDI, MX2_3, 8U232AM), + UFTDI_DEV(FTDI, MX4_5, 8U232AM), + UFTDI_DEV(FTDI, LK202, 8U232AM), + UFTDI_DEV(FTDI, LK204, 8U232AM), + UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13M, 8U232AM), + UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13S, 8U232AM), + UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13U, 8U232AM), + UFTDI_DEV(FTDI, EISCOU, 8U232AM), + UFTDI_DEV(FTDI, UOPTBR, 8U232AM), + UFTDI_DEV(FTDI, EMCU2D, 8U232AM), + UFTDI_DEV(FTDI, PCMSFU, 8U232AM), + UFTDI_DEV(FTDI, EMCU2H, 8U232AM), + UFTDI_DEV(FTDI, MAXSTREAM, 8U232AM), + UFTDI_DEV(FTDI, CTI_USB_NANO_485, 8U232AM), + UFTDI_DEV(FTDI, CTI_USB_MINI_485, 8U232AM), + UFTDI_DEV(SIIG2, US2308, 8U232AM), + UFTDI_DEV(INTREPIDCS, VALUECAN, 8U232AM), + UFTDI_DEV(INTREPIDCS, NEOVI, 8U232AM), + UFTDI_DEV(BBELECTRONICS, USOTL4, 8U232AM), + UFTDI_DEV(MATRIXORBITAL, MOUA, 8U232AM), + UFTDI_DEV(MARVELL, SHEEVAPLUG, 8U232AM), + UFTDI_DEV(MELCO, PCOPRS1, 8U232AM), + UFTDI_DEV(RATOC, REXUSB60F, 8U232AM), +#undef UFTDI_DEV +}; + +static int +uftdi_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UFTDI_CONFIG_INDEX) { + return (ENXIO); + } + /* attach to all present interfaces */ + + return (usbd_lookup_id_by_uaa(uftdi_devs, sizeof(uftdi_devs), uaa)); +} + +static int +uftdi_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uftdi_softc *sc = device_get_softc(dev); + int error; + + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uftdi", NULL, MTX_DEF); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + DPRINTF("\n"); + + sc->sc_iface_index = uaa->info.bIfaceIndex; + sc->sc_type = USB_GET_DRIVER_INFO(uaa); + + switch (sc->sc_type) { + case UFTDI_TYPE_SIO: + sc->sc_hdrlen = 1; + break; + case UFTDI_TYPE_8U232AM: + default: + sc->sc_hdrlen = 0; + break; + } + + error = usbd_transfer_setup(uaa->device, + &sc->sc_iface_index, sc->sc_xfer, uftdi_config, + UFTDI_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating USB " + "transfers failed\n"); + goto detach; + } + sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; + + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + /* set a valid "lcr" value */ + + sc->sc_last_lcr = + (FTDI_SIO_SET_DATA_STOP_BITS_2 | + FTDI_SIO_SET_DATA_PARITY_NONE | + FTDI_SIO_SET_DATA_BITS(8)); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uftdi_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); /* success */ + +detach: + uftdi_detach(dev); + return (ENXIO); +} + +static int +uftdi_detach(device_t dev) +{ + struct uftdi_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UFTDI_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +uftdi_cfg_open(struct ucom_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + struct usb_device_request req; + + DPRINTF(""); + + /* perform a full reset on the device */ + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_RESET; + USETW(req.wValue, FTDI_SIO_RESET_SIO); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + + /* turn on RTS/CTS flow control */ + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_FLOW_CTRL; + USETW(req.wValue, 0); + USETW2(req.wIndex, FTDI_SIO_RTS_CTS_HS, wIndex); + USETW(req.wLength, 0); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + + /* + * NOTE: with the new UCOM layer there will always be a + * "uftdi_cfg_param()" call after "open()", so there is no need for + * "open()" to configure anything + */ +} + +static void +uftdi_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uftdi_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + uint8_t buf[1]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, + sc->sc_hdrlen, UFTDI_OBUFSIZE - sc->sc_hdrlen, + &actlen)) { + + if (sc->sc_hdrlen > 0) { + buf[0] = + FTDI_OUT_TAG(actlen, sc->sc_ucom.sc_portno); + usbd_copy_in(pc, 0, buf, 1); + } + usbd_xfer_set_frame_len(xfer, 0, actlen + sc->sc_hdrlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uftdi_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uftdi_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[2]; + uint8_t ftdi_msr; + uint8_t msr; + uint8_t lsr; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen < 2) { + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, 2); + + ftdi_msr = FTDI_GET_MSR(buf); + lsr = FTDI_GET_LSR(buf); + + msr = 0; + if (ftdi_msr & FTDI_SIO_CTS_MASK) + msr |= SER_CTS; + if (ftdi_msr & FTDI_SIO_DSR_MASK) + msr |= SER_DSR; + if (ftdi_msr & FTDI_SIO_RI_MASK) + msr |= SER_RI; + if (ftdi_msr & FTDI_SIO_RLSD_MASK) + msr |= SER_DCD; + + if ((sc->sc_msr != msr) || + ((sc->sc_lsr & FTDI_LSR_MASK) != (lsr & FTDI_LSR_MASK))) { + DPRINTF("status change msr=0x%02x (0x%02x) " + "lsr=0x%02x (0x%02x)\n", msr, sc->sc_msr, + lsr, sc->sc_lsr); + + sc->sc_msr = msr; + sc->sc_lsr = lsr; + + ucom_status_change(&sc->sc_ucom); + } + actlen -= 2; + + if (actlen > 0) { + ucom_put_data(&sc->sc_ucom, pc, 2, actlen); + } + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uftdi_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + uint16_t wValue; + struct usb_device_request req; + + wValue = onoff ? FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_MODEM_CTRL; + USETW(req.wValue, wValue); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uftdi_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + uint16_t wValue; + struct usb_device_request req; + + wValue = onoff ? FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_MODEM_CTRL; + USETW(req.wValue, wValue); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uftdi_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + uint16_t wValue; + struct usb_device_request req; + + if (onoff) { + sc->sc_last_lcr |= FTDI_SIO_SET_BREAK; + } else { + sc->sc_last_lcr &= ~FTDI_SIO_SET_BREAK; + } + + wValue = sc->sc_last_lcr; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_DATA; + USETW(req.wValue, wValue); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static int +uftdi_set_parm_soft(struct termios *t, + struct uftdi_param_config *cfg, uint8_t type) +{ + memset(cfg, 0, sizeof(*cfg)); + + switch (type) { + case UFTDI_TYPE_SIO: + switch (t->c_ospeed) { + case 300: + cfg->rate = ftdi_sio_b300; + break; + case 600: + cfg->rate = ftdi_sio_b600; + break; + case 1200: + cfg->rate = ftdi_sio_b1200; + break; + case 2400: + cfg->rate = ftdi_sio_b2400; + break; + case 4800: + cfg->rate = ftdi_sio_b4800; + break; + case 9600: + cfg->rate = ftdi_sio_b9600; + break; + case 19200: + cfg->rate = ftdi_sio_b19200; + break; + case 38400: + cfg->rate = ftdi_sio_b38400; + break; + case 57600: + cfg->rate = ftdi_sio_b57600; + break; + case 115200: + cfg->rate = ftdi_sio_b115200; + break; + default: + return (EINVAL); + } + break; + + case UFTDI_TYPE_8U232AM: + if (uftdi_8u232am_getrate(t->c_ospeed, &cfg->rate)) { + return (EINVAL); + } + break; + } + + if (t->c_cflag & CSTOPB) + cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_2; + else + cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_1; + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) { + cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_ODD; + } else { + cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_EVEN; + } + } else { + cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_NONE; + } + + switch (t->c_cflag & CSIZE) { + case CS5: + cfg->lcr |= FTDI_SIO_SET_DATA_BITS(5); + break; + + case CS6: + cfg->lcr |= FTDI_SIO_SET_DATA_BITS(6); + break; + + case CS7: + cfg->lcr |= FTDI_SIO_SET_DATA_BITS(7); + break; + + case CS8: + cfg->lcr |= FTDI_SIO_SET_DATA_BITS(8); + break; + } + + if (t->c_cflag & CRTSCTS) { + cfg->v_flow = FTDI_SIO_RTS_CTS_HS; + } else if (t->c_iflag & (IXON | IXOFF)) { + cfg->v_flow = FTDI_SIO_XON_XOFF_HS; + cfg->v_start = t->c_cc[VSTART]; + cfg->v_stop = t->c_cc[VSTOP]; + } else { + cfg->v_flow = FTDI_SIO_DISABLE_FLOW_CTRL; + } + + return (0); +} + +static int +uftdi_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uftdi_softc *sc = ucom->sc_parent; + struct uftdi_param_config cfg; + + DPRINTF("\n"); + + return (uftdi_set_parm_soft(t, &cfg, sc->sc_type)); +} + +static void +uftdi_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + struct uftdi_param_config cfg; + struct usb_device_request req; + + if (uftdi_set_parm_soft(t, &cfg, sc->sc_type)) { + /* should not happen */ + return; + } + sc->sc_last_lcr = cfg.lcr; + + DPRINTF("\n"); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_BAUD_RATE; + USETW(req.wValue, cfg.rate); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_DATA; + USETW(req.wValue, cfg.lcr); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_FLOW_CTRL; + USETW2(req.wValue, cfg.v_stop, cfg.v_start); + USETW2(req.wIndex, cfg.v_flow, wIndex); + USETW(req.wLength, 0); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uftdi_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + DPRINTF("msr=0x%02x lsr=0x%02x\n", + sc->sc_msr, sc->sc_lsr); + + *msr = sc->sc_msr; + *lsr = sc->sc_lsr; +} + +static void +uftdi_start_read(struct ucom_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_RD]); +} + +static void +uftdi_stop_read(struct ucom_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_RD]); +} + +static void +uftdi_start_write(struct ucom_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_WR]); +} + +static void +uftdi_stop_write(struct ucom_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_WR]); +} + +/*------------------------------------------------------------------------* + * uftdi_8u232am_getrate + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +uftdi_8u232am_getrate(uint32_t speed, uint16_t *rate) +{ + /* Table of the nearest even powers-of-2 for values 0..15. */ + static const uint8_t roundoff[16] = { + 0, 2, 2, 4, 4, 4, 8, 8, + 8, 8, 8, 8, 16, 16, 16, 16, + }; + uint32_t d; + uint32_t freq; + uint16_t result; + + if ((speed < 178) || (speed > ((3000000 * 100) / 97))) + return (1); /* prevent numerical overflow */ + + /* Special cases for 2M and 3M. */ + if ((speed >= ((3000000 * 100) / 103)) && + (speed <= ((3000000 * 100) / 97))) { + result = 0; + goto done; + } + if ((speed >= ((2000000 * 100) / 103)) && + (speed <= ((2000000 * 100) / 97))) { + result = 1; + goto done; + } + d = (FTDI_8U232AM_FREQ << 4) / speed; + d = (d & ~15) + roundoff[d & 15]; + + if (d < FTDI_8U232AM_MIN_DIV) + d = FTDI_8U232AM_MIN_DIV; + else if (d > FTDI_8U232AM_MAX_DIV) + d = FTDI_8U232AM_MAX_DIV; + + /* + * Calculate the frequency needed for "d" to exactly divide down to + * our target "speed", and check that the actual frequency is within + * 3% of this. + */ + freq = (speed * d); + if ((freq < ((FTDI_8U232AM_FREQ * 1600ULL) / 103)) || + (freq > ((FTDI_8U232AM_FREQ * 1600ULL) / 97))) + return (1); + + /* + * Pack the divisor into the resultant value. The lower 14-bits + * hold the integral part, while the upper 2 bits encode the + * fractional component: either 0, 0.5, 0.25, or 0.125. + */ + result = (d >> 4); + if (d & 8) + result |= 0x4000; + else if (d & 4) + result |= 0x8000; + else if (d & 2) + result |= 0xc000; + +done: + *rate = result; + return (0); +} + +static void +uftdi_poll(struct ucom_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UFTDI_N_TRANSFER); +} diff --git a/sys/bus/u4b/serial/uftdi_reg.h b/sys/bus/u4b/serial/uftdi_reg.h new file mode 100644 index 0000000000..0074bc5d70 --- /dev/null +++ b/sys/bus/u4b/serial/uftdi_reg.h @@ -0,0 +1,340 @@ +/* $NetBSD: uftdireg.h,v 1.6 2002/07/11 21:14:28 augustss Exp $ */ +/* $FreeBSD$ */ + +/* + * Definitions for the FTDI USB Single Port Serial Converter - + * known as FTDI_SIO (Serial Input/Output application of the chipset) + * + * The device is based on the FTDI FT8U100AX chip. It has a DB25 on one side, + * USB on the other. + * + * Thanx to FTDI (http://www.ftdi.co.uk) for so kindly providing details + * of the protocol required to talk to the device and ongoing assistence + * during development. + * + * Bill Ryder - bryder@sgi.com of Silicon Graphics, Inc. is the original + * author of this file. + */ +/* Modified by Lennart Augustsson */ + +/* Vendor Request Interface */ +#define FTDI_SIO_RESET 0 /* Reset the port */ +#define FTDI_SIO_MODEM_CTRL 1 /* Set the modem control register */ +#define FTDI_SIO_SET_FLOW_CTRL 2 /* Set flow control register */ +#define FTDI_SIO_SET_BAUD_RATE 3 /* Set baud rate */ +#define FTDI_SIO_SET_DATA 4 /* Set the data characteristics of the + * port */ +#define FTDI_SIO_GET_STATUS 5 /* Retrieve current value of status + * reg */ +#define FTDI_SIO_SET_EVENT_CHAR 6 /* Set the event character */ +#define FTDI_SIO_SET_ERROR_CHAR 7 /* Set the error character */ + +/* Port Identifier Table */ +#define FTDI_PIT_DEFAULT 0 /* SIOA */ +#define FTDI_PIT_SIOA 1 /* SIOA */ +#define FTDI_PIT_SIOB 2 /* SIOB */ +#define FTDI_PIT_PARALLEL 3 /* Parallel */ + +enum uftdi_type { + UFTDI_TYPE_SIO, + UFTDI_TYPE_8U232AM +}; + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_RESET + * wValue: Control Value + * 0 = Reset SIO + * 1 = Purge RX buffer + * 2 = Purge TX buffer + * wIndex: Port + * wLength: 0 + * Data: None + * + * The Reset SIO command has this effect: + * + * Sets flow control set to 'none' + * Event char = 0x0d + * Event trigger = disabled + * Purge RX buffer + * Purge TX buffer + * Clear DTR + * Clear RTS + * baud and data format not reset + * + * The Purge RX and TX buffer commands affect nothing except the buffers + * + */ +/* FTDI_SIO_RESET */ +#define FTDI_SIO_RESET_SIO 0 +#define FTDI_SIO_RESET_PURGE_RX 1 +#define FTDI_SIO_RESET_PURGE_TX 2 + + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_SET_BAUDRATE + * wValue: BaudRate value - see below + * wIndex: Port + * wLength: 0 + * Data: None + */ +/* FTDI_SIO_SET_BAUDRATE */ +enum { + ftdi_sio_b300 = 0, + ftdi_sio_b600 = 1, + ftdi_sio_b1200 = 2, + ftdi_sio_b2400 = 3, + ftdi_sio_b4800 = 4, + ftdi_sio_b9600 = 5, + ftdi_sio_b19200 = 6, + ftdi_sio_b38400 = 7, + ftdi_sio_b57600 = 8, + ftdi_sio_b115200 = 9 +}; + +#define FTDI_8U232AM_FREQ 3000000 + +/* Bounds for normal divisors as 4-bit fixed precision ints. */ +#define FTDI_8U232AM_MIN_DIV 0x20 +#define FTDI_8U232AM_MAX_DIV 0x3fff8 + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_SET_DATA + * wValue: Data characteristics (see below) + * wIndex: Port + * wLength: 0 + * Data: No + * + * Data characteristics + * + * B0..7 Number of data bits + * B8..10 Parity + * 0 = None + * 1 = Odd + * 2 = Even + * 3 = Mark + * 4 = Space + * B11..13 Stop Bits + * 0 = 1 + * 1 = 1.5 + * 2 = 2 + * B14..15 Reserved + * + */ +/* FTDI_SIO_SET_DATA */ +#define FTDI_SIO_SET_DATA_BITS(n) (n) +#define FTDI_SIO_SET_DATA_PARITY_NONE (0x0 << 8) +#define FTDI_SIO_SET_DATA_PARITY_ODD (0x1 << 8) +#define FTDI_SIO_SET_DATA_PARITY_EVEN (0x2 << 8) +#define FTDI_SIO_SET_DATA_PARITY_MARK (0x3 << 8) +#define FTDI_SIO_SET_DATA_PARITY_SPACE (0x4 << 8) +#define FTDI_SIO_SET_DATA_STOP_BITS_1 (0x0 << 11) +#define FTDI_SIO_SET_DATA_STOP_BITS_15 (0x1 << 11) +#define FTDI_SIO_SET_DATA_STOP_BITS_2 (0x2 << 11) +#define FTDI_SIO_SET_BREAK (0x1 << 14) + + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_MODEM_CTRL + * wValue: ControlValue (see below) + * wIndex: Port + * wLength: 0 + * Data: None + * + * NOTE: If the device is in RTS/CTS flow control, the RTS set by this + * command will be IGNORED without an error being returned + * Also - you can not set DTR and RTS with one control message + * + * ControlValue + * B0 DTR state + * 0 = reset + * 1 = set + * B1 RTS state + * 0 = reset + * 1 = set + * B2..7 Reserved + * B8 DTR state enable + * 0 = ignore + * 1 = use DTR state + * B9 RTS state enable + * 0 = ignore + * 1 = use RTS state + * B10..15 Reserved + */ +/* FTDI_SIO_MODEM_CTRL */ +#define FTDI_SIO_SET_DTR_MASK 0x1 +#define FTDI_SIO_SET_DTR_HIGH (1 | ( FTDI_SIO_SET_DTR_MASK << 8)) +#define FTDI_SIO_SET_DTR_LOW (0 | ( FTDI_SIO_SET_DTR_MASK << 8)) +#define FTDI_SIO_SET_RTS_MASK 0x2 +#define FTDI_SIO_SET_RTS_HIGH (2 | ( FTDI_SIO_SET_RTS_MASK << 8)) +#define FTDI_SIO_SET_RTS_LOW (0 | ( FTDI_SIO_SET_RTS_MASK << 8)) + + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_FLOW_CTRL + * wValue: Xoff/Xon + * wIndex: Protocol/Port - hIndex is protocl / lIndex is port + * wLength: 0 + * Data: None + * + * hIndex protocol is: + * B0 Output handshaking using RTS/CTS + * 0 = disabled + * 1 = enabled + * B1 Output handshaking using DTR/DSR + * 0 = disabled + * 1 = enabled + * B2 Xon/Xoff handshaking + * 0 = disabled + * 1 = enabled + * + * A value of zero in the hIndex field disables handshaking + * + * If Xon/Xoff handshaking is specified, the hValue field should contain the + * XOFF character and the lValue field contains the XON character. + */ +/* FTDI_SIO_SET_FLOW_CTRL */ +#define FTDI_SIO_DISABLE_FLOW_CTRL 0x0 +#define FTDI_SIO_RTS_CTS_HS 0x1 +#define FTDI_SIO_DTR_DSR_HS 0x2 +#define FTDI_SIO_XON_XOFF_HS 0x4 + + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_EVENT_CHAR + * wValue: Event Char + * wIndex: Port + * wLength: 0 + * Data: None + * + * wValue: + * B0..7 Event Character + * B8 Event Character Processing + * 0 = disabled + * 1 = enabled + * B9..15 Reserved + * + * FTDI_SIO_SET_EVENT_CHAR + * + * Set the special event character for the specified communications port. + * If the device sees this character it will immediately return the + * data read so far - rather than wait 40ms or until 62 bytes are read + * which is what normally happens. + */ + + + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_ERROR_CHAR + * wValue: Error Char + * wIndex: Port + * wLength: 0 + * Data: None + * + * Error Char + * B0..7 Error Character + * B8 Error Character Processing + * 0 = disabled + * 1 = enabled + * B9..15 Reserved + * + * + * FTDI_SIO_SET_ERROR_CHAR + * Set the parity error replacement character for the specified communications + * port. + */ + + +/* + * BmRequestType: 1100 0000b + * bRequest: FTDI_SIO_GET_MODEM_STATUS + * wValue: zero + * wIndex: Port + * wLength: 1 + * Data: Status + * + * One byte of data is returned + * B0..3 0 + * B4 CTS + * 0 = inactive + * 1 = active + * B5 DSR + * 0 = inactive + * 1 = active + * B6 Ring Indicator (RI) + * 0 = inactive + * 1 = active + * B7 Receive Line Signal Detect (RLSD) + * 0 = inactive + * 1 = active + * + * FTDI_SIO_GET_MODEM_STATUS + * Retrieve the current value of the modem status register. + */ +#define FTDI_SIO_CTS_MASK 0x10 +#define FTDI_SIO_DSR_MASK 0x20 +#define FTDI_SIO_RI_MASK 0x40 +#define FTDI_SIO_RLSD_MASK 0x80 + + + +/* + * + * DATA FORMAT + * + * IN Endpoint + * + * The device reserves the first two bytes of data on this endpoint to contain + * the current values of the modem and line status registers. In the absence of + * data, the device generates a message consisting of these two status bytes + * every 40 ms. + * + * Byte 0: Modem Status + * NOTE: 4 upper bits have same layout as the MSR register in a 16550 + * + * Offset Description + * B0..3 Port + * B4 Clear to Send (CTS) + * B5 Data Set Ready (DSR) + * B6 Ring Indicator (RI) + * B7 Receive Line Signal Detect (RLSD) + * + * Byte 1: Line Status + * NOTE: same layout as the LSR register in a 16550 + * + * Offset Description + * B0 Data Ready (DR) + * B1 Overrun Error (OE) + * B2 Parity Error (PE) + * B3 Framing Error (FE) + * B4 Break Interrupt (BI) + * B5 Transmitter Holding Register (THRE) + * B6 Transmitter Empty (TEMT) + * B7 Error in RCVR FIFO + * + * + * OUT Endpoint + * + * This device reserves the first bytes of data on this endpoint contain the + * length and port identifier of the message. For the FTDI USB Serial converter + * the port identifier is always 1. + * + * Byte 0: Port & length + * + * Offset Description + * B0..1 Port + * B2..7 Length of message - (not including Byte 0) + * + */ +#define FTDI_PORT_MASK 0x0f +#define FTDI_MSR_MASK 0xf0 +#define FTDI_GET_MSR(p) (((p)[0]) & FTDI_MSR_MASK) +#define FTDI_GET_LSR(p) ((p)[1]) +#define FTDI_LSR_MASK (~0x60) /* interesting bits */ +#define FTDI_OUT_TAG(len, port) (((len) << 2) | (port)) diff --git a/sys/bus/u4b/serial/ugensa.c b/sys/bus/u4b/serial/ugensa.c new file mode 100644 index 0000000000..6b0955e911 --- /dev/null +++ b/sys/bus/u4b/serial/ugensa.c @@ -0,0 +1,376 @@ +/* $FreeBSD$ */ +/* $NetBSD: ugensa.c,v 1.9.2.1 2007/03/24 14:55:50 yamt Exp $ */ + +/* + * Copyright (c) 2004, 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Roland C. Dowdeswell . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * NOTE: all function names beginning like "ugensa_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR usb_debug +#include +#include + +#include + +#define UGENSA_BUF_SIZE 2048 /* bytes */ +#define UGENSA_CONFIG_INDEX 0 +#define UGENSA_IFACE_INDEX 0 +#define UGENSA_IFACE_MAX 8 /* exclusivly */ + +enum { + UGENSA_BULK_DT_WR, + UGENSA_BULK_DT_RD, + UGENSA_N_TRANSFER, +}; + +struct ugensa_sub_softc { + struct ucom_softc *sc_ucom_ptr; + struct usb_xfer *sc_xfer[UGENSA_N_TRANSFER]; +}; + +struct ugensa_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom[UGENSA_IFACE_MAX]; + struct ugensa_sub_softc sc_sub[UGENSA_IFACE_MAX]; + + struct mtx sc_mtx; + uint8_t sc_niface; +}; + +/* prototypes */ + +static device_probe_t ugensa_probe; +static device_attach_t ugensa_attach; +static device_detach_t ugensa_detach; + +static usb_callback_t ugensa_bulk_write_callback; +static usb_callback_t ugensa_bulk_read_callback; + +static void ugensa_start_read(struct ucom_softc *); +static void ugensa_stop_read(struct ucom_softc *); +static void ugensa_start_write(struct ucom_softc *); +static void ugensa_stop_write(struct ucom_softc *); +static void ugensa_poll(struct ucom_softc *ucom); + +static const struct usb_config ugensa_xfer_config[UGENSA_N_TRANSFER] = { + + [UGENSA_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UGENSA_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &ugensa_bulk_write_callback, + }, + + [UGENSA_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UGENSA_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &ugensa_bulk_read_callback, + }, +}; + +static const struct ucom_callback ugensa_callback = { + .ucom_start_read = &ugensa_start_read, + .ucom_stop_read = &ugensa_stop_read, + .ucom_start_write = &ugensa_start_write, + .ucom_stop_write = &ugensa_stop_write, + .ucom_poll = &ugensa_poll, +}; + +static device_method_t ugensa_methods[] = { + /* Device methods */ + DEVMETHOD(device_probe, ugensa_probe), + DEVMETHOD(device_attach, ugensa_attach), + DEVMETHOD(device_detach, ugensa_detach), + {0, 0} +}; + +static devclass_t ugensa_devclass; + +static driver_t ugensa_driver = { + .name = "ugensa", + .methods = ugensa_methods, + .size = sizeof(struct ugensa_softc), +}; + +DRIVER_MODULE(ugensa, uhub, ugensa_driver, ugensa_devclass, NULL, 0); +MODULE_DEPEND(ugensa, ucom, 1, 1, 1); +MODULE_DEPEND(ugensa, usb, 1, 1, 1); +MODULE_VERSION(ugensa, 1); + +static const STRUCT_USB_HOST_ID ugensa_devs[] = { + {USB_VPI(USB_VENDOR_AIRPRIME, USB_PRODUCT_AIRPRIME_PC5220, 0)}, + {USB_VPI(USB_VENDOR_CMOTECH, USB_PRODUCT_CMOTECH_CDMA_MODEM1, 0)}, + {USB_VPI(USB_VENDOR_KYOCERA2, USB_PRODUCT_KYOCERA2_CDMA_MSM_K, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_49GPLUS, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL2, USB_PRODUCT_NOVATEL2_FLEXPACKGPS, 0)}, +}; + +static int +ugensa_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UGENSA_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != 0) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(ugensa_devs, sizeof(ugensa_devs), uaa)); +} + +static int +ugensa_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ugensa_softc *sc = device_get_softc(dev); + struct ugensa_sub_softc *ssc; + struct usb_interface *iface; + int32_t error; + uint8_t iface_index; + int x, cnt; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "ugensa", NULL, MTX_DEF); + + /* Figure out how many interfaces this device has got */ + for (cnt = 0; cnt < UGENSA_IFACE_MAX; cnt++) { + if ((usbd_get_endpoint(uaa->device, cnt, ugensa_xfer_config + 0) == NULL) || + (usbd_get_endpoint(uaa->device, cnt, ugensa_xfer_config + 1) == NULL)) { + /* we have reached the end */ + break; + } + } + + if (cnt == 0) { + device_printf(dev, "No interfaces\n"); + goto detach; + } + for (x = 0; x < cnt; x++) { + iface = usbd_get_iface(uaa->device, x); + if (iface->idesc->bInterfaceClass != UICLASS_VENDOR) + /* Not a serial port, most likely a SD reader */ + continue; + + ssc = sc->sc_sub + sc->sc_niface; + ssc->sc_ucom_ptr = sc->sc_ucom + sc->sc_niface; + + iface_index = (UGENSA_IFACE_INDEX + x); + error = usbd_transfer_setup(uaa->device, + &iface_index, ssc->sc_xfer, ugensa_xfer_config, + UGENSA_N_TRANSFER, ssc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating USB " + "transfers failed\n"); + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(ssc->sc_xfer[UGENSA_BULK_DT_WR]); + usbd_xfer_set_stall(ssc->sc_xfer[UGENSA_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + /* initialize port number */ + ssc->sc_ucom_ptr->sc_portno = sc->sc_niface; + sc->sc_niface++; + if (x != uaa->info.bIfaceIndex) + usbd_set_parent_iface(uaa->device, x, + uaa->info.bIfaceIndex); + } + device_printf(dev, "Found %d interfaces.\n", sc->sc_niface); + + error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_niface, sc, + &ugensa_callback, &sc->sc_mtx); + if (error) { + DPRINTF("attach failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); /* success */ + +detach: + ugensa_detach(dev); + return (ENXIO); /* failure */ +} + +static int +ugensa_detach(device_t dev) +{ + struct ugensa_softc *sc = device_get_softc(dev); + uint8_t x; + + ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); + + for (x = 0; x < sc->sc_niface; x++) { + usbd_transfer_unsetup(sc->sc_sub[x].sc_xfer, UGENSA_N_TRANSFER); + } + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +ugensa_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ugensa_sub_softc *ssc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(ssc->sc_ucom_ptr, pc, 0, + UGENSA_BUF_SIZE, &actlen)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +ugensa_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ugensa_sub_softc *ssc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(ssc->sc_ucom_ptr, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +ugensa_start_read(struct ucom_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usbd_transfer_start(ssc->sc_xfer[UGENSA_BULK_DT_RD]); +} + +static void +ugensa_stop_read(struct ucom_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usbd_transfer_stop(ssc->sc_xfer[UGENSA_BULK_DT_RD]); +} + +static void +ugensa_start_write(struct ucom_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usbd_transfer_start(ssc->sc_xfer[UGENSA_BULK_DT_WR]); +} + +static void +ugensa_stop_write(struct ucom_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usbd_transfer_stop(ssc->sc_xfer[UGENSA_BULK_DT_WR]); +} + +static void +ugensa_poll(struct ucom_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usbd_transfer_poll(ssc->sc_xfer, UGENSA_N_TRANSFER); +} diff --git a/sys/bus/u4b/serial/uipaq.c b/sys/bus/u4b/serial/uipaq.c new file mode 100644 index 0000000000..d038e17e65 --- /dev/null +++ b/sys/bus/u4b/serial/uipaq.c @@ -0,0 +1,1350 @@ +/* $NetBSD: uipaq.c,v 1.4 2006/11/16 01:33:27 christos Exp $ */ +/* $OpenBSD: uipaq.c,v 1.1 2005/06/17 23:50:33 deraadt Exp $ */ + +/* + * Copyright (c) 2000-2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * iPAQ driver + * + * 19 July 2003: Incorporated changes suggested by Sam Lawrance from + * the uppc module + * + * + * Contact isis@cs.umd.edu if you have any questions/comments about this driver + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR usb_debug +#include +#include + +#include + +#define UIPAQ_CONFIG_INDEX 0 /* config number 1 */ +#define UIPAQ_IFACE_INDEX 0 + +#define UIPAQ_BUF_SIZE 1024 + +enum { + UIPAQ_BULK_DT_WR, + UIPAQ_BULK_DT_RD, + UIPAQ_N_TRANSFER, +}; + +struct uipaq_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UIPAQ_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint16_t sc_line; + + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* modem status register */ +}; + +static device_probe_t uipaq_probe; +static device_attach_t uipaq_attach; +static device_detach_t uipaq_detach; + +static usb_callback_t uipaq_write_callback; +static usb_callback_t uipaq_read_callback; + +static void uipaq_start_read(struct ucom_softc *); +static void uipaq_stop_read(struct ucom_softc *); +static void uipaq_start_write(struct ucom_softc *); +static void uipaq_stop_write(struct ucom_softc *); +static void uipaq_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void uipaq_cfg_set_rts(struct ucom_softc *, uint8_t); +static void uipaq_cfg_set_break(struct ucom_softc *, uint8_t); +static void uipaq_poll(struct ucom_softc *ucom); + +static const struct usb_config uipaq_config_data[UIPAQ_N_TRANSFER] = { + + [UIPAQ_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UIPAQ_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &uipaq_write_callback, + }, + + [UIPAQ_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UIPAQ_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uipaq_read_callback, + }, +}; + +static const struct ucom_callback uipaq_callback = { + .ucom_cfg_set_dtr = &uipaq_cfg_set_dtr, + .ucom_cfg_set_rts = &uipaq_cfg_set_rts, + .ucom_cfg_set_break = &uipaq_cfg_set_break, + .ucom_start_read = &uipaq_start_read, + .ucom_stop_read = &uipaq_stop_read, + .ucom_start_write = &uipaq_start_write, + .ucom_stop_write = &uipaq_stop_write, + .ucom_poll = &uipaq_poll, +}; + +/* + * Much of this list is generated from lists of other drivers that + * support the same hardware. Numeric values are used where no usbdevs + * entries exist. + */ +static const STRUCT_USB_HOST_ID uipaq_devs[] = { + /* Socket USB Sync */ + {USB_VPI(0x0104, 0x00be, 0)}, + /* USB Sync 0301 */ + {USB_VPI(0x04ad, 0x0301, 0)}, + /* USB Sync 0302 */ + {USB_VPI(0x04ad, 0x0302, 0)}, + /* USB Sync 0303 */ + {USB_VPI(0x04ad, 0x0303, 0)}, + /* GPS Pocket PC USB Sync */ + {USB_VPI(0x04ad, 0x0306, 0)}, + /* HHP PDT */ + {USB_VPI(0x0536, 0x01a0, 0)}, + /* Intermec Mobile Computer */ + {USB_VPI(0x067e, 0x1001, 0)}, + /* Linkup Systems USB Sync */ + {USB_VPI(0x094b, 0x0001, 0)}, + /* BCOM USB Sync 0065 */ + {USB_VPI(0x0960, 0x0065, 0)}, + /* BCOM USB Sync 0066 */ + {USB_VPI(0x0960, 0x0066, 0)}, + /* BCOM USB Sync 0067 */ + {USB_VPI(0x0960, 0x0067, 0)}, + /* Portatec USB Sync */ + {USB_VPI(0x0961, 0x0010, 0)}, + /* Trimble GeoExplorer */ + {USB_VPI(0x099e, 0x0052, 0)}, + /* TDS Data Collector */ + {USB_VPI(0x099e, 0x4000, 0)}, + /* Motorola iDEN Smartphone */ + {USB_VPI(0x0c44, 0x03a2, 0)}, + /* Cesscom Luxian Series */ + {USB_VPI(0x0c8e, 0x6000, 0)}, + /* Motorola PowerPad Pocket PCDevice */ + {USB_VPI(0x0cad, 0x9001, 0)}, + /* Freedom Scientific USB Sync */ + {USB_VPI(0x0f4e, 0x0200, 0)}, + /* Cyberbank USB Sync */ + {USB_VPI(0x0f98, 0x0201, 0)}, + /* Wistron USB Sync */ + {USB_VPI(0x0fb8, 0x3001, 0)}, + /* Wistron USB Sync */ + {USB_VPI(0x0fb8, 0x3002, 0)}, + /* Wistron USB Sync */ + {USB_VPI(0x0fb8, 0x3003, 0)}, + /* Wistron USB Sync */ + {USB_VPI(0x0fb8, 0x4001, 0)}, + /* E-TEN USB Sync */ + {USB_VPI(0x1066, 0x00ce, 0)}, + /* E-TEN P3XX Pocket PC */ + {USB_VPI(0x1066, 0x0300, 0)}, + /* E-TEN P5XX Pocket PC */ + {USB_VPI(0x1066, 0x0500, 0)}, + /* E-TEN P6XX Pocket PC */ + {USB_VPI(0x1066, 0x0600, 0)}, + /* E-TEN P7XX Pocket PC */ + {USB_VPI(0x1066, 0x0700, 0)}, + /* Psion Teklogix Sync 753x */ + {USB_VPI(0x1114, 0x0001, 0)}, + /* Psion Teklogix Sync netBookPro */ + {USB_VPI(0x1114, 0x0004, 0)}, + /* Psion Teklogix Sync 7525 */ + {USB_VPI(0x1114, 0x0006, 0)}, + /* VES USB Sync */ + {USB_VPI(0x1182, 0x1388, 0)}, + /* Rugged Pocket PC 2003 */ + {USB_VPI(0x11d9, 0x1002, 0)}, + /* Rugged Pocket PC 2003 */ + {USB_VPI(0x11d9, 0x1003, 0)}, + /* USB Sync 03 */ + {USB_VPI(0x1231, 0xce01, 0)}, + /* USB Sync 03 */ + {USB_VPI(0x1231, 0xce02, 0)}, + /* Mio DigiWalker PPC StrongARM */ + {USB_VPI(0x3340, 0x011c, 0)}, + /* Mio DigiWalker 338 */ + {USB_VPI(0x3340, 0x0326, 0)}, + /* Mio DigiWalker 338 */ + {USB_VPI(0x3340, 0x0426, 0)}, + /* Mio DigiWalker USB Sync */ + {USB_VPI(0x3340, 0x043a, 0)}, + /* MiTAC USB Sync 528 */ + {USB_VPI(0x3340, 0x051c, 0)}, + /* Mio DigiWalker SmartPhone USB Sync */ + {USB_VPI(0x3340, 0x053a, 0)}, + /* MiTAC USB Sync */ + {USB_VPI(0x3340, 0x071c, 0)}, + /* Generic PPC StrongARM */ + {USB_VPI(0x3340, 0x0b1c, 0)}, + /* Generic PPC USB Sync */ + {USB_VPI(0x3340, 0x0e3a, 0)}, + /* Itautec USB Sync */ + {USB_VPI(0x3340, 0x0f1c, 0)}, + /* Generic SmartPhone USB Sync */ + {USB_VPI(0x3340, 0x0f3a, 0)}, + /* Itautec USB Sync */ + {USB_VPI(0x3340, 0x1326, 0)}, + /* YAKUMO USB Sync */ + {USB_VPI(0x3340, 0x191c, 0)}, + /* Vobis USB Sync */ + {USB_VPI(0x3340, 0x2326, 0)}, + /* MEDION Winodws Moble USB Sync */ + {USB_VPI(0x3340, 0x3326, 0)}, + /* Legend USB Sync */ + {USB_VPI(0x3708, 0x20ce, 0)}, + /* Lenovo USB Sync */ + {USB_VPI(0x3708, 0x21ce, 0)}, + /* Mobile Media Technology USB Sync */ + {USB_VPI(0x4113, 0x0210, 0)}, + /* Mobile Media Technology USB Sync */ + {USB_VPI(0x4113, 0x0211, 0)}, + /* Mobile Media Technology USB Sync */ + {USB_VPI(0x4113, 0x0400, 0)}, + /* Mobile Media Technology USB Sync */ + {USB_VPI(0x4113, 0x0410, 0)}, + /* Smartphone */ + {USB_VPI(0x4505, 0x0010, 0)}, + /* SAGEM Wireless Assistant */ + {USB_VPI(0x5e04, 0xce00, 0)}, + /* c10 Series */ + {USB_VPI(USB_VENDOR_ACER, 0x1631, 0)}, + /* c20 Series */ + {USB_VPI(USB_VENDOR_ACER, 0x1632, 0)}, + /* Acer n10 Handheld USB Sync */ + {USB_VPI(USB_VENDOR_ACER, 0x16e1, 0)}, + /* Acer n20 Handheld USB Sync */ + {USB_VPI(USB_VENDOR_ACER, 0x16e2, 0)}, + /* Acer n30 Handheld USB Sync */ + {USB_VPI(USB_VENDOR_ACER, 0x16e3, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x4200, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x4201, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x4202, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x9200, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x9202, 0)}, + /**/ + {USB_VPI(USB_VENDOR_ASUS, USB_PRODUCT_ASUS_P535, 0)}, + /* CASIO USB Sync 2001 */ + {USB_VPI(USB_VENDOR_CASIO, 0x2001, 0)}, + /* CASIO USB Sync 2003 */ + {USB_VPI(USB_VENDOR_CASIO, 0x2003, 0)}, + /**/ + {USB_VPI(USB_VENDOR_CASIO, USB_PRODUCT_CASIO_BE300, 0)}, + /* MyGuide 7000 XL USB Sync */ + {USB_VPI(USB_VENDOR_COMPAL, 0x0531, 0)}, + /* Compaq iPAQ USB Sync */ + {USB_VPI(USB_VENDOR_COMPAQ, 0x0032, 0)}, + /**/ + {USB_VPI(USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_IPAQPOCKETPC, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4001, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4002, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4003, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4004, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4005, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4006, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4007, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4008, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4009, 0)}, + /* Fujitsu Siemens Computers USB Sync */ + {USB_VPI(USB_VENDOR_FSC, 0x1001, 0)}, + /* FUJITSU USB Sync */ + {USB_VPI(USB_VENDOR_FUJITSU, 0x1058, 0)}, + /* FUJITSU USB Sync */ + {USB_VPI(USB_VENDOR_FUJITSU, 0x1079, 0)}, + /* Askey USB Sync */ + {USB_VPI(USB_VENDOR_GIGASET, 0x0601, 0)}, + /* Hitachi USB Sync */ + {USB_VPI(USB_VENDOR_HITACHI, 0x0014, 0)}, + /* HP USB Sync 1612 */ + {USB_VPI(USB_VENDOR_HP, 0x1216, 0)}, + /* HP USB Sync 1620 */ + {USB_VPI(USB_VENDOR_HP, 0x2016, 0)}, + /* HP USB Sync 1621 */ + {USB_VPI(USB_VENDOR_HP, 0x2116, 0)}, + /* HP USB Sync 1622 */ + {USB_VPI(USB_VENDOR_HP, 0x2216, 0)}, + /* HP USB Sync 1630 */ + {USB_VPI(USB_VENDOR_HP, 0x3016, 0)}, + /* HP USB Sync 1631 */ + {USB_VPI(USB_VENDOR_HP, 0x3116, 0)}, + /* HP USB Sync 1632 */ + {USB_VPI(USB_VENDOR_HP, 0x3216, 0)}, + /* HP USB Sync 1640 */ + {USB_VPI(USB_VENDOR_HP, 0x4016, 0)}, + /* HP USB Sync 1641 */ + {USB_VPI(USB_VENDOR_HP, 0x4116, 0)}, + /* HP USB Sync 1642 */ + {USB_VPI(USB_VENDOR_HP, 0x4216, 0)}, + /* HP USB Sync 1650 */ + {USB_VPI(USB_VENDOR_HP, 0x5016, 0)}, + /* HP USB Sync 1651 */ + {USB_VPI(USB_VENDOR_HP, 0x5116, 0)}, + /* HP USB Sync 1652 */ + {USB_VPI(USB_VENDOR_HP, 0x5216, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_2215, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_568J, 0)}, + /* HTC USB Modem */ + {USB_VPI(USB_VENDOR_HTC, 0x00cf, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a01, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a02, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a03, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a04, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a05, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a06, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a07, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a08, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a09, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0f, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a10, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a11, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a12, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a13, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a14, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a15, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a16, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a17, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a18, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a19, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1f, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a20, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a21, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a22, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a23, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a24, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a25, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a26, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a27, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a28, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a29, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2f, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a30, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a31, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a32, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a33, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a34, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a35, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a36, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a37, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a38, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a39, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3f, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a40, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a41, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a42, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a43, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a44, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a45, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a46, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a47, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a48, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a49, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4f, 0)}, + /* HTC SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a50, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a52, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a53, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a54, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a55, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a56, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a57, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a58, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a59, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5f, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a60, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a61, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a62, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a63, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a64, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a65, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a66, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a67, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a68, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a69, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6f, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a70, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a71, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a72, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a73, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a74, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a75, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a76, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a77, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a78, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a79, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7f, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a80, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a81, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a82, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a83, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a84, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a85, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a86, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a87, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a88, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a89, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8f, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a90, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a91, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a92, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a93, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a94, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a95, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a96, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a97, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a98, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a99, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9f, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_PPC6700MODEM, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_SMARTPHONE, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_WINMOBILE, 0)}, + /* High Tech Computer Wizard Smartphone */ + {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_WIZARD, 0)}, + /* JVC USB Sync */ + {USB_VPI(USB_VENDOR_JVC, 0x3011, 0)}, + /* JVC USB Sync */ + {USB_VPI(USB_VENDOR_JVC, 0x3012, 0)}, + /* LGE USB Sync */ + {USB_VPI(USB_VENDOR_LG, 0x9c01, 0)}, + /* Microsoft USB Sync */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x00ce, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0400, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0401, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0402, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0403, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0404, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0405, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0406, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0407, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0408, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0409, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040a, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040b, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040c, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040d, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040e, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040f, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0410, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0411, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0412, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0413, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0414, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0415, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0416, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0417, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0432, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0433, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0434, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0435, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0436, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0437, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0438, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0439, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043b, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043c, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043d, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043e, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043f, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0440, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0441, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0442, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0443, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0444, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0445, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0446, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0447, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0448, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0449, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044b, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044c, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044d, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044e, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044f, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0450, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0451, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0452, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0453, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0454, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0455, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0456, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0457, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0458, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0459, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045b, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045c, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045d, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045e, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045f, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0460, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0461, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0462, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0463, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0464, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0465, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0466, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0467, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0468, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0469, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046b, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046c, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046d, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046e, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046f, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0470, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0471, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0472, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0473, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0474, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0475, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0476, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0477, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0478, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0479, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x047a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x047b, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04c8, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04c9, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04ca, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04cb, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04cc, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04cd, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04ce, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04d7, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04d8, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04d9, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04da, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04db, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04dc, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04dd, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04de, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04df, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e0, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e1, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e2, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e3, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e4, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e5, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e6, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e7, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e8, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e9, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04ea, 0)}, + /* Motorola MPx200 Smartphone */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4204, 0)}, + /* Motorola MPc GSM */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4214, 0)}, + /* Motorola MPx220 Smartphone */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4224, 0)}, + /* Motorola MPc CDMA */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4234, 0)}, + /* Motorola MPx100 Smartphone */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4244, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x00d5, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x00d6, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x00d7, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x8024, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x8025, 0)}, + /* Panasonic USB Sync */ + {USB_VPI(USB_VENDOR_PANASONIC, 0x2500, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f00, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f01, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f02, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f03, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f04, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6611, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6613, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6615, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6617, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6619, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x661b, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x662e, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6630, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6632, 0)}, + /* SHARP WS003SH USB Modem */ + {USB_VPI(USB_VENDOR_SHARP, 0x9102, 0)}, + /* SHARP WS004SH USB Modem */ + {USB_VPI(USB_VENDOR_SHARP, 0x9121, 0)}, + /* SHARP S01SH USB Modem */ + {USB_VPI(USB_VENDOR_SHARP, 0x9151, 0)}, + /**/ + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_WZERO3ES, 0)}, + /**/ + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_WZERO3ADES, 0)}, + /**/ + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_WILLCOM03, 0)}, + /* Symbol USB Sync */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2000, 0)}, + /* Symbol USB Sync 0x2001 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2001, 0)}, + /* Symbol USB Sync 0x2002 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2002, 0)}, + /* Symbol USB Sync 0x2003 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2003, 0)}, + /* Symbol USB Sync 0x2004 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2004, 0)}, + /* Symbol USB Sync 0x2005 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2005, 0)}, + /* Symbol USB Sync 0x2006 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2006, 0)}, + /* Symbol USB Sync 0x2007 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2007, 0)}, + /* Symbol USB Sync 0x2008 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2008, 0)}, + /* Symbol USB Sync 0x2009 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2009, 0)}, + /* Symbol USB Sync 0x200a */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x200a, 0)}, + /* TOSHIBA USB Sync 0700 */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0700, 0)}, + /* TOSHIBA Pocket PC e310 */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0705, 0)}, + /* TOSHIBA Pocket PC e330 Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0707, 0)}, + /* TOSHIBA Pocket PC e350Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0708, 0)}, + /* TOSHIBA Pocket PC e750 Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0709, 0)}, + /* TOSHIBA Pocket PC e400 Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x070a, 0)}, + /* TOSHIBA Pocket PC e800 Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x070b, 0)}, + /* TOSHIBA Pocket PC e740 */ + {USB_VPI(USB_VENDOR_TOSHIBA, USB_PRODUCT_TOSHIBA_POCKETPC_E740, 0)}, + /* ViewSonic Color Pocket PC V35 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x0ed9, 0)}, + /* ViewSonic Color Pocket PC V36 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1527, 0)}, + /* ViewSonic Color Pocket PC V37 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1529, 0)}, + /* ViewSonic Color Pocket PC V38 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x152b, 0)}, + /* ViewSonic Pocket PC */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x152e, 0)}, + /* ViewSonic Communicator Pocket PC */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1921, 0)}, + /* ViewSonic Smartphone */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1922, 0)}, + /* ViewSonic Pocket PC V30 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1923, 0)}, +}; + +static device_method_t uipaq_methods[] = { + DEVMETHOD(device_probe, uipaq_probe), + DEVMETHOD(device_attach, uipaq_attach), + DEVMETHOD(device_detach, uipaq_detach), + {0, 0} +}; + +static devclass_t uipaq_devclass; + +static driver_t uipaq_driver = { + .name = "uipaq", + .methods = uipaq_methods, + .size = sizeof(struct uipaq_softc), +}; + +DRIVER_MODULE(uipaq, uhub, uipaq_driver, uipaq_devclass, NULL, 0); +MODULE_DEPEND(uipaq, ucom, 1, 1, 1); +MODULE_DEPEND(uipaq, usb, 1, 1, 1); +MODULE_VERSION(uipaq, 1); + +static int +uipaq_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UIPAQ_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UIPAQ_IFACE_INDEX) { + return (ENXIO); + } + if (uaa->info.bInterfaceClass == UICLASS_IAD) { + DPRINTF("IAD detected - not UIPAQ serial device\n"); + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uipaq_devs, sizeof(uipaq_devs), uaa)); +} + +static int +uipaq_attach(device_t dev) +{ + struct usb_device_request req; + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uipaq_softc *sc = device_get_softc(dev); + int error; + uint8_t iface_index; + uint8_t i; + + sc->sc_udev = uaa->device; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uipaq", NULL, MTX_DEF); + + /* + * Send magic bytes, cribbed from Linux ipaq driver that + * claims to have sniffed them from Win98. Wait for driver to + * become ready on device side? + */ + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, UCDC_LINE_DTR); + USETW(req.wIndex, 0x0); + USETW(req.wLength, 0); + for (i = 0; i != 64; i++) { + error = + usbd_do_request_flags(uaa->device, NULL, &req, + NULL, 0, NULL, 100); + if (error == 0) + break; + usb_pause_mtx(NULL, hz / 10); + } + + iface_index = UIPAQ_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, uipaq_config_data, + UIPAQ_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UIPAQ_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UIPAQ_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uipaq_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + uipaq_detach(dev); + return (ENXIO); +} + +int +uipaq_detach(device_t dev) +{ + struct uipaq_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UIPAQ_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +uipaq_start_read(struct ucom_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UIPAQ_BULK_DT_RD]); +} + +static void +uipaq_stop_read(struct ucom_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UIPAQ_BULK_DT_RD]); +} + +static void +uipaq_start_write(struct ucom_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UIPAQ_BULK_DT_WR]); +} + +static void +uipaq_stop_write(struct ucom_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UIPAQ_BULK_DT_WR]); +} + +static void +uipaq_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uipaq_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff=%d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = UIPAQ_IFACE_INDEX; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uipaq_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uipaq_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff=%d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = UIPAQ_IFACE_INDEX; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uipaq_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uipaq_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t temp; + + temp = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, temp); + req.wIndex[0] = UIPAQ_IFACE_INDEX; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uipaq_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uipaq_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UIPAQ_BUF_SIZE, &actlen)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uipaq_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uipaq_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uipaq_poll(struct ucom_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UIPAQ_N_TRANSFER); +} diff --git a/sys/bus/u4b/serial/ulpt.c b/sys/bus/u4b/serial/ulpt.c new file mode 100644 index 0000000000..063e2974d0 --- /dev/null +++ b/sys/bus/u4b/serial/ulpt.c @@ -0,0 +1,761 @@ +#include +__FBSDID("$FreeBSD$"); + +/* $NetBSD: ulpt.c,v 1.60 2003/10/04 21:19:50 augustss Exp $ */ + +/*- + * Copyright (c) 1998, 2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Printer Class spec: http://www.usb.org/developers/data/devclass/usbprint109.PDF + * Printer Class spec: http://www.usb.org/developers/devclass_docs/usbprint11.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR ulpt_debug +#include +#include + +#ifdef USB_DEBUG +static int ulpt_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ulpt, CTLFLAG_RW, 0, "USB ulpt"); +SYSCTL_INT(_hw_usb_ulpt, OID_AUTO, debug, CTLFLAG_RW, + &ulpt_debug, 0, "Debug level"); +#endif + +#define ULPT_BSIZE (1<<15) /* bytes */ +#define ULPT_IFQ_MAXLEN 2 /* units */ + +#define UR_GET_DEVICE_ID 0x00 +#define UR_GET_PORT_STATUS 0x01 +#define UR_SOFT_RESET 0x02 + +#define LPS_NERR 0x08 /* printer no error */ +#define LPS_SELECT 0x10 /* printer selected */ +#define LPS_NOPAPER 0x20 /* printer out of paper */ +#define LPS_INVERT (LPS_SELECT|LPS_NERR) +#define LPS_MASK (LPS_SELECT|LPS_NERR|LPS_NOPAPER) + +enum { + ULPT_BULK_DT_WR, + ULPT_BULK_DT_RD, + ULPT_INTR_DT_RD, + ULPT_N_TRANSFER, +}; + +struct ulpt_softc { + struct usb_fifo_sc sc_fifo; + struct usb_fifo_sc sc_fifo_noreset; + struct mtx sc_mtx; + struct usb_callout sc_watchdog; + + device_t sc_dev; + struct usb_device *sc_udev; + struct usb_fifo *sc_fifo_open[2]; + struct usb_xfer *sc_xfer[ULPT_N_TRANSFER]; + + int sc_fflags; /* current open flags, FREAD and + * FWRITE */ + uint8_t sc_iface_no; + uint8_t sc_last_status; + uint8_t sc_zlps; /* number of consequtive zero length + * packets received */ +}; + +/* prototypes */ + +static device_probe_t ulpt_probe; +static device_attach_t ulpt_attach; +static device_detach_t ulpt_detach; + +static usb_callback_t ulpt_write_callback; +static usb_callback_t ulpt_read_callback; +static usb_callback_t ulpt_status_callback; + +static void ulpt_reset(struct ulpt_softc *); +static void ulpt_watchdog(void *); + +static usb_fifo_close_t ulpt_close; +static usb_fifo_cmd_t ulpt_start_read; +static usb_fifo_cmd_t ulpt_start_write; +static usb_fifo_cmd_t ulpt_stop_read; +static usb_fifo_cmd_t ulpt_stop_write; +static usb_fifo_ioctl_t ulpt_ioctl; +static usb_fifo_open_t ulpt_open; +static usb_fifo_open_t unlpt_open; + +static struct usb_fifo_methods ulpt_fifo_methods = { + .f_close = &ulpt_close, + .f_ioctl = &ulpt_ioctl, + .f_open = &ulpt_open, + .f_start_read = &ulpt_start_read, + .f_start_write = &ulpt_start_write, + .f_stop_read = &ulpt_stop_read, + .f_stop_write = &ulpt_stop_write, + .basename[0] = "ulpt", +}; + +static struct usb_fifo_methods unlpt_fifo_methods = { + .f_close = &ulpt_close, + .f_ioctl = &ulpt_ioctl, + .f_open = &unlpt_open, + .f_start_read = &ulpt_start_read, + .f_start_write = &ulpt_start_write, + .f_stop_read = &ulpt_stop_read, + .f_stop_write = &ulpt_stop_write, + .basename[0] = "unlpt", +}; + +static void +ulpt_reset(struct ulpt_softc *sc) +{ + struct usb_device_request req; + + DPRINTFN(2, "\n"); + + req.bRequest = UR_SOFT_RESET; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, 0); + + /* + * There was a mistake in the USB printer 1.0 spec that gave the + * request type as UT_WRITE_CLASS_OTHER; it should have been + * UT_WRITE_CLASS_INTERFACE. Many printers use the old one, + * so we try both. + */ + + mtx_lock(&sc->sc_mtx); + req.bmRequestType = UT_WRITE_CLASS_OTHER; + if (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, + &req, NULL, 0, NULL, 2 * USB_MS_HZ)) { /* 1.0 */ + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + if (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, + &req, NULL, 0, NULL, 2 * USB_MS_HZ)) { /* 1.1 */ + /* ignore error */ + } + } + mtx_unlock(&sc->sc_mtx); +} + +static void +ulpt_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ulpt_softc *sc = usbd_xfer_softc(xfer); + struct usb_fifo *f = sc->sc_fifo_open[USB_FIFO_TX]; + struct usb_page_cache *pc; + int actlen, max; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + if (f == NULL) { + /* should not happen */ + DPRINTF("no FIFO\n"); + return; + } + DPRINTF("state=0x%x actlen=%d\n", USB_GET_STATE(xfer), actlen); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + max = usbd_xfer_max_len(xfer); + if (usb_fifo_get_data(f, pc, 0, max, &actlen, 0)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +ulpt_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ulpt_softc *sc = usbd_xfer_softc(xfer); + struct usb_fifo *f = sc->sc_fifo_open[USB_FIFO_RX]; + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + if (f == NULL) { + /* should not happen */ + DPRINTF("no FIFO\n"); + return; + } + DPRINTF("state=0x%x\n", USB_GET_STATE(xfer)); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen == 0) { + + if (sc->sc_zlps == 4) { + /* enable BULK throttle */ + usbd_xfer_set_interval(xfer, 500); /* ms */ + } else { + sc->sc_zlps++; + } + } else { + /* disable BULK throttle */ + + usbd_xfer_set_interval(xfer, 0); + sc->sc_zlps = 0; + } + + pc = usbd_xfer_get_frame(xfer, 0); + usb_fifo_put_data(f, pc, 0, actlen, 1); + + case USB_ST_SETUP: +tr_setup: + if (usb_fifo_put_bytes_max(f) != 0) { + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + /* disable BULK throttle */ + usbd_xfer_set_interval(xfer, 0); + sc->sc_zlps = 0; + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +ulpt_status_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ulpt_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc; + uint8_t cur_status; + uint8_t new_status; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + pc = usbd_xfer_get_frame(xfer, 1); + usbd_copy_out(pc, 0, &cur_status, 1); + + cur_status = (cur_status ^ LPS_INVERT) & LPS_MASK; + new_status = cur_status & ~sc->sc_last_status; + sc->sc_last_status = cur_status; + + if (new_status & LPS_SELECT) + log(LOG_NOTICE, "%s: offline\n", + device_get_nameunit(sc->sc_dev)); + else if (new_status & LPS_NOPAPER) + log(LOG_NOTICE, "%s: out of paper\n", + device_get_nameunit(sc->sc_dev)); + else if (new_status & LPS_NERR) + log(LOG_NOTICE, "%s: output error\n", + device_get_nameunit(sc->sc_dev)); + break; + + case USB_ST_SETUP: + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_PORT_STATUS; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, 1); + usbd_xfer_set_frames(xfer, 2); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTF("error=%s\n", usbd_errstr(error)); + if (error != USB_ERR_CANCELLED) { + /* wait for next watchdog timeout */ + } + break; + } +} + +static const struct usb_config ulpt_config[ULPT_N_TRANSFER] = { + [ULPT_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = ULPT_BSIZE, + .flags = {.pipe_bof = 1,.proxy_buffer = 1}, + .callback = &ulpt_write_callback, + }, + + [ULPT_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = ULPT_BSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1}, + .callback = &ulpt_read_callback, + }, + + [ULPT_INTR_DT_RD] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request) + 1, + .callback = &ulpt_status_callback, + .timeout = 1000, /* 1 second */ + }, +}; + +static void +ulpt_start_read(struct usb_fifo *fifo) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_start(sc->sc_xfer[ULPT_BULK_DT_RD]); +} + +static void +ulpt_stop_read(struct usb_fifo *fifo) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_stop(sc->sc_xfer[ULPT_BULK_DT_RD]); +} + +static void +ulpt_start_write(struct usb_fifo *fifo) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_start(sc->sc_xfer[ULPT_BULK_DT_WR]); +} + +static void +ulpt_stop_write(struct usb_fifo *fifo) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_stop(sc->sc_xfer[ULPT_BULK_DT_WR]); +} + +static int +ulpt_open(struct usb_fifo *fifo, int fflags) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + /* we assume that open is a serial process */ + + if (sc->sc_fflags == 0) { + + /* reset USB paralell port */ + + ulpt_reset(sc); + } + return (unlpt_open(fifo, fflags)); +} + +static int +unlpt_open(struct usb_fifo *fifo, int fflags) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + if (sc->sc_fflags & fflags) { + return (EBUSY); + } + if (fflags & FREAD) { + /* clear stall first */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[ULPT_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + if (usb_fifo_alloc_buffer(fifo, + usbd_xfer_max_len(sc->sc_xfer[ULPT_BULK_DT_RD]), + ULPT_IFQ_MAXLEN)) { + return (ENOMEM); + } + /* set which FIFO is opened */ + sc->sc_fifo_open[USB_FIFO_RX] = fifo; + } + if (fflags & FWRITE) { + /* clear stall first */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[ULPT_BULK_DT_WR]); + mtx_unlock(&sc->sc_mtx); + if (usb_fifo_alloc_buffer(fifo, + usbd_xfer_max_len(sc->sc_xfer[ULPT_BULK_DT_WR]), + ULPT_IFQ_MAXLEN)) { + return (ENOMEM); + } + /* set which FIFO is opened */ + sc->sc_fifo_open[USB_FIFO_TX] = fifo; + } + sc->sc_fflags |= fflags & (FREAD | FWRITE); + return (0); +} + +static void +ulpt_close(struct usb_fifo *fifo, int fflags) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + sc->sc_fflags &= ~(fflags & (FREAD | FWRITE)); + + if (fflags & (FREAD | FWRITE)) { + usb_fifo_free_buffer(fifo); + } +} + +static int +ulpt_ioctl(struct usb_fifo *fifo, u_long cmd, void *data, + int fflags) +{ + return (ENODEV); +} + +static const STRUCT_USB_HOST_ID ulpt_devs[] = { + /* Uni-directional USB printer */ + {USB_IFACE_CLASS(UICLASS_PRINTER), + USB_IFACE_SUBCLASS(UISUBCLASS_PRINTER), + USB_IFACE_PROTOCOL(UIPROTO_PRINTER_UNI)}, + + /* Bi-directional USB printer */ + {USB_IFACE_CLASS(UICLASS_PRINTER), + USB_IFACE_SUBCLASS(UISUBCLASS_PRINTER), + USB_IFACE_PROTOCOL(UIPROTO_PRINTER_BI)}, + + /* 1284 USB printer */ + {USB_IFACE_CLASS(UICLASS_PRINTER), + USB_IFACE_SUBCLASS(UISUBCLASS_PRINTER), + USB_IFACE_PROTOCOL(UIPROTO_PRINTER_1284)}, +}; + +static int +ulpt_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + int error; + + DPRINTFN(11, "\n"); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + + error = usbd_lookup_id_by_uaa(ulpt_devs, sizeof(ulpt_devs), uaa); + if (error) + return (error); + + return (BUS_PROBE_GENERIC); +} + +static int +ulpt_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ulpt_softc *sc = device_get_softc(dev); + struct usb_interface_descriptor *id; + int unit = device_get_unit(dev); + int error; + uint8_t iface_index = uaa->info.bIfaceIndex; + uint8_t alt_index; + + DPRINTFN(11, "sc=%p\n", sc); + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, "ulpt lock", NULL, MTX_DEF | MTX_RECURSE); + + usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0); + + /* search through all the descriptors looking for bidir mode */ + + id = usbd_get_interface_descriptor(uaa->iface); + alt_index = 0 - 1; + while (1) { + if (id == NULL) { + break; + } + if ((id->bDescriptorType == UDESC_INTERFACE) && + (id->bLength >= sizeof(*id))) { + if (id->bInterfaceNumber != uaa->info.bIfaceNum) { + break; + } else { + alt_index++; + if ((id->bInterfaceClass == UICLASS_PRINTER) && + (id->bInterfaceSubClass == UISUBCLASS_PRINTER) && + (id->bInterfaceProtocol == UIPROTO_PRINTER_BI)) { + goto found; + } + } + } + id = (void *)usb_desc_foreach( + usbd_get_config_descriptor(uaa->device), (void *)id); + } + goto detach; + +found: + + DPRINTF("setting alternate " + "config number: %d\n", alt_index); + + if (alt_index) { + + error = usbd_set_alt_interface_index + (uaa->device, iface_index, alt_index); + + if (error) { + DPRINTF("could not set alternate " + "config, error=%s\n", usbd_errstr(error)); + goto detach; + } + } + sc->sc_iface_no = id->bInterfaceNumber; + + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, ulpt_config, ULPT_N_TRANSFER, + sc, &sc->sc_mtx); + if (error) { + DPRINTF("error=%s\n", usbd_errstr(error)); + goto detach; + } + device_printf(sc->sc_dev, "using bi-directional mode\n"); + +#if 0 +/* + * This code is disabled because for some mysterious reason it causes + * printing not to work. But only sometimes, and mostly with + * UHCI and less often with OHCI. *sigh* + */ + { + struct usb_config_descriptor *cd = usbd_get_config_descriptor(dev); + struct usb_device_request req; + int len, alen; + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_DEVICE_ID; + USETW(req.wValue, cd->bConfigurationValue); + USETW2(req.wIndex, id->bInterfaceNumber, id->bAlternateSetting); + USETW(req.wLength, sizeof devinfo - 1); + error = usbd_do_request_flags(dev, &req, devinfo, USB_SHORT_XFER_OK, + &alen, USB_DEFAULT_TIMEOUT); + if (error) { + device_printf(sc->sc_dev, "cannot get device id\n"); + } else if (alen <= 2) { + device_printf(sc->sc_dev, "empty device id, no " + "printer connected?\n"); + } else { + /* devinfo now contains an IEEE-1284 device ID */ + len = ((devinfo[0] & 0xff) << 8) | (devinfo[1] & 0xff); + if (len > sizeof devinfo - 3) + len = sizeof devinfo - 3; + devinfo[len] = 0; + printf("%s: device id <", device_get_nameunit(sc->sc_dev)); + ieee1284_print_id(devinfo + 2); + printf(">\n"); + } + } +#endif + + error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &ulpt_fifo_methods, &sc->sc_fifo, + unit, 0 - 1, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + if (error) { + goto detach; + } + error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &unlpt_fifo_methods, &sc->sc_fifo_noreset, + unit, 0 - 1, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + if (error) { + goto detach; + } + /* start reading of status */ + + mtx_lock(&sc->sc_mtx); + ulpt_watchdog(sc); + mtx_unlock(&sc->sc_mtx); + return (0); + +detach: + ulpt_detach(dev); + return (ENOMEM); +} + +static int +ulpt_detach(device_t dev) +{ + struct ulpt_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + usb_fifo_detach(&sc->sc_fifo); + usb_fifo_detach(&sc->sc_fifo_noreset); + + mtx_lock(&sc->sc_mtx); + usb_callout_stop(&sc->sc_watchdog); + mtx_unlock(&sc->sc_mtx); + + usbd_transfer_unsetup(sc->sc_xfer, ULPT_N_TRANSFER); + usb_callout_drain(&sc->sc_watchdog); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +#if 0 +/* XXX This does not belong here. */ + +/* + * Compare two strings until the second ends. + */ + +static uint8_t +ieee1284_compare(const char *a, const char *b) +{ + while (1) { + + if (*b == 0) { + break; + } + if (*a != *b) { + return 1; + } + b++; + a++; + } + return 0; +} + +/* + * Print select parts of an IEEE 1284 device ID. + */ +void +ieee1284_print_id(char *str) +{ + char *p, *q; + + for (p = str - 1; p; p = strchr(p, ';')) { + p++; /* skip ';' */ + if (ieee1284_compare(p, "MFG:") == 0 || + ieee1284_compare(p, "MANUFACTURER:") == 0 || + ieee1284_compare(p, "MDL:") == 0 || + ieee1284_compare(p, "MODEL:") == 0) { + q = strchr(p, ';'); + if (q) + printf("%.*s", (int)(q - p + 1), p); + } + } +} + +#endif + +static void +ulpt_watchdog(void *arg) +{ + struct ulpt_softc *sc = arg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + /* + * Only read status while the device is not opened, due to + * possible hardware or firmware bug in some printers. + */ + if (sc->sc_fflags == 0) + usbd_transfer_start(sc->sc_xfer[ULPT_INTR_DT_RD]); + + usb_callout_reset(&sc->sc_watchdog, + hz, &ulpt_watchdog, sc); +} + +static devclass_t ulpt_devclass; + +static device_method_t ulpt_methods[] = { + DEVMETHOD(device_probe, ulpt_probe), + DEVMETHOD(device_attach, ulpt_attach), + DEVMETHOD(device_detach, ulpt_detach), + {0, 0} +}; + +static driver_t ulpt_driver = { + .name = "ulpt", + .methods = ulpt_methods, + .size = sizeof(struct ulpt_softc), +}; + +DRIVER_MODULE(ulpt, uhub, ulpt_driver, ulpt_devclass, NULL, 0); +MODULE_DEPEND(ulpt, usb, 1, 1, 1); +MODULE_VERSION(ulpt, 1); diff --git a/sys/bus/u4b/serial/umcs.c b/sys/bus/u4b/serial/umcs.c new file mode 100644 index 0000000000..a2b7852300 --- /dev/null +++ b/sys/bus/u4b/serial/umcs.c @@ -0,0 +1,1075 @@ +/*- + * Copyright (c) 2010 Lev Serebryakov . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This driver supports several multiport USB-to-RS232 serial adapters driven + * by MosChip mos7820 and mos7840, bridge chips. + * The adapters are sold under many different brand names. + * + * Datasheets are available at MosChip www site at + * http://www.moschip.com. The datasheets don't contain full + * programming information for the chip. + * + * It is nornal to have only two enabled ports in devices, based on + * quad-port mos7840. + * + */ +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR umcs_debug +#include +#include + +#include + +#include + +#define UMCS7840_MODVER 1 + +#ifdef USB_DEBUG +static int umcs_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, umcs, CTLFLAG_RW, 0, "USB umcs quadport serial adapter"); +SYSCTL_INT(_hw_usb_umcs, OID_AUTO, debug, CTLFLAG_RW, &umcs_debug, 0, "Debug level"); +#endif /* USB_DEBUG */ + + +/* + * Two-port devices (both with 7820 chip and 7840 chip configured as two-port) + * have ports 0 and 2, with ports 1 and 3 omitted. + * So,PHYSICAL port numbers (indexes) on two-port device will be 0 and 2. + * This driver trys to use physical numbers as much as possible. + */ + +/* + * Indexed by PHYSICAL port number. + * Pack non-regular registers to array to easier if-less access. + */ +struct umcs7840_port_registers { + uint8_t reg_sp; /* SP register. */ + uint8_t reg_control; /* CONTROL register. */ + uint8_t reg_dcr; /* DCR0 register. DCR1 & DCR2 can be + * calculated */ +}; + +static const struct umcs7840_port_registers umcs7840_port_registers[UMCS7840_MAX_PORTS] = { + {.reg_sp = MCS7840_DEV_REG_SP1,.reg_control = MCS7840_DEV_REG_CONTROL1,.reg_dcr = MCS7840_DEV_REG_DCR0_1}, + {.reg_sp = MCS7840_DEV_REG_SP2,.reg_control = MCS7840_DEV_REG_CONTROL2,.reg_dcr = MCS7840_DEV_REG_DCR0_2}, + {.reg_sp = MCS7840_DEV_REG_SP3,.reg_control = MCS7840_DEV_REG_CONTROL3,.reg_dcr = MCS7840_DEV_REG_DCR0_3}, + {.reg_sp = MCS7840_DEV_REG_SP4,.reg_control = MCS7840_DEV_REG_CONTROL4,.reg_dcr = MCS7840_DEV_REG_DCR0_4}, +}; + +enum { + UMCS7840_BULK_RD_EP, + UMCS7840_BULK_WR_EP, + UMCS7840_N_TRANSFERS +}; + +struct umcs7840_softc_oneport { + struct usb_xfer *sc_xfer[UMCS7840_N_TRANSFERS]; /* Control structures + * for two transfers */ + + uint8_t sc_lcr; /* local line control register */ + uint8_t sc_mcr; /* local modem control register */ + uint8_t sc_lsr; /* local line status register */ + uint8_t sc_msr; /* local modem status register */ +}; + +struct umcs7840_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom[UMCS7840_MAX_PORTS]; /* Need to be continuous + * array, so indexed by + * LOGICAL port + * (subunit) number */ + + struct usb_xfer *sc_intr_xfer; /* Interrupt endpoint */ + + device_t sc_dev; /* Device for error prints */ + struct usb_device *sc_udev; /* USB Device for all operations */ + struct mtx sc_mtx; /* ucom requires this */ + + uint8_t sc_driver_done; /* Flag when enumeration is finished */ + + uint8_t sc_numports; /* Number of ports (subunits) */ + struct umcs7840_softc_oneport sc_ports[UMCS7840_MAX_PORTS]; /* Indexed by PHYSICAL + * port number. */ +}; + +/* prototypes */ +static usb_error_t umcs7840_get_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t *); +static usb_error_t umcs7840_set_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t); +static usb_error_t umcs7840_get_UART_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t, uint8_t *); +static usb_error_t umcs7840_set_UART_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t, uint8_t); + +static usb_error_t umcs7840_set_baudrate(struct umcs7840_softc *, uint8_t, uint32_t); +static usb_error_t umcs7840_calc_baudrate(uint32_t rate, uint16_t *, uint8_t *); + +static void umcs7840_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); +static void umcs7840_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void umcs7840_cfg_set_rts(struct ucom_softc *, uint8_t); +static void umcs7840_cfg_set_break(struct ucom_softc *, uint8_t); +static void umcs7840_cfg_param(struct ucom_softc *, struct termios *); +static void umcs7840_cfg_open(struct ucom_softc *); +static void umcs7840_cfg_close(struct ucom_softc *); + +static int umcs7840_pre_param(struct ucom_softc *, struct termios *); + +static void umcs7840_start_read(struct ucom_softc *); +static void umcs7840_stop_read(struct ucom_softc *); + +static void umcs7840_start_write(struct ucom_softc *); +static void umcs7840_stop_write(struct ucom_softc *); + +static void umcs7840_poll(struct ucom_softc *ucom); + +static device_probe_t umcs7840_probe; +static device_attach_t umcs7840_attach; +static device_detach_t umcs7840_detach; + +static usb_callback_t umcs7840_intr_callback; +static usb_callback_t umcs7840_read_callback1; +static usb_callback_t umcs7840_read_callback2; +static usb_callback_t umcs7840_read_callback3; +static usb_callback_t umcs7840_read_callback4; +static usb_callback_t umcs7840_write_callback1; +static usb_callback_t umcs7840_write_callback2; +static usb_callback_t umcs7840_write_callback3; +static usb_callback_t umcs7840_write_callback4; + +static void umcs7840_read_callbackN(struct usb_xfer *, usb_error_t, uint8_t); +static void umcs7840_write_callbackN(struct usb_xfer *, usb_error_t, uint8_t); + +/* Indexed by LOGICAL port number (subunit), so two-port device uses 0 & 1 */ +static usb_callback_t *umcs7840_rw_callbacks[UMCS7840_MAX_PORTS][UMCS7840_N_TRANSFERS] = { + {&umcs7840_read_callback1, &umcs7840_write_callback1}, + {&umcs7840_read_callback2, &umcs7840_write_callback2}, + {&umcs7840_read_callback3, &umcs7840_write_callback3}, + {&umcs7840_read_callback4, &umcs7840_write_callback4}, +}; + +static const struct usb_config umcs7840_bulk_config_data[UMCS7840_N_TRANSFERS] = { + [UMCS7840_BULK_RD_EP] = { + .type = UE_BULK, + .endpoint = 0x01, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umcs7840_read_callback1, + .if_index = 0, + }, + + [UMCS7840_BULK_WR_EP] = { + .type = UE_BULK, + .endpoint = 0x02, + .direction = UE_DIR_OUT, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umcs7840_write_callback1, + .if_index = 0, + }, +}; + +static const struct usb_config umcs7840_intr_config_data[1] = { + [0] = { + .type = UE_INTERRUPT, + .endpoint = 0x09, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umcs7840_intr_callback, + .if_index = 0, + }, +}; + +static struct ucom_callback umcs7840_callback = { + .ucom_cfg_get_status = &umcs7840_cfg_get_status, + + .ucom_cfg_set_dtr = &umcs7840_cfg_set_dtr, + .ucom_cfg_set_rts = &umcs7840_cfg_set_rts, + .ucom_cfg_set_break = &umcs7840_cfg_set_break, + + .ucom_cfg_param = &umcs7840_cfg_param, + .ucom_cfg_open = &umcs7840_cfg_open, + .ucom_cfg_close = &umcs7840_cfg_close, + + .ucom_pre_param = &umcs7840_pre_param, + + .ucom_start_read = &umcs7840_start_read, + .ucom_stop_read = &umcs7840_stop_read, + + .ucom_start_write = &umcs7840_start_write, + .ucom_stop_write = &umcs7840_stop_write, + + .ucom_poll = &umcs7840_poll, +}; + +static const STRUCT_USB_HOST_ID umcs7840_devs[] = { + {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7820, 0)}, + {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7840, 0)}, +}; + +static device_method_t umcs7840_methods[] = { + DEVMETHOD(device_probe, umcs7840_probe), + DEVMETHOD(device_attach, umcs7840_attach), + DEVMETHOD(device_detach, umcs7840_detach), + {0, 0} +}; + +static devclass_t umcs7840_devclass; + +static driver_t umcs7840_driver = { + .name = "umcs7840", + .methods = umcs7840_methods, + .size = sizeof(struct umcs7840_softc), +}; + +DRIVER_MODULE(umcs7840, uhub, umcs7840_driver, umcs7840_devclass, 0, 0); +MODULE_DEPEND(umcs7840, ucom, 1, 1, 1); +MODULE_DEPEND(umcs7840, usb, 1, 1, 1); +MODULE_VERSION(umcs7840, UMCS7840_MODVER); + +static int +umcs7840_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != MCS7840_CONFIG_INDEX) + return (ENXIO); + if (uaa->info.bIfaceIndex != MCS7840_IFACE_INDEX) + return (ENXIO); + return (usbd_lookup_id_by_uaa(umcs7840_devs, sizeof(umcs7840_devs), uaa)); +} + +static int +umcs7840_attach(device_t dev) +{ + struct usb_config umcs7840_config_tmp[UMCS7840_N_TRANSFERS]; + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct umcs7840_softc *sc = device_get_softc(dev); + + uint8_t iface_index = MCS7840_IFACE_INDEX; + int error; + int subunit; + int n; + uint8_t data; + + for (n = 0; n < UMCS7840_N_TRANSFERS; ++n) + umcs7840_config_tmp[n] = umcs7840_bulk_config_data[n]; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "umcs7840", NULL, MTX_DEF); + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + + /* + * Get number of ports + * Documentation (full datasheet) says, that number of ports is + * set as MCS7840_DEV_MODE_SELECT24S bit in MODE R/Only + * register. But vendor driver uses these undocumented + * register & bit. + * + * Experiments show, that MODE register can have `0' + * (4 ports) bit on 2-port device, so use vendor driver's way. + * + * Also, see notes in header file for these constants. + */ + umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_GPIO, &data); + if (data & MCS7840_DEV_GPIO_4PORTS) { + sc->sc_numports = 4; + /* Store physical port numbers in sc_portno */ + sc->sc_ucom[0].sc_portno = 0; + sc->sc_ucom[1].sc_portno = 1; + sc->sc_ucom[2].sc_portno = 2; + sc->sc_ucom[3].sc_portno = 3; + } else { + sc->sc_numports = 2; + /* Store physical port numbers in sc_portno */ + sc->sc_ucom[0].sc_portno = 0; + sc->sc_ucom[1].sc_portno = 2; /* '1' is skipped */ + } + device_printf(dev, "Chip mcs%04x, found %d active ports\n", uaa->info.idProduct, sc->sc_numports); + if (!umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_MODE, &data)) { + device_printf(dev, "On-die confguration: RST: active %s, HRD: %s, PLL: %s, POR: %s, Ports: %s, EEPROM write %s, IrDA is %savailable\n", + (data & MCS7840_DEV_MODE_RESET) ? "low" : "high", + (data & MCS7840_DEV_MODE_SER_PRSNT) ? "yes" : "no", + (data & MCS7840_DEV_MODE_PLLBYPASS) ? "bypassed" : "avail", + (data & MCS7840_DEV_MODE_PORBYPASS) ? "bypassed" : "avail", + (data & MCS7840_DEV_MODE_SELECT24S) ? "2" : "4", + (data & MCS7840_DEV_MODE_EEPROMWR) ? "enabled" : "disabled", + (data & MCS7840_DEV_MODE_IRDA) ? "" : "not "); + } + /* Setup all transfers */ + for (subunit = 0; subunit < sc->sc_numports; ++subunit) { + for (n = 0; n < UMCS7840_N_TRANSFERS; ++n) { + /* Set endpoint address */ + umcs7840_config_tmp[n].endpoint = umcs7840_bulk_config_data[n].endpoint + 2 * sc->sc_ucom[subunit].sc_portno; + umcs7840_config_tmp[n].callback = umcs7840_rw_callbacks[subunit][n]; + } + error = usbd_transfer_setup(uaa->device, + &iface_index, sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer, umcs7840_config_tmp, + UMCS7840_N_TRANSFERS, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB transfers failed for subunit %d of %d\n", + subunit + 1, sc->sc_numports); + goto detach; + } + } + error = usbd_transfer_setup(uaa->device, + &iface_index, &sc->sc_intr_xfer, umcs7840_intr_config_data, + 1, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB transfers failed for interrupt\n"); + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + for (subunit = 0; subunit < sc->sc_numports; ++subunit) { + usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_RD_EP]); + usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_WR_EP]); + } + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_numports, sc, + &umcs7840_callback, &sc->sc_mtx); + if (error) + goto detach; + + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + umcs7840_detach(dev); + return (ENXIO); +} + +static int +umcs7840_detach(device_t dev) +{ + struct umcs7840_softc *sc = device_get_softc(dev); + int subunit; + + ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); + + for (subunit = 0; subunit < sc->sc_numports; ++subunit) + usbd_transfer_unsetup(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer, UMCS7840_N_TRANSFERS); + usbd_transfer_unsetup(&sc->sc_intr_xfer, 1); + + mtx_destroy(&sc->sc_mtx); + return (0); +} + +static void +umcs7840_cfg_open(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint16_t pn = ucom->sc_portno; + uint8_t data; + + /* If it very first open, finish global configuration */ + if (!sc->sc_driver_done) { + /* + * USB enumeration is finished, pass internal memory to FIFOs + * If it is done in the end of "attach", kernel panics. + */ + if (umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_CONTROL1, &data)) + return; + data |= MCS7840_DEV_CONTROL1_DRIVER_DONE; + if (umcs7840_set_reg_sync(sc, MCS7840_DEV_REG_CONTROL1, data)) + return; + sc->sc_driver_done = 1; + } + /* Toggle reset bit on-off */ + if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, &data)) + return; + data |= MCS7840_DEV_SPx_UART_RESET; + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data)) + return; + data &= ~MCS7840_DEV_SPx_UART_RESET; + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data)) + return; + + /* Set RS-232 mode */ + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_SCRATCHPAD, MCS7840_UART_SCRATCHPAD_RS232)) + return; + + /* Disable RX on time of initialization */ + if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data)) + return; + data |= MCS7840_DEV_CONTROLx_RX_DISABLE; + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data)) + return; + + /* Disable all interrupts */ + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, 0)) + return; + + /* Reset FIFO -- documented */ + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_FCR, 0)) + return; + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_FCR, + MCS7840_UART_FCR_ENABLE | MCS7840_UART_FCR_FLUSHRHR | + MCS7840_UART_FCR_FLUSHTHR | MCS7840_UART_FCR_RTL_1_14)) + return; + + /* Set 8 bit, no parity, 1 stop bit -- documented */ + sc->sc_ports[pn].sc_lcr = MCS7840_UART_LCR_DATALEN8 | MCS7840_UART_LCR_STOPB1; + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr)) + return; + + /* + * Enable DTR/RTS on modem control, enable modem interrupts -- + * documented + */ + sc->sc_ports[pn].sc_mcr = MCS7840_UART_MCR_DTR | MCS7840_UART_MCR_RTS | MCS7840_UART_MCR_IE; + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr)) + return; + + /* Clearing Bulkin and Bulkout FIFO */ + if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, &data)) + return; + data |= MCS7840_DEV_SPx_RESET_OUT_FIFO | MCS7840_DEV_SPx_RESET_IN_FIFO; + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data)) + return; + data &= ~(MCS7840_DEV_SPx_RESET_OUT_FIFO | MCS7840_DEV_SPx_RESET_IN_FIFO); + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data)) + return; + + /* Set speed 9600 */ + if (umcs7840_set_baudrate(sc, pn, 9600)) + return; + + + /* Finally enable all interrupts -- documented */ + /* + * Copied from vendor driver, I don't know why we should read LCR + * here + */ + if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, &sc->sc_ports[pn].sc_lcr)) + return; + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, + MCS7840_UART_IER_RXSTAT | MCS7840_UART_IER_MODEM)) + return; + + /* Enable RX */ + if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data)) + return; + data &= ~MCS7840_DEV_CONTROLx_RX_DISABLE; + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data)) + return; + + /* Read LSR & MSR */ + if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LSR, &sc->sc_ports[pn].sc_lsr)) + return; + if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_MSR, &sc->sc_ports[pn].sc_msr)) + return; + DPRINTF("Port %d has been opened, LSR=%02x MSR=%02x\n", pn, sc->sc_ports[pn].sc_lsr, sc->sc_ports[pn].sc_msr); +} + +static void +umcs7840_cfg_close(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint16_t pn = ucom->sc_portno; + uint8_t data; + + umcs7840_stop_read(ucom); + umcs7840_stop_write(ucom); + + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, 0); + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, 0); + + /* Disable RX */ + if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data)) + return; + data |= MCS7840_DEV_CONTROLx_RX_DISABLE; + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data)) + return; + DPRINTF("Port %d has been closed\n", pn); +} + +static void +umcs7840_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + if (onoff) + sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_DTR; + else + sc->sc_ports[pn].sc_mcr &= ~MCS7840_UART_MCR_DTR; + + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr); + DPRINTF("Port %d DTR set to: %s\n", pn, onoff ? "on" : "off"); +} + +static void +umcs7840_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + if (onoff) + sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_RTS; + else + sc->sc_ports[pn].sc_mcr &= ~MCS7840_UART_MCR_RTS; + + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr); + DPRINTF("Port %d RTS set to: %s\n", pn, onoff ? "on" : "off"); +} + +static void +umcs7840_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + if (onoff) + sc->sc_ports[pn].sc_lcr |= MCS7840_UART_LCR_BREAK; + else + sc->sc_ports[pn].sc_lcr &= ~MCS7840_UART_LCR_BREAK; + + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr); + DPRINTF("Port %d BREAK set to: %s\n", pn, onoff ? "on" : "off"); +} + + +static void +umcs7840_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + uint8_t lcr = sc->sc_ports[pn].sc_lcr; + uint8_t mcr = sc->sc_ports[pn].sc_mcr; + + DPRINTF("Port %d config:\n", pn); + if (t->c_cflag & CSTOPB) { + DPRINTF(" 2 stop bits\n"); + lcr |= MCS7840_UART_LCR_STOPB2; + } else { + lcr |= MCS7840_UART_LCR_STOPB1; + DPRINTF(" 1 stop bit\n"); + } + + lcr &= ~MCS7840_UART_LCR_PARITYMASK; + if (t->c_cflag & PARENB) { + lcr |= MCS7840_UART_LCR_PARITYON; + if (t->c_cflag & PARODD) { + lcr = MCS7840_UART_LCR_PARITYODD; + DPRINTF(" parity on - odd\n"); + } else { + lcr = MCS7840_UART_LCR_PARITYEVEN; + DPRINTF(" parity on - even\n"); + } + } else { + lcr &= ~MCS7840_UART_LCR_PARITYON; + DPRINTF(" parity off\n"); + } + + lcr &= ~MCS7840_UART_LCR_DATALENMASK; + switch (t->c_cflag & CSIZE) { + case CS5: + lcr |= MCS7840_UART_LCR_DATALEN5; + DPRINTF(" 5 bit\n"); + break; + case CS6: + lcr |= MCS7840_UART_LCR_DATALEN6; + DPRINTF(" 6 bit\n"); + break; + case CS7: + lcr |= MCS7840_UART_LCR_DATALEN7; + DPRINTF(" 7 bit\n"); + break; + case CS8: + lcr |= MCS7840_UART_LCR_DATALEN8; + DPRINTF(" 8 bit\n"); + break; + } + + if (t->c_cflag & CRTSCTS) { + mcr |= MCS7840_UART_MCR_CTSRTS; + DPRINTF(" CTS/RTS\n"); + } else + mcr &= ~MCS7840_UART_MCR_CTSRTS; + + if (t->c_cflag & (CDTR_IFLOW | CDSR_OFLOW)) { + mcr |= MCS7840_UART_MCR_DTRDSR; + DPRINTF(" DTR/DSR\n"); + } else + mcr &= ~MCS7840_UART_MCR_DTRDSR; + + sc->sc_ports[pn].sc_lcr = lcr; + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr); + DPRINTF("Port %d LCR=%02x\n", pn, sc->sc_ports[pn].sc_lcr); + + sc->sc_ports[pn].sc_mcr = mcr; + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr); + DPRINTF("Port %d MCR=%02x\n", pn, sc->sc_ports[pn].sc_mcr); + + umcs7840_set_baudrate(sc, pn, t->c_ospeed); +} + + +static int +umcs7840_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + uint8_t clk; + uint16_t divisor; + + if (umcs7840_calc_baudrate(t->c_ospeed, &divisor, &clk) || !divisor) + return (EINVAL); + return (0); +} + +static void +umcs7840_start_read(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + /* Start interrupt transfer */ + usbd_transfer_start(sc->sc_intr_xfer); + + /* Start read transfer */ + usbd_transfer_start(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_RD_EP]); +} + +static void +umcs7840_stop_read(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + /* Stop read transfer */ + usbd_transfer_stop(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_RD_EP]); +} + +static void +umcs7840_start_write(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + /* Start interrupt transfer */ + usbd_transfer_start(sc->sc_intr_xfer); + + /* Start write transfer */ + usbd_transfer_start(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_WR_EP]); +} + +static void +umcs7840_stop_write(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + /* Stop write transfer */ + usbd_transfer_stop(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_WR_EP]); +} + +static void +umcs7840_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + + *lsr = sc->sc_ports[ucom->sc_portno].sc_lsr; + *msr = sc->sc_ports[ucom->sc_portno].sc_msr; + DPRINTF("Port %d status: LSR=%02x MSR=%02x\n", ucom->sc_portno, *lsr, *msr); +} + +static void +umcs7840_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umcs7840_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[13]; + int actlen; + int subunit; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen == 5 || actlen == 13) { + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, actlen); + /* Check status of all ports */ + for (subunit = 0; subunit < sc->sc_numports; ++subunit) { + uint8_t pn = sc->sc_ucom[subunit].sc_portno; + + if (buf[pn] & MCS7840_UART_ISR_NOPENDING) + continue; + DPRINTF("Port %d has pending interrupt: %02x (FIFO: %02x)\n", pn, buf[pn] & MCS7840_UART_ISR_INTMASK, buf[pn] & (~MCS7840_UART_ISR_INTMASK)); + switch (buf[pn] & MCS7840_UART_ISR_INTMASK) { + case MCS7840_UART_ISR_RXERR: + case MCS7840_UART_ISR_RXHASDATA: + case MCS7840_UART_ISR_RXTIMEOUT: + /* Read new LSR */ + if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LSR, &sc->sc_ports[pn].sc_lsr)) + break; /* Inner switch */ + ucom_status_change(&sc->sc_ucom[subunit]); + /* Inner switch */ + break; + case MCS7840_UART_ISR_TXEMPTY: + /* Do nothing */ + break; /* Inner switch */ + case MCS7840_UART_ISR_MSCHANGE: + /* Read new MSR */ + if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_MSR, &sc->sc_ports[pn].sc_msr)) + break; /* Inner switch */ + DPRINTF("Port %d: new MSR %02x\n", pn, sc->sc_ports[pn].sc_msr); + ucom_status_change(&sc->sc_ucom[subunit]); + break; + } + } + } else + device_printf(sc->sc_dev, "Invalid interrupt data length %d", actlen); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umcs7840_read_callback1(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_read_callbackN(xfer, error, 0); +} + +static void +umcs7840_read_callback2(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_read_callbackN(xfer, error, 1); +} +static void +umcs7840_read_callback3(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_read_callbackN(xfer, error, 2); +} + +static void +umcs7840_read_callback4(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_read_callbackN(xfer, error, 3); +} + +static void +umcs7840_read_callbackN(struct usb_xfer *xfer, usb_error_t error, uint8_t subunit) +{ + struct umcs7840_softc *sc = usbd_xfer_softc(xfer); + struct ucom_softc *ucom = &sc->sc_ucom[subunit]; + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + DPRINTF("Port %d read, state = %d, data length = %d\n", ucom->sc_portno, USB_GET_STATE(xfer), actlen); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(ucom, pc, 0, actlen); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umcs7840_write_callback1(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_write_callbackN(xfer, error, 0); +} + +static void +umcs7840_write_callback2(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_write_callbackN(xfer, error, 1); +} + +static void +umcs7840_write_callback3(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_write_callbackN(xfer, error, 2); +} + +static void +umcs7840_write_callback4(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_write_callbackN(xfer, error, 3); +} + +static void +umcs7840_write_callbackN(struct usb_xfer *xfer, usb_error_t error, uint8_t subunit) +{ + struct umcs7840_softc *sc = usbd_xfer_softc(xfer); + struct ucom_softc *ucom = &sc->sc_ucom[subunit]; + struct usb_page_cache *pc; + uint32_t actlen; + + DPRINTF("Port %d write, state = %d\n", ucom->sc_portno, USB_GET_STATE(xfer)); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(ucom, pc, 0, usbd_xfer_max_len(xfer), &actlen)) { + DPRINTF("Port %d write, has %d bytes\n", ucom->sc_portno, actlen); + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umcs7840_poll(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + + DPRINTF("Port %d poll\n", ucom->sc_portno); + usbd_transfer_poll(sc->sc_ports[ucom->sc_portno].sc_xfer, UMCS7840_N_TRANSFERS); + usbd_transfer_poll(&sc->sc_intr_xfer, 1); +} + +static usb_error_t +umcs7840_get_reg_sync(struct umcs7840_softc *sc, uint8_t reg, uint8_t *data) +{ + struct usb_device_request req; + usb_error_t err; + uint16_t len; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = MCS7840_RDREQ; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, UMCS7840_READ_LENGTH); + + err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, (void *)data, 0, &len, UMCS7840_CTRL_TIMEOUT); + if (err == USB_ERR_NORMAL_COMPLETION && len != 1) { + device_printf(sc->sc_dev, "Reading register %d failed: invalid length %d\n", reg, len); + return (USB_ERR_INVAL); + } else if (err) + device_printf(sc->sc_dev, "Reading register %d failed: %s\n", reg, usbd_errstr(err)); + return (err); +} + +static usb_error_t +umcs7840_set_reg_sync(struct umcs7840_softc *sc, uint8_t reg, uint8_t data) +{ + struct usb_device_request req; + usb_error_t err; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = MCS7840_WRREQ; + USETW(req.wValue, data); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, NULL, 0, NULL, UMCS7840_CTRL_TIMEOUT); + if (err) + device_printf(sc->sc_dev, "Writing register %d failed: %s\n", reg, usbd_errstr(err)); + + return (err); +} + +static usb_error_t +umcs7840_get_UART_reg_sync(struct umcs7840_softc *sc, uint8_t portno, uint8_t reg, uint8_t *data) +{ + struct usb_device_request req; + uint16_t wVal; + usb_error_t err; + uint16_t len; + + /* portno is port number */ + wVal = ((uint16_t)(portno + 1)) << 8; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = MCS7840_RDREQ; + USETW(req.wValue, wVal); + USETW(req.wIndex, reg); + USETW(req.wLength, UMCS7840_READ_LENGTH); + + err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, (void *)data, 0, &len, UMCS7840_CTRL_TIMEOUT); + if (err == USB_ERR_NORMAL_COMPLETION && len != 1) { + device_printf(sc->sc_dev, "Reading UART%d register %d failed: invalid length %d\n", portno, reg, len); + return (USB_ERR_INVAL); + } else if (err) + device_printf(sc->sc_dev, "Reading UART%d register %d failed: %s\n", portno, reg, usbd_errstr(err)); + return (err); +} + +static usb_error_t +umcs7840_set_UART_reg_sync(struct umcs7840_softc *sc, uint8_t portno, uint8_t reg, uint8_t data) +{ + struct usb_device_request req; + usb_error_t err; + uint16_t wVal; + + /* portno is port number */ + wVal = ((uint16_t)(portno + 1)) << 8 | data; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = MCS7840_WRREQ; + USETW(req.wValue, wVal); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, NULL, 0, NULL, UMCS7840_CTRL_TIMEOUT); + if (err) + device_printf(sc->sc_dev, "Writing UART%d register %d failed: %s\n", portno, reg, usbd_errstr(err)); + return (err); +} + +static usb_error_t +umcs7840_set_baudrate(struct umcs7840_softc *sc, uint8_t portno, uint32_t rate) +{ + usb_error_t err; + uint16_t divisor; + uint8_t clk; + uint8_t data; + + if (umcs7840_calc_baudrate(rate, &divisor, &clk)) { + DPRINTF("Port %d bad speed: %d\n", portno, rate); + return (-1); + } + if (divisor == 0 || (clk & MCS7840_DEV_SPx_CLOCK_MASK) != clk) { + DPRINTF("Port %d bad speed calculation: %d\n", portno, rate); + return (-1); + } + DPRINTF("Port %d set speed: %d (%02x / %d)\n", portno, rate, clk, divisor); + + /* Set clock source for standard BAUD frequences */ + err = umcs7840_get_reg_sync(sc, umcs7840_port_registers[portno].reg_sp, &data); + if (err) + return (err); + data &= MCS7840_DEV_SPx_CLOCK_MASK; + data |= clk; + err = umcs7840_set_reg_sync(sc, umcs7840_port_registers[portno].reg_sp, data); + if (err) + return (err); + + /* Set divider */ + sc->sc_ports[portno].sc_lcr |= MCS7840_UART_LCR_DIVISORS; + err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_LCR, sc->sc_ports[portno].sc_lcr); + if (err) + return (err); + + err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_DLL, (uint8_t)(divisor & 0xff)); + if (err) + return (err); + err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_DLM, (uint8_t)((divisor >> 8) & 0xff)); + if (err) + return (err); + + /* Turn off access to DLL/DLM registers of UART */ + sc->sc_ports[portno].sc_lcr &= ~MCS7840_UART_LCR_DIVISORS; + err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_LCR, sc->sc_ports[portno].sc_lcr); + if (err) + return (err); + return (0); +} + +/* Maximum speeds for standard frequences, when PLL is not used */ +static const uint32_t umcs7840_baudrate_divisors[] = {0, 115200, 230400, 403200, 460800, 806400, 921600, 1572864, 3145728,}; +static const uint8_t umcs7840_baudrate_divisors_len = sizeof(umcs7840_baudrate_divisors) / sizeof(umcs7840_baudrate_divisors[0]); + +static usb_error_t +umcs7840_calc_baudrate(uint32_t rate, uint16_t *divisor, uint8_t *clk) +{ + uint8_t i = 0; + + if (rate > umcs7840_baudrate_divisors[umcs7840_baudrate_divisors_len - 1]) + return (-1); + + for (i = 0; i < umcs7840_baudrate_divisors_len - 1 && + !(rate > umcs7840_baudrate_divisors[i] && rate <= umcs7840_baudrate_divisors[i + 1]); ++i); + *divisor = umcs7840_baudrate_divisors[i + 1] / rate; + /* 0x00 .. 0x70 */ + *clk = i << MCS7840_DEV_SPx_CLOCK_SHIFT; + return (0); +} diff --git a/sys/bus/u4b/serial/umcs.h b/sys/bus/u4b/serial/umcs.h new file mode 100644 index 0000000000..310b4af65f --- /dev/null +++ b/sys/bus/u4b/serial/umcs.h @@ -0,0 +1,644 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2010 Lev Serebryakov . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#ifndef _UMCS7840_H_ +#define _UMCS7840_H_ + +#define UMCS7840_MAX_PORTS 4 + +#define UMCS7840_READ_LENGTH 1 /* bytes */ +#define UMCS7840_CTRL_TIMEOUT 500 /* ms */ + +/* Read/Wrtire registers vendor commands */ +#define MCS7840_RDREQ 0x0d +#define MCS7840_WRREQ 0x0e + +/* Read/Wrtie EEPROM values */ +#define MCS7840_EEPROM_RW_WVALUE 0x0900 + +/* + * All these registers are documented only in full datasheet, + * which can be requested from MosChip tech support. + */ +#define MCS7840_DEV_REG_SP1 0x00 /* Options for for UART 1, R/W */ +#define MCS7840_DEV_REG_CONTROL1 0x01 /* Control bits for UART 1, + * R/W */ +#define MCS7840_DEV_REG_PINPONGHIGH 0x02 /* High bits of ping-pong + * register, R/W */ +#define MCS7840_DEV_REG_PINPONGLOW 0x03 /* Low bits of ping-pong + * register, R/W */ +/* DCRx_1 Registers goes here (see below, they are documented) */ +#define MCS7840_DEV_REG_GPIO 0x07 /* GPIO_0 and GPIO_1 bits, + * undocumented, see notes + * below R/W */ +#define MCS7840_DEV_REG_SP2 0x08 /* Options for for UART 2, R/W */ +#define MCS7840_DEV_REG_CONTROL2 0x09 /* Control bits for UART 2, + * R/W */ +#define MCS7840_DEV_REG_SP3 0x0a /* Options for for UART 3, R/W */ +#define MCS7840_DEV_REG_CONTROL3 0x0b /* Control bits for UART 3, + * R/W */ +#define MCS7840_DEV_REG_SP4 0x0c /* Options for for UART 4, R/W */ +#define MCS7840_DEV_REG_CONTROL4 0x0d /* Control bits for UART 4, + * R/W */ +#define MCS7840_DEV_REG_PLL_DIV_M 0x0e /* Pre-diviedr for PLL, R/W */ +#define MCS7840_DEV_REG_UNKNOWN1 0x0f /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_PLL_DIV_N 0x10 /* Loop divider for PLL, R/W */ +#define MCS7840_DEV_REG_CLOCK_MUX 0x12 /* PLL input clock & Interrupt + * endpoint control, R/W */ +#define MCS7840_DEV_REG_UNKNOWN2 0x11 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_CLOCK_SELECT12 0x13 /* Clock source for ports 1 & + * 2, R/W */ +#define MCS7840_DEV_REG_CLOCK_SELECT34 0x14 /* Clock source for ports 3 & + * 4, R/W */ +#define MCS7840_DEV_REG_UNKNOWN3 0x15 /* NOT MENTIONED AND NOT USED */ +/* DCRx_2-DCRx_4 Registers goes here (see below, they are documented) */ +#define MCS7840_DEV_REG_UNKNOWN4 0x1f /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWN5 0x20 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWN6 0x21 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWN7 0x22 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWN8 0x23 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWN9 0x24 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWNA 0x25 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWNB 0x26 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWNC 0x27 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWND 0x28 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWNE 0x29 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWNF 0x2a /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_MODE 0x2b /* Hardware configuration, + * R/Only */ +#define MCS7840_DEV_REG_SP1_ICG 0x2c /* Inter character gap + * configuration for Port 1, + * R/W */ +#define MCS7840_DEV_REG_SP2_ICG 0x2d /* Inter character gap + * configuration for Port 2, + * R/W */ +#define MCS7840_DEV_REG_SP3_ICG 0x2e /* Inter character gap + * configuration for Port 3, + * R/W */ +#define MCS7840_DEV_REG_SP4_ICG 0x2f /* Inter character gap + * configuration for Port 4, + * R/W */ +#define MCS7840_DEV_REG_RX_SAMPLING12 0x30 /* RX sampling for ports 1 & + * 2, R/W */ +#define MCS7840_DEV_REG_RX_SAMPLING34 0x31 /* RX sampling for ports 3 & + * 4, R/W */ +#define MCS7840_DEV_REG_BI_FIFO_STAT1 0x32 /* Bulk-In FIFO Stat for Port + * 1, contains number of + * availiable bytes, R/Only */ +#define MCS7840_DEV_REG_BO_FIFO_STAT1 0x33 /* Bulk-out FIFO Stat for Port + * 1, contains number of + * availiable bytes, R/Only */ +#define MCS7840_DEV_REG_BI_FIFO_STAT2 0x34 /* Bulk-In FIFO Stat for Port + * 2, contains number of + * availiable bytes, R/Only */ +#define MCS7840_DEV_REG_BO_FIFO_STAT2 0x35 /* Bulk-out FIFO Stat for Port + * 2, contains number of + * availiable bytes, R/Only */ +#define MCS7840_DEV_REG_BI_FIFO_STAT3 0x36 /* Bulk-In FIFO Stat for Port + * 3, contains number of + * availiable bytes, R/Only */ +#define MCS7840_DEV_REG_BO_FIFO_STAT3 0x37 /* Bulk-out FIFO Stat for Port + * 3, contains number of + * availiable bytes, R/Only */ +#define MCS7840_DEV_REG_BI_FIFO_STAT4 0x38 /* Bulk-In FIFO Stat for Port + * 4, contains number of + * availiable bytes, R/Only */ +#define MCS7840_DEV_REG_BO_FIFO_STAT4 0x39 /* Bulk-out FIFO Stat for Port + * 4, contains number of + * availiable bytes, R/Only */ +#define MCS7840_DEV_REG_ZERO_PERIOD1 0x3a /* Period between zero out + * frames for Port 1, R/W */ +#define MCS7840_DEV_REG_ZERO_PERIOD2 0x3b /* Period between zero out + * frames for Port 1, R/W */ +#define MCS7840_DEV_REG_ZERO_PERIOD3 0x3c /* Period between zero out + * frames for Port 1, R/W */ +#define MCS7840_DEV_REG_ZERO_PERIOD4 0x3d /* Period between zero out + * frames for Port 1, R/W */ +#define MCS7840_DEV_REG_ZERO_ENABLE 0x3e /* Enable/disable of zero out + * frames, R/W */ +#define MCS7840_DEV_REG_THR_VAL_LOW1 0x3f /* Low 8 bits of threshhold + * value for Bulk-Out for Port + * 1, R/W */ +#define MCS7840_DEV_REG_THR_VAL_HIGH1 0x40 /* High 1 bit of threshhold + * value for Bulk-Out and + * enable flag for Port 1, R/W */ +#define MCS7840_DEV_REG_THR_VAL_LOW2 0x41 /* Low 8 bits of threshhold + * value for Bulk-Out for Port + * 2, R/W */ +#define MCS7840_DEV_REG_THR_VAL_HIGH2 0x42 /* High 1 bit of threshhold + * value for Bulk-Out and + * enable flag for Port 2, R/W */ +#define MCS7840_DEV_REG_THR_VAL_LOW3 0x43 /* Low 8 bits of threshhold + * value for Bulk-Out for Port + * 3, R/W */ +#define MCS7840_DEV_REG_THR_VAL_HIGH3 0x44 /* High 1 bit of threshhold + * value for Bulk-Out and + * enable flag for Port 3, R/W */ +#define MCS7840_DEV_REG_THR_VAL_LOW4 0x45 /* Low 8 bits of threshhold + * value for Bulk-Out for Port + * 4, R/W */ +#define MCS7840_DEV_REG_THR_VAL_HIGH4 0x46 /* High 1 bit of threshhold + * value for Bulk-Out and + * enable flag for Port 4, R/W */ + +/* Bits for SPx registers */ +#define MCS7840_DEV_SPx_LOOP_PIPES 0x01 /* Loop Bulk-Out FIFO to the + * Bulk-In FIFO, default = 0 */ +#define MCS7840_DEV_SPx_SKIP_ERR_DATA 0x02 /* Drop data bytes from UART, + * which were recevied with + * errors, default = 0 */ +#define MCS7840_DEV_SPx_RESET_OUT_FIFO 0x04 /* Reset Bulk-Out FIFO */ +#define MCS7840_DEV_SPx_RESET_IN_FIFO 0x08 /* Reset Bulk-In FIFO */ +#define MCS7840_DEV_SPx_CLOCK_MASK 0x70 /* Mask to extract Baud CLK + * source */ +#define MCS7840_DEV_SPx_CLOCK_X1 0x00 /* CLK = 1.8432Mhz, max speed + * = 115200 bps, default */ +#define MCS7840_DEV_SPx_CLOCK_X2 0x10 /* CLK = 3.6864Mhz, max speed + * = 230400 bps */ +#define MCS7840_DEV_SPx_CLOCK_X35 0x20 /* CLK = 6.4512Mhz, max speed + * = 403200 bps */ +#define MCS7840_DEV_SPx_CLOCK_X4 0x30 /* CLK = 7.3728Mhz, max speed + * = 460800 bps */ +#define MCS7840_DEV_SPx_CLOCK_X7 0x40 /* CLK = 12.9024Mhz, max speed + * = 806400 bps */ +#define MCS7840_DEV_SPx_CLOCK_X8 0x50 /* CLK = 14.7456Mhz, max speed + * = 921600 bps */ +#define MCS7840_DEV_SPx_CLOCK_24MHZ 0x60 /* CLK = 24.0000Mhz, max speed + * = 1.5 Mbps */ +#define MCS7840_DEV_SPx_CLOCK_48MHZ 0x70 /* CLK = 48.0000Mhz, max speed + * = 3.0 Mbps */ +#define MCS7840_DEV_SPx_CLOCK_SHIFT 4 /* Value 0..7 can be shifted + * to get clock value */ +#define MCS7840_DEV_SPx_UART_RESET 0x80 /* Reset UART */ + +/* Bits for CONTROLx registers */ +#define MCS7840_DEV_CONTROLx_HWFC 0x01 /* Enable hardware flow + * control (when power + * down? It is unclear + * in documents), + * default = 0 */ +#define MCS7840_DEV_CONTROLx_UNUNSED1 0x02 /* Reserved */ +#define MCS7840_DEV_CONTROLx_CTS_ENABLE 0x04 /* CTS changes are + * translated to MSR, + * default = 0 */ +#define MCS7840_DEV_CONTROLx_UNUSED2 0x08 /* Reserved for ports + * 2,3,4 */ +#define MCS7840_DEV_CONTROL1_DRIVER_DONE 0x08 /* USB enumerating is + * finished, USB + * enumeration memory + * can be used as FIFOs */ +#define MCS7840_DEV_CONTROLx_RX_NEGATE 0x10 /* Negate RX input, + * works for IrDA mode + * only, default = 0 */ +#define MCS7840_DEV_CONTROLx_RX_DISABLE 0x20 /* Disable RX logic, + * works only for + * RS-232/RS-485 mode, + * default = 0 */ +#define MCS7840_DEV_CONTROLx_FSM_CONTROL 0x40 /* Disable RX FSM when + * TX is in progress, + * works for IrDA mode + * only, default = 0 */ +#define MCS7840_DEV_CONTROLx_UNUSED3 0x80 /* Reserved */ + +/* + * Bits for PINPONGx registers + * These registers control how often two input buffers + * for Bulk-In FIFOs are swapped. One of buffers is used + * for USB trnasfer, other for receiving data from UART. + * Exact meaning of 15 bit value in these registers is unknown + */ +#define MCS7840_DEV_PINPONGHIGH_MULT 128 /* Only 7 bits in PINPONGLOW + * register */ +#define MCS7840_DEV_PINPONGLOW_BITS 7 /* Only 7 bits in PINPONGLOW + * register */ + +/* + * THIS ONE IS UNDOCUMENTED IN FULL DATASHEET, but e-mail from tech support + * confirms, that it is register for GPIO_0 and GPIO_1 data input/output. + * Chips has 2 GPIO, but first one (lower bit) MUST be used by device + * authors as "number of port" indicator, grounded (0) for two-port + * devices and pulled-up to 1 for 4-port devices. + */ +#define MCS7840_DEV_GPIO_4PORTS 0x01 /* Device has 4 ports + * configured */ +#define MCS7840_DEV_GPIO_GPIO_0 0x01 /* The same as above */ +#define MCS7840_DEV_GPIO_GPIO_1 0x02 /* GPIO_1 data */ + +/* + * Constants for PLL dividers + * Ouptut frequency of PLL is: + * Fout = (N/M) * Fin. + * Default PLL input frequency Fin is 12Mhz (on-chip). + */ +#define MCS7840_DEV_PLL_DIV_M_BITS 6 /* Number of useful bits for M + * divider */ +#define MCS7840_DEV_PLL_DIV_M_MASK 0x3f /* Mask for M divider */ +#define MCS7840_DEV_PLL_DIV_M_MIN 1 /* Minimum value for M, 0 is + * forbidden */ +#define MCS7840_DEV_PLL_DIV_M_DEF 1 /* Default value for M */ +#define MCS7840_DEV_PLL_DIV_M_MAX 63 /* Maximum value for M */ +#define MCS7840_DEV_PLL_DIV_N_BITS 6 /* Number of useful bits for N + * divider */ +#define MCS7840_DEV_PLL_DIV_N_MASK 0x3f /* Mask for N divider */ +#define MCS7840_DEV_PLL_DIV_N_MIN 1 /* Minimum value for N, 0 is + * forbidden */ +#define MCS7840_DEV_PLL_DIV_N_DEF 8 /* Default value for N */ +#define MCS7840_DEV_PLL_DIV_N_MAX 63 /* Maximum value for N */ + +/* Bits for CLOCK_MUX register */ +#define MCS7840_DEV_CLOCK_MUX_INPUTMASK 0x03 /* Mask to extract PLL clock + * input */ +#define MCS7840_DEV_CLOCK_MUX_IN12MHZ 0x00 /* 12Mhz PLL input, default */ +#define MCS7840_DEV_CLOCK_MUX_INEXTRN 0x01 /* External (device-depended) + * PLL input */ +#define MCS7840_DEV_CLOCK_MUX_INRSV1 0x02 /* Reserved */ +#define MCS7840_DEV_CLOCK_MUX_INRSV2 0x03 /* Reserved */ +#define MCS7840_DEV_CLOCK_MUX_PLLHIGH 0x04 /* 0 = PLL Output is + * 20MHz-100MHz (default), 1 = + * 100MHz-300MHz range */ +#define MCS7840_DEV_CLOCK_MUX_INTRFIFOS 0x08 /* Enable additional 8 bytes + * fro Interrupt USB pipe with + * USB FIFOs statuses, default + * = 0 */ +#define MCS7840_DEV_CLOCK_MUX_RESERVED1 0x10 /* Unused */ +#define MCS7840_DEV_CLOCK_MUX_RESERVED2 0x20 /* Unused */ +#define MCS7840_DEV_CLOCK_MUX_RESERVED3 0x40 /* Unused */ +#define MCS7840_DEV_CLOCK_MUX_RESERVED4 0x80 /* Unused */ + +/* Bits for CLOCK_SELECTxx registers */ +#define MCS7840_DEV_CLOCK_SELECT1_MASK 0x07 /* Bits for port 1 in + * CLOCK_SELECT12 */ +#define MCS7840_DEV_CLOCK_SELECT1_SHIFT 0 /* Shift for port 1in + * CLOCK_SELECT12 */ +#define MCS7840_DEV_CLOCK_SELECT2_MASK 0x38 /* Bits for port 2 in + * CLOCK_SELECT12 */ +#define MCS7840_DEV_CLOCK_SELECT2_SHIFT 3 /* Shift for port 2 in + * CLOCK_SELECT12 */ +#define MCS7840_DEV_CLOCK_SELECT3_MASK 0x07 /* Bits for port 3 in + * CLOCK_SELECT23 */ +#define MCS7840_DEV_CLOCK_SELECT3_SHIFT 0 /* Shift for port 3 in + * CLOCK_SELECT23 */ +#define MCS7840_DEV_CLOCK_SELECT4_MASK 0x38 /* Bits for port 4 in + * CLOCK_SELECT23 */ +#define MCS7840_DEV_CLOCK_SELECT4_SHIFT 3 /* Shift for port 4 in + * CLOCK_SELECT23 */ +#define MCS7840_DEV_CLOCK_SELECT_STD 0x00 /* STANDARD baudrate derived + * from 96Mhz, default for all + * ports */ +#define MCS7840_DEV_CLOCK_SELECT_30MHZ 0x01 /* 30Mhz */ +#define MCS7840_DEV_CLOCK_SELECT_96MHZ 0x02 /* 96Mhz direct */ +#define MCS7840_DEV_CLOCK_SELECT_120MHZ 0x03 /* 120Mhz */ +#define MCS7840_DEV_CLOCK_SELECT_PLL 0x04 /* PLL output (see for M and N + * dividers) */ +#define MCS7840_DEV_CLOCK_SELECT_EXT 0x05 /* External clock input + * (device-dependend) */ +#define MCS7840_DEV_CLOCK_SELECT_RES1 0x06 /* Unused */ +#define MCS7840_DEV_CLOCK_SELECT_RES2 0x07 /* Unused */ + +/* Bits for MODE register */ +#define MCS7840_DEV_MODE_RESERVED1 0x01 /* Unused */ +#define MCS7840_DEV_MODE_RESET 0x02 /* 0: RESET = Active High + * (default), 1: Reserved (?) */ +#define MCS7840_DEV_MODE_SER_PRSNT 0x04 /* 0: Reserved, 1: Do not use + * hardocded values (default) + * (?) */ +#define MCS7840_DEV_MODE_PLLBYPASS 0x08 /* 1: PLL output is bypassed, + * default = 0 */ +#define MCS7840_DEV_MODE_PORBYPASS 0x10 /* 1: Power-On Reset is + * bypassed, default = 0 */ +#define MCS7840_DEV_MODE_SELECT24S 0x20 /* 0: 4 Serial Ports / IrDA + * active, 1: 2 Serial Ports / + * IrDA active */ +#define MCS7840_DEV_MODE_EEPROMWR 0x40 /* EEPROM write is enabled, + * default */ +#define MCS7840_DEV_MODE_IRDA 0x80 /* IrDA mode is activated + * (could be turned on), + * default */ + +/* Bits for SPx ICG */ +#define MCS7840_DEV_SPx_ICG_DEF 0x24 /* All 8 bits is used as + * number of BAUD clocks of + * pause */ + +/* + * Bits for RX_SAMPLINGxx registers + * These registers control when bit value will be sampled within + * the baud period. + * 0 is very beginning of period, 15 is very end, 7 is the middle. + */ +#define MCS7840_DEV_RX_SAMPLING1_MASK 0x0f /* Bits for port 1 in + * RX_SAMPLING12 */ +#define MCS7840_DEV_RX_SAMPLING1_SHIFT 0 /* Shift for port 1in + * RX_SAMPLING12 */ +#define MCS7840_DEV_RX_SAMPLING2_MASK 0xf0 /* Bits for port 2 in + * RX_SAMPLING12 */ +#define MCS7840_DEV_RX_SAMPLING2_SHIFT 4 /* Shift for port 2 in + * RX_SAMPLING12 */ +#define MCS7840_DEV_RX_SAMPLING3_MASK 0x0f /* Bits for port 3 in + * RX_SAMPLING23 */ +#define MCS7840_DEV_RX_SAMPLING3_SHIFT 0 /* Shift for port 3 in + * RX_SAMPLING23 */ +#define MCS7840_DEV_RX_SAMPLING4_MASK 0xf0 /* Bits for port 4 in + * RX_SAMPLING23 */ +#define MCS7840_DEV_RX_SAMPLING4_SHIFT 4 /* Shift for port 4 in + * RX_SAMPLING23 */ +#define MCS7840_DEV_RX_SAMPLINGx_MIN 0 /* Max for any RX Sampling */ +#define MCS7840_DEV_RX_SAMPLINGx_DEF 7 /* Default for any RX + * Sampling, center of period */ +#define MCS7840_DEV_RX_SAMPLINGx_MAX 15 /* Min for any RX Sampling */ + +/* Bits for ZERO_PERIODx */ +#define MCS7840_DEV_ZERO_PERIODx_DEF 20 /* Number of Bulk-in requests + * befor sending zero-sized + * reply */ + +/* Bits for ZERO_ENABLE */ +#define MCS7840_DEV_ZERO_ENABLE_PORT1 0x01 /* Enable of sending + * zero-sized replies for port + * 1, default */ +#define MCS7840_DEV_ZERO_ENABLE_PORT2 0x02 /* Enable of sending + * zero-sized replies for port + * 2, default */ +#define MCS7840_DEV_ZERO_ENABLE_PORT3 0x04 /* Enable of sending + * zero-sized replies for port + * 3, default */ +#define MCS7840_DEV_ZERO_ENABLE_PORT4 0x08 /* Enable of sending + * zero-sized replies for port + * 4, default */ + +/* Bits for THR_VAL_HIGHx */ +#define MCS7840_DEV_THR_VAL_HIGH_MASK 0x01 /* Only one bit is used */ +#define MCS7840_DEV_THR_VAL_HIGH_MUL 256 /* This one bit is means "256" */ +#define MCS7840_DEV_THR_VAL_HIGH_SHIFT 8 /* This one bit is means "256" */ +#define MCS7840_DEV_THR_VAL_HIGH_ENABLE 0x80 /* Enable threshold */ + +/* These are documented in "public" datasheet */ +#define MCS7840_DEV_REG_DCR0_1 0x04 /* Device contol register 0 for Port + * 1, R/W */ +#define MCS7840_DEV_REG_DCR1_1 0x05 /* Device contol register 1 for Port + * 1, R/W */ +#define MCS7840_DEV_REG_DCR2_1 0x06 /* Device contol register 2 for Port + * 1, R/W */ +#define MCS7840_DEV_REG_DCR0_2 0x16 /* Device contol register 0 for Port + * 2, R/W */ +#define MCS7840_DEV_REG_DCR1_2 0x17 /* Device contol register 1 for Port + * 2, R/W */ +#define MCS7840_DEV_REG_DCR2_2 0x18 /* Device contol register 2 for Port + * 2, R/W */ +#define MCS7840_DEV_REG_DCR0_3 0x19 /* Device contol register 0 for Port + * 3, R/W */ +#define MCS7840_DEV_REG_DCR1_3 0x1a /* Device contol register 1 for Port + * 3, R/W */ +#define MCS7840_DEV_REG_DCR2_3 0x1b /* Device contol register 2 for Port + * 3, R/W */ +#define MCS7840_DEV_REG_DCR0_4 0x1c /* Device contol register 0 for Port + * 4, R/W */ +#define MCS7840_DEV_REG_DCR1_4 0x1d /* Device contol register 1 for Port + * 4, R/W */ +#define MCS7840_DEV_REG_DCR2_4 0x1e /* Device contol register 2 for Port + * 4, R/W */ + +/* Bits of DCR0 registers, documented in datasheet */ +#define MCS7840_DEV_DCR0_PWRSAVE 0x01 /* Shutdown transiver + * when USB Suspend is + * engaged, default = 1 */ +#define MCS7840_DEV_DCR0_RESERVED1 0x02 /* Unused */ +#define MCS7840_DEV_DCR0_GPIO_MODE_MASK 0x0c /* GPIO Mode bits, WORKS + * ONLY FOR PORT 1 */ +#define MCS7840_DEV_DCR0_GPIO_MODE_IN 0x00 /* GPIO Mode - Input + * (0b00), WORKS ONLY + * FOR PORT 1 */ +#define MCS7840_DEV_DCR0_GPIO_MODE_OUT 0x08 /* GPIO Mode - Input + * (0b10), WORKS ONLY + * FOR PORT 1 */ +#define MCS7840_DEV_DCR0_RTS_ACTIVE_HIGH 0x10 /* RTS Active is HIGH, + * default = 0 (low) */ +#define MCS7840_DEV_DCR0_RTS_AUTO 0x20 /* RTS is controlled by + * state of TX buffer, + * default = 0 + * (controlled by MCR) */ +#define MCS7840_DEV_DCR0_IRDA 0x40 /* IrDA mode */ +#define MCS7840_DEV_DCR0_RESERVED2 0x80 /* Unused */ + +/* Bits of DCR1 registers, documented in datasheet */ +#define MCS7840_DEV_DCR1_GPIO_CURRENT_MASK 0x03 /* Mask to extract GPIO + * current value, WORKS + * ONLY FOR PORT 1 */ +#define MCS7840_DEV_DCR1_GPIO_CURRENT_6MA 0x00 /* GPIO output current + * 6mA, WORKS ONLY FOR + * PORT 1 */ +#define MCS7840_DEV_DCR1_GPIO_CURRENT_8MA 0x01 /* GPIO output current + * 8mA, defauilt, WORKS + * ONLY FOR PORT 1 */ +#define MCS7840_DEV_DCR1_GPIO_CURRENT_10MA 0x02 /* GPIO output current + * 10mA, WORKS ONLY FOR + * PORT 1 */ +#define MCS7840_DEV_DCR1_GPIO_CURRENT_12MA 0x03 /* GPIO output current + * 12mA, WORKS ONLY FOR + * PORT 1 */ +#define MCS7840_DEV_DCR1_UART_CURRENT_MASK 0x0c /* Mask to extract UART + * signals current value */ +#define MCS7840_DEV_DCR1_UART_CURRENT_6MA 0x00 /* UART output current + * 6mA */ +#define MCS7840_DEV_DCR1_UART_CURRENT_8MA 0x04 /* UART output current + * 8mA, defauilt */ +#define MCS7840_DEV_DCR1_UART_CURRENT_10MA 0x08 /* UART output current + * 10mA */ +#define MCS7840_DEV_DCR1_UART_CURRENT_12MA 0x0c /* UART output current + * 12mA */ +#define MCS7840_DEV_DCR1_WAKEUP_DISABLE 0x10 /* Disable Remote USB + * Wakeup */ +#define MCS7840_DEV_DCR1_PLLPWRDOWN_DISABLE 0x20 /* Disable PLL power + * down when not needed, + * WORKS ONLY FOR PORT 1 */ +#define MCS7840_DEV_DCR1_LONG_INTERRUPT 0x40 /* Enable 13 bytes of + * interrupt data, with + * FIFO statistics, + * WORKS ONLY FOR PORT 1 */ +#define MCS7840_DEV_DCR1_RESERVED1 0x80 /* Unused */ + +/* + * Bits of DCR2 registers, documented in datasheet + * Wakeup will work only if DCR0_IRDA = 0 (RS-xxx mode) and + * DCR1_WAKEUP_DISABLE = 0 (wakeup enabled). + */ +#define MCS7840_DEV_DCR2_WAKEUP_CTS 0x01 /* Wakeup on CTS change, + * default = 0 */ +#define MCS7840_DEV_DCR2_WAKEUP_DCD 0x02 /* Wakeup on DCD change, + * default = 0 */ +#define MCS7840_DEV_DCR2_WAKEUP_RI 0x04 /* Wakeup on RI change, + * default = 1 */ +#define MCS7840_DEV_DCR2_WAKEUP_DSR 0x08 /* Wakeup on DSR change, + * default = 0 */ +#define MCS7840_DEV_DCR2_WAKEUP_RXD 0x10 /* Wakeup on RX Data change, + * default = 0 */ +#define MCS7840_DEV_DCR2_WAKEUP_RESUME 0x20 /* Wakeup issues RESUME + * signal, DISCONNECT + * otherwise, default = 1 */ +#define MCS7840_DEV_DCR2_RESERVED1 0x40 /* Unused */ +#define MCS7840_DEV_DCR2_SHDN_POLARITY 0x80 /* 0: Pin 12 Active Low, 1: + * Pin 12 Active High, default + * = 0 */ + +/* Interrupt endpoint bytes & bits */ +#define MCS7840_IEP_FIFO_STATUS_INDEX 5 +/* + * Thesse can be calculated as "1 << portnumber" for Bulk-out and + * "1 << (portnumber+1)" for Bulk-in + */ +#define MCS7840_IEP_BO_PORT1_HASDATA 0x01 +#define MCS7840_IEP_BI_PORT1_HASDATA 0x02 +#define MCS7840_IEP_BO_PORT2_HASDATA 0x04 +#define MCS7840_IEP_BI_PORT2_HASDATA 0x08 +#define MCS7840_IEP_BO_PORT3_HASDATA 0x10 +#define MCS7840_IEP_BI_PORT3_HASDATA 0x20 +#define MCS7840_IEP_BO_PORT4_HASDATA 0x40 +#define MCS7840_IEP_BI_PORT4_HASDATA 0x80 + +/* Documented UART registers (fully compatible with 16550 UART) */ +#define MCS7840_UART_REG_THR 0x00 /* Transmitter Holding + * Register W/Only */ +#define MCS7840_UART_REG_RHR 0x00 /* Receiver Holding Register + * R/Only */ +#define MCS7840_UART_REG_IER 0x01 /* Interrupt enable register - + * R/W */ +#define MCS7840_UART_REG_FCR 0x02 /* FIFO Control register - + * W/Only */ +#define MCS7840_UART_REG_ISR 0x02 /* Interrupt Status Registter + * R/Only */ +#define MCS7840_UART_REG_LCR 0x03 /* Line control register R/W */ +#define MCS7840_UART_REG_MCR 0x04 /* Modem control register R/W */ +#define MCS7840_UART_REG_LSR 0x05 /* Line status register R/Only */ +#define MCS7840_UART_REG_MSR 0x06 /* Modem status register + * R/Only */ +#define MCS7840_UART_REG_SCRATCHPAD 0x07 /* Scratch pad register */ + +#define MCS7840_UART_REG_DLL 0x00 /* Low bits of BAUD divider */ +#define MCS7840_UART_REG_DLM 0x01 /* High bits of BAUD divider */ + +/* IER bits */ +#define MCS7840_UART_IER_RXREADY 0x01 /* RX Ready interrumpt mask */ +#define MCS7840_UART_IER_TXREADY 0x02 /* TX Ready interrumpt mask */ +#define MCS7840_UART_IER_RXSTAT 0x04 /* RX Status interrumpt mask */ +#define MCS7840_UART_IER_MODEM 0x08 /* Modem status change + * interrumpt mask */ +#define MCS7840_UART_IER_SLEEP 0x10 /* SLEEP enable */ + +/* FCR bits */ +#define MCS7840_UART_FCR_ENABLE 0x01 /* Enable FIFO */ +#define MCS7840_UART_FCR_FLUSHRHR 0x02 /* Flush RHR and FIFO */ +#define MCS7840_UART_FCR_FLUSHTHR 0x04 /* Flush THR and FIFO */ +#define MCS7840_UART_FCR_RTLMASK 0xa0 /* Mask to select RHR + * Interrupt Trigger level */ +#define MCS7840_UART_FCR_RTL_1_1 0x00 /* L1 = 1, L2 = 1 */ +#define MCS7840_UART_FCR_RTL_1_4 0x40 /* L1 = 1, L2 = 4 */ +#define MCS7840_UART_FCR_RTL_1_8 0x80 /* L1 = 1, L2 = 8 */ +#define MCS7840_UART_FCR_RTL_1_14 0xa0 /* L1 = 1, L2 = 14 */ + +/* ISR bits */ +#define MCS7840_UART_ISR_NOPENDING 0x01 /* No interrupt pending */ +#define MCS7840_UART_ISR_INTMASK 0x3f /* Mask to select interrupt + * source */ +#define MCS7840_UART_ISR_RXERR 0x06 /* Recevir error */ +#define MCS7840_UART_ISR_RXHASDATA 0x04 /* Recevier has data */ +#define MCS7840_UART_ISR_RXTIMEOUT 0x0c /* Recevier timeout */ +#define MCS7840_UART_ISR_TXEMPTY 0x02 /* Transmitter empty */ +#define MCS7840_UART_ISR_MSCHANGE 0x00 /* Modem status change */ + +/* LCR bits */ +#define MCS7840_UART_LCR_DATALENMASK 0x03 /* Mask for data length */ +#define MCS7840_UART_LCR_DATALEN5 0x00 /* 5 data bits */ +#define MCS7840_UART_LCR_DATALEN6 0x01 /* 6 data bits */ +#define MCS7840_UART_LCR_DATALEN7 0x02 /* 7 data bits */ +#define MCS7840_UART_LCR_DATALEN8 0x03 /* 8 data bits */ + +#define MCS7840_UART_LCR_STOPBMASK 0x04 /* Mask for stop bits */ +#define MCS7840_UART_LCR_STOPB1 0x00 /* 1 stop bit in any case */ +#define MCS7840_UART_LCR_STOPB2 0x04 /* 1.5-2 stop bits depends on + * data length */ + +#define MCS7840_UART_LCR_PARITYMASK 0x38 /* Mask for all parity data */ +#define MCS7840_UART_LCR_PARITYON 0x08 /* Parity ON/OFF - ON */ +#define MCS7840_UART_LCR_PARITYODD 0x00 /* Parity Odd */ +#define MCS7840_UART_LCR_PARITYEVEN 0x10 /* Parity Even */ +#define MCS7840_UART_LCR_PARITYODD 0x00 /* Parity Odd */ +#define MCS7840_UART_LCR_PARITYFORCE 0x20 /* Force parity odd/even */ + +#define MCS7840_UART_LCR_BREAK 0x40 /* Send BREAK */ +#define MCS7840_UART_LCR_DIVISORS 0x80 /* Map DLL/DLM instead of + * xHR/IER */ + +/* LSR bits */ +#define MCS7840_UART_LSR_RHRAVAIL 0x01 /* Data available for read */ +#define MCS7840_UART_LSR_RHROVERRUN 0x02 /* Data FIFO/register overflow */ +#define MCS7840_UART_LSR_PARITYERR 0x04 /* Parity error */ +#define MCS7840_UART_LSR_FRAMEERR 0x10 /* Framing error */ +#define MCS7840_UART_LSR_BREAKERR 0x20 /* BREAK sigmal received */ +#define MCS7840_UART_LSR_THREMPTY 0x40 /* THR register is empty, + * ready for transmit */ +#define MCS7840_UART_LSR_HASERR 0x80 /* Has error in receiver FIFO */ + +/* MCR bits */ +#define MCS7840_UART_MCR_DTR 0x01 /* Force DTR to be active + * (low) */ +#define MCS7840_UART_MCR_RTS 0x02 /* Force RTS to be active + * (low) */ +#define MCS7840_UART_MCR_IE 0x04 /* Enable interrupts (from + * code, not documented) */ +#define MCS7840_UART_MCR_LOOPBACK 0x10 /* Enable local loopback test + * mode */ +#define MCS7840_UART_MCR_CTSRTS 0x20 /* Enable CTS/RTS flow control + * in 550 (FIFO) mode */ +#define MCS7840_UART_MCR_DTRDSR 0x40 /* Enable DTR/DSR flow control + * in 550 (FIFO) mode */ +#define MCS7840_UART_MCR_DCD 0x80 /* Enable DCD flow control in + * 550 (FIFO) mode */ + +/* MSR bits */ +#define MCS7840_UART_MSR_DELTACTS 0x01 /* CTS was changed since last + * read */ +#define MCS7840_UART_MSR_DELTADSR 0x02 /* DSR was changed since last + * read */ +#define MCS7840_UART_MSR_DELTARI 0x04 /* RI was changed from low to + * high since last read */ +#define MCS7840_UART_MSR_DELTADCD 0x08 /* DCD was changed since last + * read */ +#define MCS7840_UART_MSR_NEGCTS 0x10 /* Negated CTS signal */ +#define MCS7840_UART_MSR_NEGDSR 0x20 /* Negated DSR signal */ +#define MCS7840_UART_MSR_NEGRI 0x40 /* Negated RI signal */ +#define MCS7840_UART_MSR_NEGDCD 0x80 /* Negated DCD signal */ + +/* SCRATCHPAD bits */ +#define MCS7840_UART_SCRATCHPAD_RS232 0x00 /* RS-485 disabled */ +#define MCS7840_UART_SCRATCHPAD_RS485_DTRRX 0x80 /* RS-485 mode, DTR High + * = RX */ +#define MCS7840_UART_SCRATCHPAD_RS485_DTRTX 0xc0 /* RS-485 mode, DTR High + * = TX */ + +#define MCS7840_CONFIG_INDEX 0 +#define MCS7840_IFACE_INDEX 0 + +#endif diff --git a/sys/bus/u4b/serial/umct.c b/sys/bus/u4b/serial/umct.c new file mode 100644 index 0000000000..16dd4a1aba --- /dev/null +++ b/sys/bus/u4b/serial/umct.c @@ -0,0 +1,639 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2003 Scott Long + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/* + * Driver for the MCT (Magic Control Technology) USB-RS232 Converter. + * Based on the superb documentation from the linux mct_u232 driver by + * Wolfgang Grandeggar . + * This device smells a lot like the Belkin F5U103, except that it has + * suffered some mild brain-damage. This driver is based off of the ubsa.c + * driver from Alexander Kabaev . Merging the two together + * might be useful, though the subtle differences might lead to lots of + * #ifdef's. + */ + +/* + * NOTE: all function names beginning like "umct_cfg_" can only + * be called from within the config thread function ! + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR usb_debug +#include +#include + +#include + +/* The UMCT advertises the standard 8250 UART registers */ +#define UMCT_GET_MSR 2 /* Get Modem Status Register */ +#define UMCT_GET_MSR_SIZE 1 +#define UMCT_GET_LCR 6 /* Get Line Control Register */ +#define UMCT_GET_LCR_SIZE 1 +#define UMCT_SET_BAUD 5 /* Set the Baud Rate Divisor */ +#define UMCT_SET_BAUD_SIZE 4 +#define UMCT_SET_LCR 7 /* Set Line Control Register */ +#define UMCT_SET_LCR_SIZE 1 +#define UMCT_SET_MCR 10 /* Set Modem Control Register */ +#define UMCT_SET_MCR_SIZE 1 + +#define UMCT_INTR_INTERVAL 100 +#define UMCT_IFACE_INDEX 0 +#define UMCT_CONFIG_INDEX 0 + +enum { + UMCT_BULK_DT_WR, + UMCT_BULK_DT_RD, + UMCT_INTR_DT_RD, + UMCT_N_TRANSFER, +}; + +struct umct_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_device *sc_udev; + struct usb_xfer *sc_xfer[UMCT_N_TRANSFER]; + struct mtx sc_mtx; + + uint32_t sc_unit; + + uint16_t sc_obufsize; + + uint8_t sc_lsr; + uint8_t sc_msr; + uint8_t sc_lcr; + uint8_t sc_mcr; + uint8_t sc_iface_no; + uint8_t sc_swap_cb; + uint8_t sc_name[16]; +}; + +/* prototypes */ + +static device_probe_t umct_probe; +static device_attach_t umct_attach; +static device_detach_t umct_detach; + +static usb_callback_t umct_intr_callback; +static usb_callback_t umct_intr_callback_sub; +static usb_callback_t umct_read_callback; +static usb_callback_t umct_read_callback_sub; +static usb_callback_t umct_write_callback; + +static void umct_cfg_do_request(struct umct_softc *sc, uint8_t request, + uint16_t len, uint32_t value); +static void umct_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void umct_cfg_set_break(struct ucom_softc *, uint8_t); +static void umct_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void umct_cfg_set_rts(struct ucom_softc *, uint8_t); +static uint8_t umct_calc_baud(uint32_t); +static int umct_pre_param(struct ucom_softc *, struct termios *); +static void umct_cfg_param(struct ucom_softc *, struct termios *); +static void umct_start_read(struct ucom_softc *); +static void umct_stop_read(struct ucom_softc *); +static void umct_start_write(struct ucom_softc *); +static void umct_stop_write(struct ucom_softc *); +static void umct_poll(struct ucom_softc *ucom); + +static const struct usb_config umct_config[UMCT_N_TRANSFER] = { + + [UMCT_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = 0, /* use wMaxPacketSize */ + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &umct_write_callback, + }, + + [UMCT_BULK_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umct_read_callback, + .ep_index = 0, /* first interrupt endpoint */ + }, + + [UMCT_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umct_intr_callback, + .ep_index = 1, /* second interrupt endpoint */ + }, +}; + +static const struct ucom_callback umct_callback = { + .ucom_cfg_get_status = &umct_cfg_get_status, + .ucom_cfg_set_dtr = &umct_cfg_set_dtr, + .ucom_cfg_set_rts = &umct_cfg_set_rts, + .ucom_cfg_set_break = &umct_cfg_set_break, + .ucom_cfg_param = &umct_cfg_param, + .ucom_pre_param = &umct_pre_param, + .ucom_start_read = &umct_start_read, + .ucom_stop_read = &umct_stop_read, + .ucom_start_write = &umct_start_write, + .ucom_stop_write = &umct_stop_write, + .ucom_poll = &umct_poll, +}; + +static const STRUCT_USB_HOST_ID umct_devs[] = { + {USB_VPI(USB_VENDOR_MCT, USB_PRODUCT_MCT_USB232, 0)}, + {USB_VPI(USB_VENDOR_MCT, USB_PRODUCT_MCT_SITECOM_USB232, 0)}, + {USB_VPI(USB_VENDOR_MCT, USB_PRODUCT_MCT_DU_H3SP_USB232, 0)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U109, 0)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U409, 0)}, +}; + +static device_method_t umct_methods[] = { + DEVMETHOD(device_probe, umct_probe), + DEVMETHOD(device_attach, umct_attach), + DEVMETHOD(device_detach, umct_detach), + {0, 0} +}; + +static devclass_t umct_devclass; + +static driver_t umct_driver = { + .name = "umct", + .methods = umct_methods, + .size = sizeof(struct umct_softc), +}; + +DRIVER_MODULE(umct, uhub, umct_driver, umct_devclass, NULL, 0); +MODULE_DEPEND(umct, ucom, 1, 1, 1); +MODULE_DEPEND(umct, usb, 1, 1, 1); +MODULE_VERSION(umct, 1); + +static int +umct_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UMCT_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UMCT_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(umct_devs, sizeof(umct_devs), uaa)); +} + +static int +umct_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct umct_softc *sc = device_get_softc(dev); + int32_t error; + uint16_t maxp; + uint8_t iface_index; + + sc->sc_udev = uaa->device; + sc->sc_unit = device_get_unit(dev); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "umct", NULL, MTX_DEF); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + sc->sc_iface_no = uaa->info.bIfaceNum; + + iface_index = UMCT_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, umct_config, UMCT_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating USB " + "transfers failed\n"); + goto detach; + } + + /* + * The real bulk-in endpoint is also marked as an interrupt. + * The only way to differentiate it from the real interrupt + * endpoint is to look at the wMaxPacketSize field. + */ + maxp = usbd_xfer_max_framelen(sc->sc_xfer[UMCT_BULK_DT_RD]); + if (maxp == 0x2) { + + /* guessed wrong - switch around endpoints */ + + struct usb_xfer *temp = sc->sc_xfer[UMCT_INTR_DT_RD]; + + sc->sc_xfer[UMCT_INTR_DT_RD] = sc->sc_xfer[UMCT_BULK_DT_RD]; + sc->sc_xfer[UMCT_BULK_DT_RD] = temp; + sc->sc_swap_cb = 1; + } + + sc->sc_obufsize = usbd_xfer_max_len(sc->sc_xfer[UMCT_BULK_DT_WR]); + + if (uaa->info.idProduct == USB_PRODUCT_MCT_SITECOM_USB232) { + if (sc->sc_obufsize > 16) { + sc->sc_obufsize = 16; + } + } + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &umct_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); /* success */ + +detach: + umct_detach(dev); + return (ENXIO); /* failure */ +} + +static int +umct_detach(device_t dev) +{ + struct umct_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UMCT_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +umct_cfg_do_request(struct umct_softc *sc, uint8_t request, + uint16_t len, uint32_t value) +{ + struct usb_device_request req; + usb_error_t err; + uint8_t temp[4]; + + if (len > 4) + len = 4; + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = request; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, len); + USETDW(temp, value); + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, temp, 0, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usbd_errstr(err)); + } + return; +} + +static void +umct_intr_callback_sub(struct usb_xfer *xfer, usb_error_t error) +{ + struct umct_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[2]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen < 2) { + DPRINTF("too short message\n"); + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, sizeof(buf)); + + sc->sc_msr = buf[0]; + sc->sc_lsr = buf[1]; + + ucom_status_change(&sc->sc_ucom); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umct_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct umct_softc *sc = ucom->sc_parent; + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +umct_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umct_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_lcr |= 0x40; + else + sc->sc_lcr &= ~0x40; + + umct_cfg_do_request(sc, UMCT_SET_LCR, UMCT_SET_LCR_SIZE, sc->sc_lcr); +} + +static void +umct_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umct_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_mcr |= 0x01; + else + sc->sc_mcr &= ~0x01; + + umct_cfg_do_request(sc, UMCT_SET_MCR, UMCT_SET_MCR_SIZE, sc->sc_mcr); +} + +static void +umct_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umct_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_mcr |= 0x02; + else + sc->sc_mcr &= ~0x02; + + umct_cfg_do_request(sc, UMCT_SET_MCR, UMCT_SET_MCR_SIZE, sc->sc_mcr); +} + +static uint8_t +umct_calc_baud(uint32_t baud) +{ + switch (baud) { + case B300:return (0x1); + case B600: + return (0x2); + case B1200: + return (0x3); + case B2400: + return (0x4); + case B4800: + return (0x6); + case B9600: + return (0x8); + case B19200: + return (0x9); + case B38400: + return (0xa); + case B57600: + return (0xb); + case 115200: + return (0xc); + case B0: + default: + break; + } + return (0x0); +} + +static int +umct_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + return (0); /* we accept anything */ +} + +static void +umct_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct umct_softc *sc = ucom->sc_parent; + uint32_t value; + + value = umct_calc_baud(t->c_ospeed); + umct_cfg_do_request(sc, UMCT_SET_BAUD, UMCT_SET_BAUD_SIZE, value); + + value = (sc->sc_lcr & 0x40); + + switch (t->c_cflag & CSIZE) { + case CS5: + value |= 0x0; + break; + case CS6: + value |= 0x1; + break; + case CS7: + value |= 0x2; + break; + default: + case CS8: + value |= 0x3; + break; + } + + value |= (t->c_cflag & CSTOPB) ? 0x4 : 0; + if (t->c_cflag & PARENB) { + value |= 0x8; + value |= (t->c_cflag & PARODD) ? 0x0 : 0x10; + } + /* + * XXX There doesn't seem to be a way to tell the device + * to use flow control. + */ + + sc->sc_lcr = value; + umct_cfg_do_request(sc, UMCT_SET_LCR, UMCT_SET_LCR_SIZE, value); +} + +static void +umct_start_read(struct ucom_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint */ + usbd_transfer_start(sc->sc_xfer[UMCT_INTR_DT_RD]); + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UMCT_BULK_DT_RD]); +} + +static void +umct_stop_read(struct ucom_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint */ + usbd_transfer_stop(sc->sc_xfer[UMCT_INTR_DT_RD]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UMCT_BULK_DT_RD]); +} + +static void +umct_start_write(struct ucom_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UMCT_BULK_DT_WR]); +} + +static void +umct_stop_write(struct ucom_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UMCT_BULK_DT_WR]); +} + +static void +umct_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umct_softc *sc = usbd_xfer_softc(xfer); + + if (sc->sc_swap_cb) + umct_intr_callback_sub(xfer, error); + else + umct_read_callback_sub(xfer, error); +} + +static void +umct_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umct_softc *sc = usbd_xfer_softc(xfer); + + if (sc->sc_swap_cb) + umct_read_callback_sub(xfer, error); + else + umct_intr_callback_sub(xfer, error); +} + +static void +umct_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umct_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + sc->sc_obufsize, &actlen)) { + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umct_read_callback_sub(struct usb_xfer *xfer, usb_error_t error) +{ + struct umct_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umct_poll(struct ucom_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UMCT_N_TRANSFER); +} diff --git a/sys/bus/u4b/serial/umodem.c b/sys/bus/u4b/serial/umodem.c new file mode 100644 index 0000000000..9289eec32f --- /dev/null +++ b/sys/bus/u4b/serial/umodem.c @@ -0,0 +1,888 @@ +/* $NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $ */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2003, M. Warner Losh . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf + * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf + * http://www.usb.org/developers/devclass_docs/cdc_wmc10.zip + */ + +/* + * TODO: + * - Add error recovery in various places; the big problem is what + * to do in a callback if there is an error. + * - Implement a Call Device for modems without multiplexed commands. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "usbdevs.h" + +#include + +#define USB_DEBUG_VAR umodem_debug +#include +#include +#include + +#include + +#ifdef USB_DEBUG +static int umodem_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, umodem, CTLFLAG_RW, 0, "USB umodem"); +SYSCTL_INT(_hw_usb_umodem, OID_AUTO, debug, CTLFLAG_RW, + &umodem_debug, 0, "Debug level"); +#endif + +static const STRUCT_USB_HOST_ID umodem_devs[] = { + /* Generic Modem class match */ + {USB_IFACE_CLASS(UICLASS_CDC), + USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), + USB_IFACE_PROTOCOL(UIPROTO_CDC_AT)}, + /* Huawei Modem class match */ + {USB_IFACE_CLASS(UICLASS_CDC), + USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), + USB_IFACE_PROTOCOL(0xFF)}, + /* Kyocera AH-K3001V */ + {USB_VPI(USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_AHK3001V, 1)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720, 1)}, + {USB_VPI(USB_VENDOR_CURITEL, USB_PRODUCT_CURITEL_PC5740, 1)}, +}; + +/* + * As speeds for umodem deivces increase, these numbers will need to + * be increased. They should be good for G3 speeds and below. + * + * TODO: The TTY buffers should be increased! + */ +#define UMODEM_BUF_SIZE 1024 + +enum { + UMODEM_BULK_WR, + UMODEM_BULK_RD, + UMODEM_INTR_RD, + UMODEM_N_TRANSFER, +}; + +#define UMODEM_MODVER 1 /* module version */ + +struct umodem_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UMODEM_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint16_t sc_line; + + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* modem status register */ + uint8_t sc_ctrl_iface_no; + uint8_t sc_data_iface_no; + uint8_t sc_iface_index[2]; + uint8_t sc_cm_over_data; + uint8_t sc_cm_cap; /* CM capabilities */ + uint8_t sc_acm_cap; /* ACM capabilities */ +}; + +static device_probe_t umodem_probe; +static device_attach_t umodem_attach; +static device_detach_t umodem_detach; + +static usb_callback_t umodem_intr_callback; +static usb_callback_t umodem_write_callback; +static usb_callback_t umodem_read_callback; + +static void umodem_start_read(struct ucom_softc *); +static void umodem_stop_read(struct ucom_softc *); +static void umodem_start_write(struct ucom_softc *); +static void umodem_stop_write(struct ucom_softc *); +static void umodem_get_caps(struct usb_attach_arg *, uint8_t *, uint8_t *); +static void umodem_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static int umodem_pre_param(struct ucom_softc *, struct termios *); +static void umodem_cfg_param(struct ucom_softc *, struct termios *); +static int umodem_ioctl(struct ucom_softc *, uint32_t, caddr_t, int, + struct thread *); +static void umodem_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void umodem_cfg_set_rts(struct ucom_softc *, uint8_t); +static void umodem_cfg_set_break(struct ucom_softc *, uint8_t); +static void *umodem_get_desc(struct usb_attach_arg *, uint8_t, uint8_t); +static usb_error_t umodem_set_comm_feature(struct usb_device *, uint8_t, + uint16_t, uint16_t); +static void umodem_poll(struct ucom_softc *ucom); +static void umodem_find_data_iface(struct usb_attach_arg *uaa, + uint8_t, uint8_t *, uint8_t *); + +static const struct usb_config umodem_config[UMODEM_N_TRANSFER] = { + + [UMODEM_BULK_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .if_index = 0, + .bufsize = UMODEM_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &umodem_write_callback, + }, + + [UMODEM_BULK_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .if_index = 0, + .bufsize = UMODEM_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &umodem_read_callback, + }, + + [UMODEM_INTR_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .if_index = 1, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umodem_intr_callback, + }, +}; + +static const struct ucom_callback umodem_callback = { + .ucom_cfg_get_status = &umodem_cfg_get_status, + .ucom_cfg_set_dtr = &umodem_cfg_set_dtr, + .ucom_cfg_set_rts = &umodem_cfg_set_rts, + .ucom_cfg_set_break = &umodem_cfg_set_break, + .ucom_cfg_param = &umodem_cfg_param, + .ucom_pre_param = &umodem_pre_param, + .ucom_ioctl = &umodem_ioctl, + .ucom_start_read = &umodem_start_read, + .ucom_stop_read = &umodem_stop_read, + .ucom_start_write = &umodem_start_write, + .ucom_stop_write = &umodem_stop_write, + .ucom_poll = &umodem_poll, +}; + +static device_method_t umodem_methods[] = { + DEVMETHOD(device_probe, umodem_probe), + DEVMETHOD(device_attach, umodem_attach), + DEVMETHOD(device_detach, umodem_detach), + {0, 0} +}; + +static devclass_t umodem_devclass; + +static driver_t umodem_driver = { + .name = "umodem", + .methods = umodem_methods, + .size = sizeof(struct umodem_softc), +}; + +DRIVER_MODULE(umodem, uhub, umodem_driver, umodem_devclass, NULL, 0); +MODULE_DEPEND(umodem, ucom, 1, 1, 1); +MODULE_DEPEND(umodem, usb, 1, 1, 1); +MODULE_VERSION(umodem, UMODEM_MODVER); + +static int +umodem_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + int error; + + DPRINTFN(11, "\n"); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + + error = usbd_lookup_id_by_uaa(umodem_devs, sizeof(umodem_devs), uaa); + if (error) + return (error); + + return (BUS_PROBE_GENERIC); +} + +static int +umodem_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct umodem_softc *sc = device_get_softc(dev); + struct usb_cdc_cm_descriptor *cmd; + struct usb_cdc_union_descriptor *cud; + uint8_t i; + int error; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "umodem", NULL, MTX_DEF); + + sc->sc_ctrl_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index[1] = uaa->info.bIfaceIndex; + sc->sc_udev = uaa->device; + + umodem_get_caps(uaa, &sc->sc_cm_cap, &sc->sc_acm_cap); + + /* get the data interface number */ + + cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); + + if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) { + + cud = usbd_find_descriptor(uaa->device, NULL, + uaa->info.bIfaceIndex, UDESC_CS_INTERFACE, + 0 - 1, UDESCSUB_CDC_UNION, 0 - 1); + + if ((cud == NULL) || (cud->bLength < sizeof(*cud))) { + DPRINTF("Missing descriptor. " + "Assuming data interface is next.\n"); + if (sc->sc_ctrl_iface_no == 0xFF) { + goto detach; + } else { + uint8_t class_match = 0; + + /* set default interface number */ + sc->sc_data_iface_no = 0xFF; + + /* try to find the data interface backwards */ + umodem_find_data_iface(uaa, + uaa->info.bIfaceIndex - 1, + &sc->sc_data_iface_no, &class_match); + + /* try to find the data interface forwards */ + umodem_find_data_iface(uaa, + uaa->info.bIfaceIndex + 1, + &sc->sc_data_iface_no, &class_match); + + /* check if nothing was found */ + if (sc->sc_data_iface_no == 0xFF) + goto detach; + } + } else { + sc->sc_data_iface_no = cud->bSlaveInterface[0]; + } + } else { + sc->sc_data_iface_no = cmd->bDataInterface; + } + + device_printf(dev, "data interface %d, has %sCM over " + "data, has %sbreak\n", + sc->sc_data_iface_no, + sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ", + sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no "); + + /* get the data interface too */ + + for (i = 0;; i++) { + struct usb_interface *iface; + struct usb_interface_descriptor *id; + + iface = usbd_get_iface(uaa->device, i); + + if (iface) { + + id = usbd_get_interface_descriptor(iface); + + if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) { + sc->sc_iface_index[0] = i; + usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); + break; + } + } else { + device_printf(dev, "no data interface\n"); + goto detach; + } + } + + if (usb_test_quirk(uaa, UQ_ASSUME_CM_OVER_DATA)) { + sc->sc_cm_over_data = 1; + } else { + if (sc->sc_cm_cap & USB_CDC_CM_OVER_DATA) { + if (sc->sc_acm_cap & USB_CDC_ACM_HAS_FEATURE) { + + error = umodem_set_comm_feature + (uaa->device, sc->sc_ctrl_iface_no, + UCDC_ABSTRACT_STATE, UCDC_DATA_MULTIPLEXED); + + /* ignore any errors */ + } + sc->sc_cm_over_data = 1; + } + } + error = usbd_transfer_setup(uaa->device, + sc->sc_iface_index, sc->sc_xfer, + umodem_config, UMODEM_N_TRANSFER, + sc, &sc->sc_mtx); + if (error) { + goto detach; + } + + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &umodem_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + umodem_detach(dev); + return (ENXIO); +} + +static void +umodem_find_data_iface(struct usb_attach_arg *uaa, + uint8_t iface_index, uint8_t *p_data_no, uint8_t *p_match_class) +{ + struct usb_interface_descriptor *id; + struct usb_interface *iface; + + iface = usbd_get_iface(uaa->device, iface_index); + + /* check for end of interfaces */ + if (iface == NULL) + return; + + id = usbd_get_interface_descriptor(iface); + + /* check for non-matching interface class */ + if (id->bInterfaceClass != UICLASS_CDC_DATA || + id->bInterfaceSubClass != UISUBCLASS_DATA) { + /* if we got a class match then return */ + if (*p_match_class) + return; + } else { + *p_match_class = 1; + } + + DPRINTFN(11, "Match at index %u\n", iface_index); + + *p_data_no = id->bInterfaceNumber; +} + +static void +umodem_start_read(struct ucom_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint, if any */ + usbd_transfer_start(sc->sc_xfer[UMODEM_INTR_RD]); + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_RD]); +} + +static void +umodem_stop_read(struct ucom_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint, if any */ + usbd_transfer_stop(sc->sc_xfer[UMODEM_INTR_RD]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_RD]); +} + +static void +umodem_start_write(struct ucom_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_WR]); +} + +static void +umodem_stop_write(struct ucom_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_WR]); +} + +static void +umodem_get_caps(struct usb_attach_arg *uaa, uint8_t *cm, uint8_t *acm) +{ + struct usb_cdc_cm_descriptor *cmd; + struct usb_cdc_acm_descriptor *cad; + + cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); + if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) { + DPRINTF("no CM desc (faking one)\n"); + *cm = USB_CDC_CM_DOES_CM | USB_CDC_CM_OVER_DATA; + } else + *cm = cmd->bmCapabilities; + + cad = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM); + if ((cad == NULL) || (cad->bLength < sizeof(*cad))) { + DPRINTF("no ACM desc\n"); + *acm = 0; + } else + *acm = cad->bmCapabilities; +} + +static void +umodem_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct umodem_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static int +umodem_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + return (0); /* we accept anything */ +} + +static void +umodem_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct umodem_softc *sc = ucom->sc_parent; + struct usb_cdc_line_state ls; + struct usb_device_request req; + + DPRINTF("sc=%p\n", sc); + + memset(&ls, 0, sizeof(ls)); + + USETDW(ls.dwDTERate, t->c_ospeed); + + ls.bCharFormat = (t->c_cflag & CSTOPB) ? + UCDC_STOP_BIT_2 : UCDC_STOP_BIT_1; + + ls.bParityType = (t->c_cflag & PARENB) ? + ((t->c_cflag & PARODD) ? + UCDC_PARITY_ODD : UCDC_PARITY_EVEN) : UCDC_PARITY_NONE; + + switch (t->c_cflag & CSIZE) { + case CS5: + ls.bDataBits = 5; + break; + case CS6: + ls.bDataBits = 6; + break; + case CS7: + ls.bDataBits = 7; + break; + case CS8: + ls.bDataBits = 8; + break; + } + + DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n", + UGETDW(ls.dwDTERate), ls.bCharFormat, + ls.bParityType, ls.bDataBits); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_LINE_CODING; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, sizeof(ls)); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, &ls, 0, 1000); +} + +static int +umodem_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data, + int flag, struct thread *td) +{ + struct umodem_softc *sc = ucom->sc_parent; + int error = 0; + + DPRINTF("cmd=0x%08x\n", cmd); + + switch (cmd) { + case USB_GET_CM_OVER_DATA: + *(int *)data = sc->sc_cm_over_data; + break; + + case USB_SET_CM_OVER_DATA: + if (*(int *)data != sc->sc_cm_over_data) { + /* XXX change it */ + } + break; + + default: + DPRINTF("unknown\n"); + error = ENOIOCTL; + break; + } + + return (error); +} + +static void +umodem_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umodem_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff=%d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +umodem_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umodem_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff=%d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +umodem_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umodem_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t temp; + + DPRINTF("onoff=%d\n", onoff); + + if (sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK) { + + temp = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, temp); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + } +} + +static void +umodem_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_cdc_notification pkt; + struct umodem_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint16_t wLen; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen < 8) { + DPRINTF("received short packet, " + "%d bytes\n", actlen); + goto tr_setup; + } + if (actlen > sizeof(pkt)) { + DPRINTF("truncating message\n"); + actlen = sizeof(pkt); + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &pkt, actlen); + + actlen -= 8; + + wLen = UGETW(pkt.wLength); + if (actlen > wLen) { + actlen = wLen; + } + if (pkt.bmRequestType != UCDC_NOTIFICATION) { + DPRINTF("unknown message type, " + "0x%02x, on notify pipe!\n", + pkt.bmRequestType); + goto tr_setup; + } + switch (pkt.bNotification) { + case UCDC_N_SERIAL_STATE: + /* + * Set the serial state in ucom driver based on + * the bits from the notify message + */ + if (actlen < 2) { + DPRINTF("invalid notification " + "length, %d bytes!\n", actlen); + break; + } + DPRINTF("notify bytes = %02x%02x\n", + pkt.data[0], + pkt.data[1]); + + /* Currently, lsr is always zero. */ + sc->sc_lsr = 0; + sc->sc_msr = 0; + + if (pkt.data[0] & UCDC_N_SERIAL_RI) { + sc->sc_msr |= SER_RI; + } + if (pkt.data[0] & UCDC_N_SERIAL_DSR) { + sc->sc_msr |= SER_DSR; + } + if (pkt.data[0] & UCDC_N_SERIAL_DCD) { + sc->sc_msr |= SER_DCD; + } + ucom_status_change(&sc->sc_ucom); + break; + + default: + DPRINTF("unknown notify message: 0x%02x\n", + pkt.bNotification); + break; + } + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +umodem_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umodem_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UMODEM_BUF_SIZE, &actlen)) { + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umodem_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umodem_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("actlen=%d\n", actlen); + + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void * +umodem_get_desc(struct usb_attach_arg *uaa, uint8_t type, uint8_t subtype) +{ + return (usbd_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex, + type, 0 - 1, subtype, 0 - 1)); +} + +static usb_error_t +umodem_set_comm_feature(struct usb_device *udev, uint8_t iface_no, + uint16_t feature, uint16_t state) +{ + struct usb_device_request req; + struct usb_cdc_abstract_state ast; + + DPRINTF("feature=%d state=%d\n", + feature, state); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_COMM_FEATURE; + USETW(req.wValue, feature); + req.wIndex[0] = iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, UCDC_ABSTRACT_STATE_LENGTH); + USETW(ast.wState, state); + + return (usbd_do_request(udev, NULL, &req, &ast)); +} + +static int +umodem_detach(device_t dev) +{ + struct umodem_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UMODEM_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +umodem_poll(struct ucom_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UMODEM_N_TRANSFER); +} diff --git a/sys/bus/u4b/serial/umoscom.c b/sys/bus/u4b/serial/umoscom.c new file mode 100644 index 0000000000..eef2f4794e --- /dev/null +++ b/sys/bus/u4b/serial/umoscom.c @@ -0,0 +1,707 @@ +/* $FreeBSD$ */ +/* $OpenBSD: umoscom.c,v 1.2 2006/10/26 06:02:43 jsg Exp $ */ + +/* + * Copyright (c) 2006 Jonathan Gray + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR umoscom_debug +#include +#include + +#include + +#ifdef USB_DEBUG +static int umoscom_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, umoscom, CTLFLAG_RW, 0, "USB umoscom"); +SYSCTL_INT(_hw_usb_umoscom, OID_AUTO, debug, CTLFLAG_RW, + &umoscom_debug, 0, "Debug level"); +#endif + +#define UMOSCOM_BUFSIZE 1024 /* bytes */ + +#define UMOSCOM_CONFIG_INDEX 0 +#define UMOSCOM_IFACE_INDEX 0 + +/* interrupt packet */ +#define UMOSCOM_IIR_RLS 0x06 +#define UMOSCOM_IIR_RDA 0x04 +#define UMOSCOM_IIR_CTI 0x0c +#define UMOSCOM_IIR_THR 0x02 +#define UMOSCOM_IIR_MS 0x00 + +/* registers */ +#define UMOSCOM_READ 0x0d +#define UMOSCOM_WRITE 0x0e +#define UMOSCOM_UART_REG 0x0300 +#define UMOSCOM_VEND_REG 0x0000 + +#define UMOSCOM_TXBUF 0x00 /* Write */ +#define UMOSCOM_RXBUF 0x00 /* Read */ +#define UMOSCOM_INT 0x01 +#define UMOSCOM_FIFO 0x02 /* Write */ +#define UMOSCOM_ISR 0x02 /* Read */ +#define UMOSCOM_LCR 0x03 +#define UMOSCOM_MCR 0x04 +#define UMOSCOM_LSR 0x05 +#define UMOSCOM_MSR 0x06 +#define UMOSCOM_SCRATCH 0x07 +#define UMOSCOM_DIV_LO 0x08 +#define UMOSCOM_DIV_HI 0x09 +#define UMOSCOM_EFR 0x0a +#define UMOSCOM_XON1 0x0b +#define UMOSCOM_XON2 0x0c +#define UMOSCOM_XOFF1 0x0d +#define UMOSCOM_XOFF2 0x0e + +#define UMOSCOM_BAUDLO 0x00 +#define UMOSCOM_BAUDHI 0x01 + +#define UMOSCOM_INT_RXEN 0x01 +#define UMOSCOM_INT_TXEN 0x02 +#define UMOSCOM_INT_RSEN 0x04 +#define UMOSCOM_INT_MDMEM 0x08 +#define UMOSCOM_INT_SLEEP 0x10 +#define UMOSCOM_INT_XOFF 0x20 +#define UMOSCOM_INT_RTS 0x40 + +#define UMOSCOM_FIFO_EN 0x01 +#define UMOSCOM_FIFO_RXCLR 0x02 +#define UMOSCOM_FIFO_TXCLR 0x04 +#define UMOSCOM_FIFO_DMA_BLK 0x08 +#define UMOSCOM_FIFO_TXLVL_MASK 0x30 +#define UMOSCOM_FIFO_TXLVL_8 0x00 +#define UMOSCOM_FIFO_TXLVL_16 0x10 +#define UMOSCOM_FIFO_TXLVL_32 0x20 +#define UMOSCOM_FIFO_TXLVL_56 0x30 +#define UMOSCOM_FIFO_RXLVL_MASK 0xc0 +#define UMOSCOM_FIFO_RXLVL_8 0x00 +#define UMOSCOM_FIFO_RXLVL_16 0x40 +#define UMOSCOM_FIFO_RXLVL_56 0x80 +#define UMOSCOM_FIFO_RXLVL_80 0xc0 + +#define UMOSCOM_ISR_MDM 0x00 +#define UMOSCOM_ISR_NONE 0x01 +#define UMOSCOM_ISR_TX 0x02 +#define UMOSCOM_ISR_RX 0x04 +#define UMOSCOM_ISR_LINE 0x06 +#define UMOSCOM_ISR_RXTIMEOUT 0x0c +#define UMOSCOM_ISR_RX_XOFF 0x10 +#define UMOSCOM_ISR_RTSCTS 0x20 +#define UMOSCOM_ISR_FIFOEN 0xc0 + +#define UMOSCOM_LCR_DBITS(x) ((x) - 5) +#define UMOSCOM_LCR_STOP_BITS_1 0x00 +#define UMOSCOM_LCR_STOP_BITS_2 0x04 /* 2 if 6-8 bits/char or 1.5 if 5 */ +#define UMOSCOM_LCR_PARITY_NONE 0x00 +#define UMOSCOM_LCR_PARITY_ODD 0x08 +#define UMOSCOM_LCR_PARITY_EVEN 0x18 +#define UMOSCOM_LCR_BREAK 0x40 +#define UMOSCOM_LCR_DIVLATCH_EN 0x80 + +#define UMOSCOM_MCR_DTR 0x01 +#define UMOSCOM_MCR_RTS 0x02 +#define UMOSCOM_MCR_LOOP 0x04 +#define UMOSCOM_MCR_INTEN 0x08 +#define UMOSCOM_MCR_LOOPBACK 0x10 +#define UMOSCOM_MCR_XONANY 0x20 +#define UMOSCOM_MCR_IRDA_EN 0x40 +#define UMOSCOM_MCR_BAUD_DIV4 0x80 + +#define UMOSCOM_LSR_RXDATA 0x01 +#define UMOSCOM_LSR_RXOVER 0x02 +#define UMOSCOM_LSR_RXPAR_ERR 0x04 +#define UMOSCOM_LSR_RXFRM_ERR 0x08 +#define UMOSCOM_LSR_RXBREAK 0x10 +#define UMOSCOM_LSR_TXEMPTY 0x20 +#define UMOSCOM_LSR_TXALLEMPTY 0x40 +#define UMOSCOM_LSR_TXFIFO_ERR 0x80 + +#define UMOSCOM_MSR_CTS_CHG 0x01 +#define UMOSCOM_MSR_DSR_CHG 0x02 +#define UMOSCOM_MSR_RI_CHG 0x04 +#define UMOSCOM_MSR_CD_CHG 0x08 +#define UMOSCOM_MSR_CTS 0x10 +#define UMOSCOM_MSR_RTS 0x20 +#define UMOSCOM_MSR_RI 0x40 +#define UMOSCOM_MSR_CD 0x80 + +#define UMOSCOM_BAUD_REF 115200 + +enum { + UMOSCOM_BULK_DT_WR, + UMOSCOM_BULK_DT_RD, + UMOSCOM_INTR_DT_RD, + UMOSCOM_N_TRANSFER, +}; + +struct umoscom_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UMOSCOM_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint8_t sc_mcr; + uint8_t sc_lcr; +}; + +/* prototypes */ + +static device_probe_t umoscom_probe; +static device_attach_t umoscom_attach; +static device_detach_t umoscom_detach; + +static usb_callback_t umoscom_write_callback; +static usb_callback_t umoscom_read_callback; +static usb_callback_t umoscom_intr_callback; + +static void umoscom_cfg_open(struct ucom_softc *); +static void umoscom_cfg_close(struct ucom_softc *); +static void umoscom_cfg_set_break(struct ucom_softc *, uint8_t); +static void umoscom_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void umoscom_cfg_set_rts(struct ucom_softc *, uint8_t); +static int umoscom_pre_param(struct ucom_softc *, struct termios *); +static void umoscom_cfg_param(struct ucom_softc *, struct termios *); +static void umoscom_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void umoscom_cfg_write(struct umoscom_softc *, uint16_t, uint16_t); +static uint8_t umoscom_cfg_read(struct umoscom_softc *, uint16_t); +static void umoscom_start_read(struct ucom_softc *); +static void umoscom_stop_read(struct ucom_softc *); +static void umoscom_start_write(struct ucom_softc *); +static void umoscom_stop_write(struct ucom_softc *); +static void umoscom_poll(struct ucom_softc *ucom); + +static const struct usb_config umoscom_config_data[UMOSCOM_N_TRANSFER] = { + + [UMOSCOM_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UMOSCOM_BUFSIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &umoscom_write_callback, + }, + + [UMOSCOM_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UMOSCOM_BUFSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &umoscom_read_callback, + }, + + [UMOSCOM_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umoscom_intr_callback, + }, +}; + +static const struct ucom_callback umoscom_callback = { + /* configuration callbacks */ + .ucom_cfg_get_status = &umoscom_cfg_get_status, + .ucom_cfg_set_dtr = &umoscom_cfg_set_dtr, + .ucom_cfg_set_rts = &umoscom_cfg_set_rts, + .ucom_cfg_set_break = &umoscom_cfg_set_break, + .ucom_cfg_param = &umoscom_cfg_param, + .ucom_cfg_open = &umoscom_cfg_open, + .ucom_cfg_close = &umoscom_cfg_close, + + /* other callbacks */ + .ucom_pre_param = &umoscom_pre_param, + .ucom_start_read = &umoscom_start_read, + .ucom_stop_read = &umoscom_stop_read, + .ucom_start_write = &umoscom_start_write, + .ucom_stop_write = &umoscom_stop_write, + .ucom_poll = &umoscom_poll, +}; + +static device_method_t umoscom_methods[] = { + DEVMETHOD(device_probe, umoscom_probe), + DEVMETHOD(device_attach, umoscom_attach), + DEVMETHOD(device_detach, umoscom_detach), + {0, 0} +}; + +static devclass_t umoscom_devclass; + +static driver_t umoscom_driver = { + .name = "umoscom", + .methods = umoscom_methods, + .size = sizeof(struct umoscom_softc), +}; + +DRIVER_MODULE(umoscom, uhub, umoscom_driver, umoscom_devclass, NULL, 0); +MODULE_DEPEND(umoscom, ucom, 1, 1, 1); +MODULE_DEPEND(umoscom, usb, 1, 1, 1); +MODULE_VERSION(umoscom, 1); + +static const STRUCT_USB_HOST_ID umoscom_devs[] = { + {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7703, 0)} +}; + +static int +umoscom_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UMOSCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UMOSCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(umoscom_devs, sizeof(umoscom_devs), uaa)); +} + +static int +umoscom_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct umoscom_softc *sc = device_get_softc(dev); + int error; + uint8_t iface_index; + + sc->sc_udev = uaa->device; + sc->sc_mcr = 0x08; /* enable interrupts */ + + /* XXX the device doesn't provide any ID string, so set a static one */ + device_set_desc(dev, "MOSCHIP USB Serial Port Adapter"); + device_printf(dev, "\n"); + + mtx_init(&sc->sc_mtx, "umoscom", NULL, MTX_DEF); + + iface_index = UMOSCOM_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, umoscom_config_data, + UMOSCOM_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UMOSCOM_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UMOSCOM_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &umoscom_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + device_printf(dev, "attach error: %s\n", usbd_errstr(error)); + umoscom_detach(dev); + return (ENXIO); +} + +static int +umoscom_detach(device_t dev) +{ + struct umoscom_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UMOSCOM_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +umoscom_cfg_open(struct ucom_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + /* Purge FIFOs or odd things happen */ + umoscom_cfg_write(sc, UMOSCOM_FIFO, 0x00 | UMOSCOM_UART_REG); + + /* Enable FIFO */ + umoscom_cfg_write(sc, UMOSCOM_FIFO, UMOSCOM_FIFO_EN | + UMOSCOM_FIFO_RXCLR | UMOSCOM_FIFO_TXCLR | + UMOSCOM_FIFO_DMA_BLK | UMOSCOM_FIFO_RXLVL_MASK | + UMOSCOM_UART_REG); + + /* Enable Interrupt Registers */ + umoscom_cfg_write(sc, UMOSCOM_INT, 0x0C | UMOSCOM_UART_REG); + + /* Magic */ + umoscom_cfg_write(sc, 0x01, 0x08); + + /* Magic */ + umoscom_cfg_write(sc, 0x00, 0x02); +} + +static void +umoscom_cfg_close(struct ucom_softc *ucom) +{ + return; +} + +static void +umoscom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umoscom_softc *sc = ucom->sc_parent; + uint16_t val; + + val = sc->sc_lcr; + if (onoff) + val |= UMOSCOM_LCR_BREAK; + + umoscom_cfg_write(sc, UMOSCOM_LCR, val | UMOSCOM_UART_REG); +} + +static void +umoscom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_mcr |= UMOSCOM_MCR_DTR; + else + sc->sc_mcr &= ~UMOSCOM_MCR_DTR; + + umoscom_cfg_write(sc, UMOSCOM_MCR, sc->sc_mcr | UMOSCOM_UART_REG); +} + +static void +umoscom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_mcr |= UMOSCOM_MCR_RTS; + else + sc->sc_mcr &= ~UMOSCOM_MCR_RTS; + + umoscom_cfg_write(sc, UMOSCOM_MCR, sc->sc_mcr | UMOSCOM_UART_REG); +} + +static int +umoscom_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + if ((t->c_ospeed <= 1) || (t->c_ospeed > 115200)) + return (EINVAL); + + return (0); +} + +static void +umoscom_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct umoscom_softc *sc = ucom->sc_parent; + uint16_t data; + + DPRINTF("speed=%d\n", t->c_ospeed); + + data = ((uint32_t)UMOSCOM_BAUD_REF) / ((uint32_t)t->c_ospeed); + + if (data == 0) { + DPRINTF("invalid baud rate!\n"); + return; + } + umoscom_cfg_write(sc, UMOSCOM_LCR, + UMOSCOM_LCR_DIVLATCH_EN | UMOSCOM_UART_REG); + + umoscom_cfg_write(sc, UMOSCOM_BAUDLO, + (data & 0xFF) | UMOSCOM_UART_REG); + + umoscom_cfg_write(sc, UMOSCOM_BAUDHI, + ((data >> 8) & 0xFF) | UMOSCOM_UART_REG); + + if (t->c_cflag & CSTOPB) + data = UMOSCOM_LCR_STOP_BITS_2; + else + data = UMOSCOM_LCR_STOP_BITS_1; + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) + data |= UMOSCOM_LCR_PARITY_ODD; + else + data |= UMOSCOM_LCR_PARITY_EVEN; + } else + data |= UMOSCOM_LCR_PARITY_NONE; + + switch (t->c_cflag & CSIZE) { + case CS5: + data |= UMOSCOM_LCR_DBITS(5); + break; + case CS6: + data |= UMOSCOM_LCR_DBITS(6); + break; + case CS7: + data |= UMOSCOM_LCR_DBITS(7); + break; + case CS8: + data |= UMOSCOM_LCR_DBITS(8); + break; + } + + sc->sc_lcr = data; + umoscom_cfg_write(sc, UMOSCOM_LCR, data | UMOSCOM_UART_REG); +} + +static void +umoscom_cfg_get_status(struct ucom_softc *ucom, uint8_t *p_lsr, uint8_t *p_msr) +{ + struct umoscom_softc *sc = ucom->sc_parent; + uint8_t lsr; + uint8_t msr; + + DPRINTFN(5, "\n"); + + /* read status registers */ + + lsr = umoscom_cfg_read(sc, UMOSCOM_LSR); + msr = umoscom_cfg_read(sc, UMOSCOM_MSR); + + /* translate bits */ + + if (msr & UMOSCOM_MSR_CTS) + *p_msr |= SER_CTS; + + if (msr & UMOSCOM_MSR_CD) + *p_msr |= SER_DCD; + + if (msr & UMOSCOM_MSR_RI) + *p_msr |= SER_RI; + + if (msr & UMOSCOM_MSR_RTS) + *p_msr |= SER_DSR; +} + +static void +umoscom_cfg_write(struct umoscom_softc *sc, uint16_t reg, uint16_t val) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UMOSCOM_WRITE; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static uint8_t +umoscom_cfg_read(struct umoscom_softc *sc, uint16_t reg) +{ + struct usb_device_request req; + uint8_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UMOSCOM_READ; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, &val, 0, 1000); + + DPRINTF("reg=0x%04x, val=0x%02x\n", reg, val); + + return (val); +} + +static void +umoscom_start_read(struct ucom_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + +#if 0 + /* start interrupt endpoint */ + usbd_transfer_start(sc->sc_xfer[UMOSCOM_INTR_DT_RD]); +#endif + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UMOSCOM_BULK_DT_RD]); +} + +static void +umoscom_stop_read(struct ucom_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + /* stop interrupt transfer */ + usbd_transfer_stop(sc->sc_xfer[UMOSCOM_INTR_DT_RD]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UMOSCOM_BULK_DT_RD]); +} + +static void +umoscom_start_write(struct ucom_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UMOSCOM_BULK_DT_WR]); +} + +static void +umoscom_stop_write(struct ucom_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UMOSCOM_BULK_DT_WR]); +} + +static void +umoscom_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umoscom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + DPRINTF("\n"); + + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UMOSCOM_BUFSIZE, &actlen)) { + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + DPRINTFN(0, "transfer failed\n"); + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umoscom_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umoscom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTF("got %d bytes\n", actlen); + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + DPRINTF("\n"); + + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + DPRINTFN(0, "transfer failed\n"); + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umoscom_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umoscom_softc *sc = usbd_xfer_softc(xfer); + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen < 2) { + DPRINTF("too short message\n"); + goto tr_setup; + } + ucom_status_change(&sc->sc_ucom); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + DPRINTFN(0, "transfer failed\n"); + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umoscom_poll(struct ucom_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UMOSCOM_N_TRANSFER); +} diff --git a/sys/bus/u4b/serial/uplcom.c b/sys/bus/u4b/serial/uplcom.c new file mode 100644 index 0000000000..f868a53ea1 --- /dev/null +++ b/sys/bus/u4b/serial/uplcom.c @@ -0,0 +1,907 @@ +/* $NetBSD: uplcom.c,v 1.21 2001/11/13 06:24:56 lukem Exp $ */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2001-2003, 2005 Shunsuke Akiyama . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ichiro FUKUHARA (ichiro@ichiro.org). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This driver supports several USB-to-RS232 serial adapters driven by + * Prolific PL-2303, PL-2303X and probably PL-2303HX USB-to-RS232 + * bridge chip. The adapters are sold under many different brand + * names. + * + * Datasheets are available at Prolific www site at + * http://www.prolific.com.tw. The datasheets don't contain full + * programming information for the chip. + * + * PL-2303HX is probably programmed the same as PL-2303X. + * + * There are several differences between PL-2303 and PL-2303(H)X. + * PL-2303(H)X can do higher bitrate in bulk mode, has _probably_ + * different command for controlling CRTSCTS and needs special + * sequence of commands for initialization which aren't also + * documented in the datasheet. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR uplcom_debug +#include +#include + +#include + +#ifdef USB_DEBUG +static int uplcom_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uplcom, CTLFLAG_RW, 0, "USB uplcom"); +SYSCTL_INT(_hw_usb_uplcom, OID_AUTO, debug, CTLFLAG_RW, + &uplcom_debug, 0, "Debug level"); +#endif + +#define UPLCOM_MODVER 1 /* module version */ + +#define UPLCOM_CONFIG_INDEX 0 +#define UPLCOM_IFACE_INDEX 0 +#define UPLCOM_SECOND_IFACE_INDEX 1 + +#ifndef UPLCOM_INTR_INTERVAL +#define UPLCOM_INTR_INTERVAL 0 /* default */ +#endif + +#define UPLCOM_BULK_BUF_SIZE 1024 /* bytes */ + +#define UPLCOM_SET_REQUEST 0x01 +#define UPLCOM_SET_CRTSCTS 0x41 +#define UPLCOM_SET_CRTSCTS_PL2303X 0x61 +#define RSAQ_STATUS_CTS 0x80 +#define RSAQ_STATUS_DSR 0x02 +#define RSAQ_STATUS_DCD 0x01 + +#define TYPE_PL2303 0 +#define TYPE_PL2303HX 1 + +enum { + UPLCOM_BULK_DT_WR, + UPLCOM_BULK_DT_RD, + UPLCOM_INTR_DT_RD, + UPLCOM_N_TRANSFER, +}; + +struct uplcom_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UPLCOM_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint16_t sc_line; + + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* uplcom status register */ + uint8_t sc_chiptype; /* type of chip */ + uint8_t sc_ctrl_iface_no; + uint8_t sc_data_iface_no; + uint8_t sc_iface_index[2]; +}; + +/* prototypes */ + +static usb_error_t uplcom_reset(struct uplcom_softc *, struct usb_device *); +static usb_error_t uplcom_pl2303_do(struct usb_device *, int8_t, uint8_t, + uint16_t, uint16_t, uint16_t); +static int uplcom_pl2303_init(struct usb_device *, uint8_t); +static void uplcom_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void uplcom_cfg_set_rts(struct ucom_softc *, uint8_t); +static void uplcom_cfg_set_break(struct ucom_softc *, uint8_t); +static int uplcom_pre_param(struct ucom_softc *, struct termios *); +static void uplcom_cfg_param(struct ucom_softc *, struct termios *); +static void uplcom_start_read(struct ucom_softc *); +static void uplcom_stop_read(struct ucom_softc *); +static void uplcom_start_write(struct ucom_softc *); +static void uplcom_stop_write(struct ucom_softc *); +static void uplcom_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void uplcom_poll(struct ucom_softc *ucom); + +static device_probe_t uplcom_probe; +static device_attach_t uplcom_attach; +static device_detach_t uplcom_detach; + +static usb_callback_t uplcom_intr_callback; +static usb_callback_t uplcom_write_callback; +static usb_callback_t uplcom_read_callback; + +static const struct usb_config uplcom_config_data[UPLCOM_N_TRANSFER] = { + + [UPLCOM_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UPLCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &uplcom_write_callback, + .if_index = 0, + }, + + [UPLCOM_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UPLCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uplcom_read_callback, + .if_index = 0, + }, + + [UPLCOM_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &uplcom_intr_callback, + .if_index = 1, + }, +}; + +static struct ucom_callback uplcom_callback = { + .ucom_cfg_get_status = &uplcom_cfg_get_status, + .ucom_cfg_set_dtr = &uplcom_cfg_set_dtr, + .ucom_cfg_set_rts = &uplcom_cfg_set_rts, + .ucom_cfg_set_break = &uplcom_cfg_set_break, + .ucom_cfg_param = &uplcom_cfg_param, + .ucom_pre_param = &uplcom_pre_param, + .ucom_start_read = &uplcom_start_read, + .ucom_stop_read = &uplcom_stop_read, + .ucom_start_write = &uplcom_start_write, + .ucom_stop_write = &uplcom_stop_write, + .ucom_poll = &uplcom_poll, +}; + +#define UPLCOM_DEV(v,p) \ + { USB_VENDOR(USB_VENDOR_##v), USB_PRODUCT(USB_PRODUCT_##v##_##p) } + +static const STRUCT_USB_HOST_ID uplcom_devs[] = { + UPLCOM_DEV(ACERP, S81), /* BenQ S81 phone */ + UPLCOM_DEV(ADLINK, ND6530), /* ADLINK ND-6530 USB-Serial */ + UPLCOM_DEV(ALCATEL, OT535), /* Alcatel One Touch 535/735 */ + UPLCOM_DEV(ALCOR, AU9720), /* Alcor AU9720 USB 2.0-RS232 */ + UPLCOM_DEV(ANCHOR, SERIAL), /* Anchor Serial adapter */ + UPLCOM_DEV(ATEN, UC232A), /* PLANEX USB-RS232 URS-03 */ + UPLCOM_DEV(BELKIN, F5U257), /* Belkin F5U257 USB to Serial */ + UPLCOM_DEV(COREGA, CGUSBRS232R), /* Corega CG-USBRS232R */ + UPLCOM_DEV(EPSON, CRESSI_EDY), /* Cressi Edy diving computer */ + UPLCOM_DEV(EPSON, N2ITION3), /* Zeagle N2iTion3 diving computer */ + UPLCOM_DEV(ELECOM, UCSGT), /* ELECOM UC-SGT Serial Adapter */ + UPLCOM_DEV(ELECOM, UCSGT0), /* ELECOM UC-SGT Serial Adapter */ + UPLCOM_DEV(HAL, IMR001), /* HAL Corporation Crossam2+USB */ + UPLCOM_DEV(HP, LD220), /* HP LD220 POS Display */ + UPLCOM_DEV(IODATA, USBRSAQ), /* I/O DATA USB-RSAQ */ + UPLCOM_DEV(IODATA, USBRSAQ5), /* I/O DATA USB-RSAQ5 */ + UPLCOM_DEV(ITEGNO, WM1080A), /* iTegno WM1080A GSM/GFPRS modem */ + UPLCOM_DEV(ITEGNO, WM2080A), /* iTegno WM2080A CDMA modem */ + UPLCOM_DEV(LEADTEK, 9531), /* Leadtek 9531 GPS */ + UPLCOM_DEV(MICROSOFT, 700WX), /* Microsoft Palm 700WX */ + UPLCOM_DEV(MOBILEACTION, MA620), /* Mobile Action MA-620 Infrared Adapter */ + UPLCOM_DEV(NETINDEX, WS002IN), /* Willcom W-S002IN */ + UPLCOM_DEV(NOKIA2, CA42), /* Nokia CA-42 cable */ + UPLCOM_DEV(OTI, DKU5), /* OTI DKU-5 cable */ + UPLCOM_DEV(PANASONIC, TYTP50P6S), /* Panasonic TY-TP50P6-S flat screen */ + UPLCOM_DEV(PLX, CA42), /* PLX CA-42 clone cable */ + UPLCOM_DEV(PROLIFIC, ALLTRONIX_GPRS), /* Alltronix ACM003U00 modem */ + UPLCOM_DEV(PROLIFIC, ALDIGA_AL11U), /* AlDiga AL-11U modem */ + UPLCOM_DEV(PROLIFIC, DCU11), /* DCU-11 Phone Cable */ + UPLCOM_DEV(PROLIFIC, HCR331), /* HCR331 Card Reader */ + UPLCOM_DEV(PROLIFIC, MICROMAX_610U), /* Micromax 610U modem */ + UPLCOM_DEV(PROLIFIC, PHAROS), /* Prolific Pharos */ + UPLCOM_DEV(PROLIFIC, PL2303), /* Generic adapter */ + UPLCOM_DEV(PROLIFIC, RSAQ2), /* I/O DATA USB-RSAQ2 */ + UPLCOM_DEV(PROLIFIC, RSAQ3), /* I/O DATA USB-RSAQ3 */ + UPLCOM_DEV(PROLIFIC, UIC_MSR206), /* UIC MSR206 Card Reader */ + UPLCOM_DEV(PROLIFIC2, PL2303), /* Prolific adapter */ + UPLCOM_DEV(RADIOSHACK, USBCABLE), /* Radio Shack USB Adapter */ + UPLCOM_DEV(RATOC, REXUSB60), /* RATOC REX-USB60 */ + UPLCOM_DEV(SAGEM, USBSERIAL), /* Sagem USB-Serial Controller */ + UPLCOM_DEV(SAMSUNG, I330), /* Samsung I330 phone cradle */ + UPLCOM_DEV(SANWA, KB_USB2), /* Sanwa KB-USB2 Multimeter cable */ + UPLCOM_DEV(SIEMENS3, EF81), /* Siemens EF81 */ + UPLCOM_DEV(SIEMENS3, SX1), /* Siemens SX1 */ + UPLCOM_DEV(SIEMENS3, X65), /* Siemens X65 */ + UPLCOM_DEV(SIEMENS3, X75), /* Siemens X75 */ + UPLCOM_DEV(SITECOM, SERIAL), /* Sitecom USB to Serial */ + UPLCOM_DEV(SMART, PL2303), /* SMART Technologies USB to Serial */ + UPLCOM_DEV(SONY, QN3), /* Sony QN3 phone cable */ + UPLCOM_DEV(SONYERICSSON, DATAPILOT), /* Sony Ericsson Datapilot */ + UPLCOM_DEV(SONYERICSSON, DCU10), /* Sony Ericsson DCU-10 Cable */ + UPLCOM_DEV(SOURCENEXT, KEIKAI8), /* SOURCENEXT KeikaiDenwa 8 */ + UPLCOM_DEV(SOURCENEXT, KEIKAI8_CHG), /* SOURCENEXT KeikaiDenwa 8 with charger */ + UPLCOM_DEV(SPEEDDRAGON, MS3303H), /* Speed Dragon USB-Serial */ + UPLCOM_DEV(SYNTECH, CPT8001C), /* Syntech CPT-8001C Barcode scanner */ + UPLCOM_DEV(TDK, UHA6400), /* TDK USB-PHS Adapter UHA6400 */ + UPLCOM_DEV(TDK, UPA9664), /* TDK USB-PHS Adapter UPA9664 */ + UPLCOM_DEV(TRIPPLITE, U209), /* Tripp-Lite U209-000-R USB to Serial */ + UPLCOM_DEV(YCCABLE, PL2303), /* YC Cable USB-Serial */ +}; +#undef UPLCOM_DEV + +static device_method_t uplcom_methods[] = { + DEVMETHOD(device_probe, uplcom_probe), + DEVMETHOD(device_attach, uplcom_attach), + DEVMETHOD(device_detach, uplcom_detach), + {0, 0} +}; + +static devclass_t uplcom_devclass; + +static driver_t uplcom_driver = { + .name = "uplcom", + .methods = uplcom_methods, + .size = sizeof(struct uplcom_softc), +}; + +DRIVER_MODULE(uplcom, uhub, uplcom_driver, uplcom_devclass, NULL, 0); +MODULE_DEPEND(uplcom, ucom, 1, 1, 1); +MODULE_DEPEND(uplcom, usb, 1, 1, 1); +MODULE_VERSION(uplcom, UPLCOM_MODVER); + +static int +uplcom_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + DPRINTFN(11, "\n"); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UPLCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UPLCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uplcom_devs, sizeof(uplcom_devs), uaa)); +} + +static int +uplcom_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uplcom_softc *sc = device_get_softc(dev); + struct usb_interface *iface; + struct usb_interface_descriptor *id; + struct usb_device_descriptor *dd; + int error; + + DPRINTFN(11, "\n"); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uplcom", NULL, MTX_DEF); + + DPRINTF("sc = %p\n", sc); + + sc->sc_udev = uaa->device; + + /* Determine the chip type. This algorithm is taken from Linux. */ + dd = usbd_get_device_descriptor(sc->sc_udev); + if (dd->bDeviceClass == 0x02) + sc->sc_chiptype = TYPE_PL2303; + else if (dd->bMaxPacketSize == 0x40) + sc->sc_chiptype = TYPE_PL2303HX; + else + sc->sc_chiptype = TYPE_PL2303; + + DPRINTF("chiptype: %s\n", + (sc->sc_chiptype == TYPE_PL2303HX) ? + "2303X" : "2303"); + + /* + * USB-RSAQ1 has two interface + * + * USB-RSAQ1 | USB-RSAQ2 + * -----------------+----------------- + * Interface 0 |Interface 0 + * Interrupt(0x81) | Interrupt(0x81) + * -----------------+ BulkIN(0x02) + * Interface 1 | BulkOUT(0x83) + * BulkIN(0x02) | + * BulkOUT(0x83) | + */ + + sc->sc_ctrl_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index[1] = UPLCOM_IFACE_INDEX; + + iface = usbd_get_iface(uaa->device, UPLCOM_SECOND_IFACE_INDEX); + if (iface) { + id = usbd_get_interface_descriptor(iface); + if (id == NULL) { + device_printf(dev, "no interface descriptor (2)\n"); + goto detach; + } + sc->sc_data_iface_no = id->bInterfaceNumber; + sc->sc_iface_index[0] = UPLCOM_SECOND_IFACE_INDEX; + usbd_set_parent_iface(uaa->device, + UPLCOM_SECOND_IFACE_INDEX, uaa->info.bIfaceIndex); + } else { + sc->sc_data_iface_no = sc->sc_ctrl_iface_no; + sc->sc_iface_index[0] = UPLCOM_IFACE_INDEX; + } + + error = usbd_transfer_setup(uaa->device, + sc->sc_iface_index, sc->sc_xfer, uplcom_config_data, + UPLCOM_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + DPRINTF("one or more missing USB endpoints, " + "error=%s\n", usbd_errstr(error)); + goto detach; + } + error = uplcom_reset(sc, uaa->device); + if (error) { + device_printf(dev, "reset failed, error=%s\n", + usbd_errstr(error)); + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uplcom_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + /* + * do the initialization during attach so that the system does not + * sleep during open: + */ + if (uplcom_pl2303_init(uaa->device, sc->sc_chiptype)) { + device_printf(dev, "init failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + uplcom_detach(dev); + return (ENXIO); +} + +static int +uplcom_detach(device_t dev) +{ + struct uplcom_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UPLCOM_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static usb_error_t +uplcom_reset(struct uplcom_softc *sc, struct usb_device *udev) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + return (usbd_do_request(udev, NULL, &req, NULL)); +} + +static usb_error_t +uplcom_pl2303_do(struct usb_device *udev, int8_t req_type, uint8_t request, + uint16_t value, uint16_t index, uint16_t length) +{ + struct usb_device_request req; + usb_error_t err; + uint8_t buf[4]; + + req.bmRequestType = req_type; + req.bRequest = request; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, length); + + err = usbd_do_request(udev, NULL, &req, buf); + if (err) { + DPRINTF("error=%s\n", usbd_errstr(err)); + return (1); + } + return (0); +} + +static int +uplcom_pl2303_init(struct usb_device *udev, uint8_t chiptype) +{ + int err; + + if (uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) + || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 0, 0) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 1) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) + || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 1, 0) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 1) + || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0, 1, 0) + || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 1, 0, 0)) + return (EIO); + + if (chiptype == TYPE_PL2303HX) + err = uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x44, 0); + else + err = uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x24, 0); + if (err) + return (EIO); + + if (uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 8, 0, 0) + || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 9, 0, 0)) + return (EIO); + return (0); +} + +static void +uplcom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uplcom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uplcom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t temp; + + DPRINTF("onoff = %d\n", onoff); + + temp = (onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, temp); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static const int32_t uplcom_rates[] = { + 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, + 19200, 28800, 38400, 57600, 115200, + /* + * Higher speeds are probably possible. PL2303X supports up to + * 6Mb and can set any rate + */ + 230400, 460800, 614400, 921600, 1228800 +}; + +#define N_UPLCOM_RATES (sizeof(uplcom_rates)/sizeof(uplcom_rates[0])) + +static int +uplcom_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uplcom_softc *sc = ucom->sc_parent; + uint8_t i; + + DPRINTF("\n"); + + /** + * Check requested baud rate. + * + * The PL2303 can only set specific baud rates, up to 1228800 baud. + * The PL2303X can set any baud rate up to 6Mb. + * The PL2303HX rev. D can set any baud rate up to 12Mb. + * + * XXX: We currently cannot identify the PL2303HX rev. D, so treat + * it the same as the PL2303X. + */ + if (sc->sc_chiptype != TYPE_PL2303HX) { + for (i = 0; i < N_UPLCOM_RATES; i++) { + if (uplcom_rates[i] == t->c_ospeed) + return (0); + } + } else { + if (t->c_ospeed <= 6000000) + return (0); + } + + DPRINTF("uplcom_param: bad baud rate (%d)\n", t->c_ospeed); + return (EIO); +} + +static void +uplcom_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb_cdc_line_state ls; + struct usb_device_request req; + + DPRINTF("sc = %p\n", sc); + + memset(&ls, 0, sizeof(ls)); + + USETDW(ls.dwDTERate, t->c_ospeed); + + if (t->c_cflag & CSTOPB) { + ls.bCharFormat = UCDC_STOP_BIT_2; + } else { + ls.bCharFormat = UCDC_STOP_BIT_1; + } + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) { + ls.bParityType = UCDC_PARITY_ODD; + } else { + ls.bParityType = UCDC_PARITY_EVEN; + } + } else { + ls.bParityType = UCDC_PARITY_NONE; + } + + switch (t->c_cflag & CSIZE) { + case CS5: + ls.bDataBits = 5; + break; + case CS6: + ls.bDataBits = 6; + break; + case CS7: + ls.bDataBits = 7; + break; + case CS8: + ls.bDataBits = 8; + break; + } + + DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n", + UGETDW(ls.dwDTERate), ls.bCharFormat, + ls.bParityType, ls.bDataBits); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_LINE_CODING; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, UCDC_LINE_STATE_LENGTH); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, &ls, 0, 1000); + + if (t->c_cflag & CRTSCTS) { + + DPRINTF("crtscts = on\n"); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + if (sc->sc_chiptype == TYPE_PL2303HX) + USETW(req.wIndex, UPLCOM_SET_CRTSCTS_PL2303X); + else + USETW(req.wIndex, UPLCOM_SET_CRTSCTS); + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + } else { + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + } +} + +static void +uplcom_start_read(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint */ + usbd_transfer_start(sc->sc_xfer[UPLCOM_INTR_DT_RD]); + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UPLCOM_BULK_DT_RD]); +} + +static void +uplcom_stop_read(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint */ + usbd_transfer_stop(sc->sc_xfer[UPLCOM_INTR_DT_RD]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UPLCOM_BULK_DT_RD]); +} + +static void +uplcom_start_write(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UPLCOM_BULK_DT_WR]); +} + +static void +uplcom_stop_write(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UPLCOM_BULK_DT_WR]); +} + +static void +uplcom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +uplcom_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uplcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[9]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("actlen = %u\n", actlen); + + if (actlen >= 9) { + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, sizeof(buf)); + + DPRINTF("status = 0x%02x\n", buf[8]); + + sc->sc_lsr = 0; + sc->sc_msr = 0; + + if (buf[8] & RSAQ_STATUS_CTS) { + sc->sc_msr |= SER_CTS; + } + if (buf[8] & RSAQ_STATUS_DSR) { + sc->sc_msr |= SER_DSR; + } + if (buf[8] & RSAQ_STATUS_DCD) { + sc->sc_msr |= SER_DCD; + } + ucom_status_change(&sc->sc_ucom); + } + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uplcom_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uplcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UPLCOM_BULK_BUF_SIZE, &actlen)) { + + DPRINTF("actlen = %d\n", actlen); + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uplcom_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uplcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uplcom_poll(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UPLCOM_N_TRANSFER); +} diff --git a/sys/bus/u4b/serial/usb_serial.c b/sys/bus/u4b/serial/usb_serial.c new file mode 100644 index 0000000000..08ce2cab3b --- /dev/null +++ b/sys/bus/u4b/serial/usb_serial.c @@ -0,0 +1,1489 @@ +/* $NetBSD: ucom.c,v 1.40 2001/11/13 06:24:54 lukem Exp $ */ + +/*- + * Copyright (c) 2001-2003, 2005, 2008 + * Shunsuke Akiyama . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define USB_DEBUG_VAR ucom_debug +#include +#include +#include + +#include + +#include "opt_gdb.h" + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom"); + +#ifdef USB_DEBUG +static int ucom_debug = 0; + +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RW, + &ucom_debug, 0, "ucom debug level"); +#endif + +#define UCOM_CONS_BUFSIZE 1024 + +static uint8_t ucom_cons_rx_buf[UCOM_CONS_BUFSIZE]; +static uint8_t ucom_cons_tx_buf[UCOM_CONS_BUFSIZE]; + +static unsigned int ucom_cons_rx_low = 0; +static unsigned int ucom_cons_rx_high = 0; + +static unsigned int ucom_cons_tx_low = 0; +static unsigned int ucom_cons_tx_high = 0; + +static int ucom_cons_unit = -1; +static int ucom_cons_subunit = 0; +static int ucom_cons_baud = 9600; +static struct ucom_softc *ucom_cons_softc = NULL; + +TUNABLE_INT("hw.usb.ucom.cons_unit", &ucom_cons_unit); +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_unit, CTLFLAG_RW, + &ucom_cons_unit, 0, "console unit number"); +TUNABLE_INT("hw.usb.ucom.cons_subunit", &ucom_cons_subunit); +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_subunit, CTLFLAG_RW, + &ucom_cons_subunit, 0, "console subunit number"); +TUNABLE_INT("hw.usb.ucom.cons_baud", &ucom_cons_baud); +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_baud, CTLFLAG_RW, + &ucom_cons_baud, 0, "console baud rate"); + +static usb_proc_callback_t ucom_cfg_start_transfers; +static usb_proc_callback_t ucom_cfg_open; +static usb_proc_callback_t ucom_cfg_close; +static usb_proc_callback_t ucom_cfg_line_state; +static usb_proc_callback_t ucom_cfg_status_change; +static usb_proc_callback_t ucom_cfg_param; + +static int ucom_unit_alloc(void); +static void ucom_unit_free(int); +static int ucom_attach_tty(struct ucom_super_softc *, struct ucom_softc *); +static void ucom_detach_tty(struct ucom_softc *); +static void ucom_queue_command(struct ucom_softc *, + usb_proc_callback_t *, struct termios *pt, + struct usb_proc_msg *t0, struct usb_proc_msg *t1); +static void ucom_shutdown(struct ucom_softc *); +static void ucom_ring(struct ucom_softc *, uint8_t); +static void ucom_break(struct ucom_softc *, uint8_t); +static void ucom_dtr(struct ucom_softc *, uint8_t); +static void ucom_rts(struct ucom_softc *, uint8_t); + +static tsw_open_t ucom_open; +static tsw_close_t ucom_close; +static tsw_ioctl_t ucom_ioctl; +static tsw_modem_t ucom_modem; +static tsw_param_t ucom_param; +static tsw_outwakeup_t ucom_outwakeup; +static tsw_free_t ucom_free; + +static struct ttydevsw ucom_class = { + .tsw_flags = TF_INITLOCK | TF_CALLOUT, + .tsw_open = ucom_open, + .tsw_close = ucom_close, + .tsw_outwakeup = ucom_outwakeup, + .tsw_ioctl = ucom_ioctl, + .tsw_param = ucom_param, + .tsw_modem = ucom_modem, + .tsw_free = ucom_free, +}; + +MODULE_DEPEND(ucom, usb, 1, 1, 1); +MODULE_VERSION(ucom, 1); + +#define UCOM_UNIT_MAX 128 /* limits size of ucom_bitmap */ + +static uint8_t ucom_bitmap[(UCOM_UNIT_MAX + 7) / 8]; +static struct mtx ucom_bitmap_mtx; +MTX_SYSINIT(ucom_bitmap_mtx, &ucom_bitmap_mtx, "ucom bitmap", MTX_DEF); + +#define UCOM_TTY_PREFIX "U" + +/* + * Mark a unit number (the X in cuaUX) as in use. + * + * Note that devices using a different naming scheme (see ucom_tty_name() + * callback) still use this unit allocation. + */ +static int +ucom_unit_alloc(void) +{ + int unit; + + mtx_lock(&ucom_bitmap_mtx); + + for (unit = 0; unit < UCOM_UNIT_MAX; unit++) { + if ((ucom_bitmap[unit / 8] & (1 << (unit % 8))) == 0) { + ucom_bitmap[unit / 8] |= (1 << (unit % 8)); + break; + } + } + + mtx_unlock(&ucom_bitmap_mtx); + + if (unit == UCOM_UNIT_MAX) + return -1; + else + return unit; +} + +/* + * Mark the unit number as not in use. + */ +static void +ucom_unit_free(int unit) +{ + mtx_lock(&ucom_bitmap_mtx); + + ucom_bitmap[unit / 8] &= ~(1 << (unit % 8)); + + mtx_unlock(&ucom_bitmap_mtx); +} + +/* + * Setup a group of one or more serial ports. + * + * The mutex pointed to by "mtx" is applied before all + * callbacks are called back. Also "mtx" must be applied + * before calling into the ucom-layer! + */ +int +ucom_attach(struct ucom_super_softc *ssc, struct ucom_softc *sc, + uint32_t subunits, void *parent, + const struct ucom_callback *callback, struct mtx *mtx) +{ + uint32_t subunit; + int error = 0; + + if ((sc == NULL) || + (subunits == 0) || + (callback == NULL)) { + return (EINVAL); + } + + /* allocate a uniq unit number */ + ssc->sc_unit = ucom_unit_alloc(); + if (ssc->sc_unit == -1) + return (ENOMEM); + + /* generate TTY name string */ + snprintf(ssc->sc_ttyname, sizeof(ssc->sc_ttyname), + UCOM_TTY_PREFIX "%d", ssc->sc_unit); + + /* create USB request handling process */ + error = usb_proc_create(&ssc->sc_tq, mtx, "ucom", USB_PRI_MED); + if (error) { + ucom_unit_free(ssc->sc_unit); + return (error); + } + ssc->sc_subunits = subunits; + + for (subunit = 0; subunit < ssc->sc_subunits; subunit++) { + sc[subunit].sc_subunit = subunit; + sc[subunit].sc_super = ssc; + sc[subunit].sc_mtx = mtx; + sc[subunit].sc_parent = parent; + sc[subunit].sc_callback = callback; + + error = ucom_attach_tty(ssc, &sc[subunit]); + if (error) { + ucom_detach(ssc, &sc[0]); + return (error); + } + sc[subunit].sc_flag |= UCOM_FLAG_ATTACHED; + } + + DPRINTF("tp = %p, unit = %d, subunits = %d\n", + sc->sc_tty, ssc->sc_unit, ssc->sc_subunits); + + return (0); +} + +/* + * NOTE: the following function will do nothing if + * the structure pointed to by "ssc" and "sc" is zero. + */ +void +ucom_detach(struct ucom_super_softc *ssc, struct ucom_softc *sc) +{ + uint32_t subunit; + + if (ssc->sc_subunits == 0) + return; /* not initialized */ + + if (ssc->sc_sysctl_ttyname != NULL) { + sysctl_remove_oid(ssc->sc_sysctl_ttyname, 1, 0); + ssc->sc_sysctl_ttyname = NULL; + } + + if (ssc->sc_sysctl_ttyports != NULL) { + sysctl_remove_oid(ssc->sc_sysctl_ttyports, 1, 0); + ssc->sc_sysctl_ttyports = NULL; + } + + usb_proc_drain(&ssc->sc_tq); + + for (subunit = 0; subunit < ssc->sc_subunits; subunit++) { + if (sc[subunit].sc_flag & UCOM_FLAG_ATTACHED) { + + ucom_detach_tty(&sc[subunit]); + + /* avoid duplicate detach */ + sc[subunit].sc_flag &= ~UCOM_FLAG_ATTACHED; + } + } + ucom_unit_free(ssc->sc_unit); + usb_proc_free(&ssc->sc_tq); +} + +static int +ucom_attach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc) +{ + struct tty *tp; + char buf[32]; /* temporary TTY device name buffer */ + + tp = tty_alloc_mutex(&ucom_class, sc, sc->sc_mtx); + if (tp == NULL) + return (ENOMEM); + + /* Check if the client has a custom TTY name */ + buf[0] = '\0'; + if (sc->sc_callback->ucom_tty_name) { + sc->sc_callback->ucom_tty_name(sc, buf, + sizeof(buf), ssc->sc_unit, sc->sc_subunit); + } + if (buf[0] == 0) { + /* Use default TTY name */ + if (ssc->sc_subunits > 1) { + /* multiple modems in one */ + snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u.%u", + ssc->sc_unit, sc->sc_subunit); + } else { + /* single modem */ + snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u", + ssc->sc_unit); + } + } + tty_makedev(tp, NULL, "%s", buf); + + sc->sc_tty = tp; + + DPRINTF("ttycreate: %s\n", buf); + cv_init(&sc->sc_cv, "ucom"); + + /* Check if this device should be a console */ + if ((ucom_cons_softc == NULL) && + (ssc->sc_unit == ucom_cons_unit) && + (sc->sc_subunit == ucom_cons_subunit)) { + struct termios t; + + DPRINTF("unit %d subunit %d is console", ssc->sc_unit, sc->sc_subunit); + + ucom_cons_softc = sc; + + memset(&t, 0, sizeof(t)); + t.c_ispeed = ucom_cons_baud; + t.c_ospeed = t.c_ispeed; + t.c_cflag = CS8; + + mtx_lock(ucom_cons_softc->sc_mtx); + ucom_cons_rx_low = 0; + ucom_cons_rx_high = 0; + ucom_cons_tx_low = 0; + ucom_cons_tx_high = 0; + sc->sc_flag |= UCOM_FLAG_CONSOLE; + ucom_open(ucom_cons_softc->sc_tty); + ucom_param(ucom_cons_softc->sc_tty, &t); + mtx_unlock(ucom_cons_softc->sc_mtx); + } + + return (0); +} + +static void +ucom_detach_tty(struct ucom_softc *sc) +{ + struct tty *tp = sc->sc_tty; + + DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty); + + if (sc->sc_flag & UCOM_FLAG_CONSOLE) { + mtx_lock(ucom_cons_softc->sc_mtx); + ucom_close(ucom_cons_softc->sc_tty); + sc->sc_flag &= ~UCOM_FLAG_CONSOLE; + mtx_unlock(ucom_cons_softc->sc_mtx); + ucom_cons_softc = NULL; + } + + /* the config thread has been stopped when we get here */ + + mtx_lock(sc->sc_mtx); + sc->sc_flag |= UCOM_FLAG_GONE; + sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_LL_READY); + mtx_unlock(sc->sc_mtx); + if (tp) { + tty_lock(tp); + + ucom_close(tp); /* close, if any */ + + tty_rel_gone(tp); + + mtx_lock(sc->sc_mtx); + /* Wait for the callback after the TTY is torn down */ + while (sc->sc_ttyfreed == 0) + cv_wait(&sc->sc_cv, sc->sc_mtx); + /* + * make sure that read and write transfers are stopped + */ + if (sc->sc_callback->ucom_stop_read) { + (sc->sc_callback->ucom_stop_read) (sc); + } + if (sc->sc_callback->ucom_stop_write) { + (sc->sc_callback->ucom_stop_write) (sc); + } + mtx_unlock(sc->sc_mtx); + } + cv_destroy(&sc->sc_cv); +} + +void +ucom_set_pnpinfo_usb(struct ucom_super_softc *ssc, device_t dev) +{ + char buf[64]; + uint8_t iface_index; + struct usb_attach_arg *uaa; + + snprintf(buf, sizeof(buf), "ttyname=" UCOM_TTY_PREFIX + "%d ttyports=%d", ssc->sc_unit, ssc->sc_subunits); + + /* Store the PNP info in the first interface for the device */ + uaa = device_get_ivars(dev); + iface_index = uaa->info.bIfaceIndex; + + if (usbd_set_pnpinfo(uaa->device, iface_index, buf) != 0) + device_printf(dev, "Could not set PNP info\n"); + + /* + * The following information is also replicated in the PNP-info + * string which is registered above: + */ + if (ssc->sc_sysctl_ttyname == NULL) { + ssc->sc_sysctl_ttyname = SYSCTL_ADD_STRING(NULL, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "ttyname", CTLFLAG_RD, ssc->sc_ttyname, 0, + "TTY device basename"); + } + if (ssc->sc_sysctl_ttyports == NULL) { + ssc->sc_sysctl_ttyports = SYSCTL_ADD_INT(NULL, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "ttyports", CTLFLAG_RD, + NULL, ssc->sc_subunits, "Number of ports"); + } +} + +static void +ucom_queue_command(struct ucom_softc *sc, + usb_proc_callback_t *fn, struct termios *pt, + struct usb_proc_msg *t0, struct usb_proc_msg *t1) +{ + struct ucom_super_softc *ssc = sc->sc_super; + struct ucom_param_task *task; + + mtx_assert(sc->sc_mtx, MA_OWNED); + + if (usb_proc_is_gone(&ssc->sc_tq)) { + DPRINTF("proc is gone\n"); + return; /* nothing to do */ + } + /* + * NOTE: The task cannot get executed before we drop the + * "sc_mtx" mutex. It is safe to update fields in the message + * structure after that the message got queued. + */ + task = (struct ucom_param_task *) + usb_proc_msignal(&ssc->sc_tq, t0, t1); + + /* Setup callback and softc pointers */ + task->hdr.pm_callback = fn; + task->sc = sc; + + /* + * Make a copy of the termios. This field is only present if + * the "pt" field is not NULL. + */ + if (pt != NULL) + task->termios_copy = *pt; + + /* + * Closing the device should be synchronous. + */ + if (fn == ucom_cfg_close) + usb_proc_mwait(&ssc->sc_tq, t0, t1); + + /* + * In case of multiple configure requests, + * keep track of the last one! + */ + if (fn == ucom_cfg_start_transfers) + sc->sc_last_start_xfer = &task->hdr; +} + +static void +ucom_shutdown(struct ucom_softc *sc) +{ + struct tty *tp = sc->sc_tty; + + mtx_assert(sc->sc_mtx, MA_OWNED); + + DPRINTF("\n"); + + /* + * Hang up if necessary: + */ + if (tp->t_termios.c_cflag & HUPCL) { + ucom_modem(tp, 0, SER_DTR); + } +} + +/* + * Return values: + * 0: normal + * else: taskqueue is draining or gone + */ +uint8_t +ucom_cfg_is_gone(struct ucom_softc *sc) +{ + struct ucom_super_softc *ssc = sc->sc_super; + + return (usb_proc_is_gone(&ssc->sc_tq)); +} + +static void +ucom_cfg_start_transfers(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + /* TTY device closed */ + return; + } + + if (_task == sc->sc_last_start_xfer) + sc->sc_flag |= UCOM_FLAG_GP_DATA; + + if (sc->sc_callback->ucom_start_read) { + (sc->sc_callback->ucom_start_read) (sc); + } + if (sc->sc_callback->ucom_start_write) { + (sc->sc_callback->ucom_start_write) (sc); + } +} + +static void +ucom_start_transfers(struct ucom_softc *sc) +{ + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + /* + * Make sure that data transfers are started in both + * directions: + */ + if (sc->sc_callback->ucom_start_read) { + (sc->sc_callback->ucom_start_read) (sc); + } + if (sc->sc_callback->ucom_start_write) { + (sc->sc_callback->ucom_start_write) (sc); + } +} + +static void +ucom_cfg_open(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + + DPRINTF("\n"); + + if (sc->sc_flag & UCOM_FLAG_LL_READY) { + + /* already opened */ + + } else { + + sc->sc_flag |= UCOM_FLAG_LL_READY; + + if (sc->sc_callback->ucom_cfg_open) { + (sc->sc_callback->ucom_cfg_open) (sc); + + /* wait a little */ + usb_pause_mtx(sc->sc_mtx, hz / 10); + } + } +} + +static int +ucom_open(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + int error; + + mtx_assert(sc->sc_mtx, MA_OWNED); + + if (sc->sc_flag & UCOM_FLAG_GONE) { + return (ENXIO); + } + if (sc->sc_flag & UCOM_FLAG_HL_READY) { + /* already opened */ + return (0); + } + DPRINTF("tp = %p\n", tp); + + if (sc->sc_callback->ucom_pre_open) { + /* + * give the lower layer a chance to disallow TTY open, for + * example if the device is not present: + */ + error = (sc->sc_callback->ucom_pre_open) (sc); + if (error) { + return (error); + } + } + sc->sc_flag |= UCOM_FLAG_HL_READY; + + /* Disable transfers */ + sc->sc_flag &= ~UCOM_FLAG_GP_DATA; + + sc->sc_lsr = 0; + sc->sc_msr = 0; + sc->sc_mcr = 0; + + /* reset programmed line state */ + sc->sc_pls_curr = 0; + sc->sc_pls_set = 0; + sc->sc_pls_clr = 0; + + ucom_queue_command(sc, ucom_cfg_open, NULL, + &sc->sc_open_task[0].hdr, + &sc->sc_open_task[1].hdr); + + /* Queue transfer enable command last */ + ucom_queue_command(sc, ucom_cfg_start_transfers, NULL, + &sc->sc_start_task[0].hdr, + &sc->sc_start_task[1].hdr); + + ucom_modem(tp, SER_DTR | SER_RTS, 0); + + ucom_ring(sc, 0); + + ucom_break(sc, 0); + + ucom_status_change(sc); + + return (0); +} + +static void +ucom_cfg_close(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + + DPRINTF("\n"); + + if (sc->sc_flag & UCOM_FLAG_LL_READY) { + sc->sc_flag &= ~UCOM_FLAG_LL_READY; + if (sc->sc_callback->ucom_cfg_close) + (sc->sc_callback->ucom_cfg_close) (sc); + } else { + /* already closed */ + } +} + +static void +ucom_close(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + + mtx_assert(sc->sc_mtx, MA_OWNED); + + DPRINTF("tp=%p\n", tp); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + DPRINTF("tp=%p already closed\n", tp); + return; + } + ucom_shutdown(sc); + + ucom_queue_command(sc, ucom_cfg_close, NULL, + &sc->sc_close_task[0].hdr, + &sc->sc_close_task[1].hdr); + + sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW); + + if (sc->sc_callback->ucom_stop_read) { + (sc->sc_callback->ucom_stop_read) (sc); + } +} + +static int +ucom_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) +{ + struct ucom_softc *sc = tty_softc(tp); + int error; + + mtx_assert(sc->sc_mtx, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return (EIO); + } + DPRINTF("cmd = 0x%08lx\n", cmd); + + switch (cmd) { +#if 0 + case TIOCSRING: + ucom_ring(sc, 1); + error = 0; + break; + case TIOCCRING: + ucom_ring(sc, 0); + error = 0; + break; +#endif + case TIOCSBRK: + ucom_break(sc, 1); + error = 0; + break; + case TIOCCBRK: + ucom_break(sc, 0); + error = 0; + break; + default: + if (sc->sc_callback->ucom_ioctl) { + error = (sc->sc_callback->ucom_ioctl) + (sc, cmd, data, 0, td); + } else { + error = ENOIOCTL; + } + break; + } + return (error); +} + +static int +ucom_modem(struct tty *tp, int sigon, int sigoff) +{ + struct ucom_softc *sc = tty_softc(tp); + uint8_t onoff; + + mtx_assert(sc->sc_mtx, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return (0); + } + if ((sigon == 0) && (sigoff == 0)) { + + if (sc->sc_mcr & SER_DTR) { + sigon |= SER_DTR; + } + if (sc->sc_mcr & SER_RTS) { + sigon |= SER_RTS; + } + if (sc->sc_msr & SER_CTS) { + sigon |= SER_CTS; + } + if (sc->sc_msr & SER_DCD) { + sigon |= SER_DCD; + } + if (sc->sc_msr & SER_DSR) { + sigon |= SER_DSR; + } + if (sc->sc_msr & SER_RI) { + sigon |= SER_RI; + } + return (sigon); + } + if (sigon & SER_DTR) { + sc->sc_mcr |= SER_DTR; + } + if (sigoff & SER_DTR) { + sc->sc_mcr &= ~SER_DTR; + } + if (sigon & SER_RTS) { + sc->sc_mcr |= SER_RTS; + } + if (sigoff & SER_RTS) { + sc->sc_mcr &= ~SER_RTS; + } + onoff = (sc->sc_mcr & SER_DTR) ? 1 : 0; + ucom_dtr(sc, onoff); + + onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0; + ucom_rts(sc, onoff); + + return (0); +} + +static void +ucom_cfg_line_state(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + uint8_t notch_bits; + uint8_t any_bits; + uint8_t prev_value; + uint8_t last_value; + uint8_t mask; + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + + mask = 0; + /* compute callback mask */ + if (sc->sc_callback->ucom_cfg_set_dtr) + mask |= UCOM_LS_DTR; + if (sc->sc_callback->ucom_cfg_set_rts) + mask |= UCOM_LS_RTS; + if (sc->sc_callback->ucom_cfg_set_break) + mask |= UCOM_LS_BREAK; + if (sc->sc_callback->ucom_cfg_set_ring) + mask |= UCOM_LS_RING; + + /* compute the bits we are to program */ + notch_bits = (sc->sc_pls_set & sc->sc_pls_clr) & mask; + any_bits = (sc->sc_pls_set | sc->sc_pls_clr) & mask; + prev_value = sc->sc_pls_curr ^ notch_bits; + last_value = sc->sc_pls_curr; + + /* reset programmed line state */ + sc->sc_pls_curr = 0; + sc->sc_pls_set = 0; + sc->sc_pls_clr = 0; + + /* ensure that we don't loose any levels */ + if (notch_bits & UCOM_LS_DTR) + sc->sc_callback->ucom_cfg_set_dtr(sc, + (prev_value & UCOM_LS_DTR) ? 1 : 0); + if (notch_bits & UCOM_LS_RTS) + sc->sc_callback->ucom_cfg_set_rts(sc, + (prev_value & UCOM_LS_RTS) ? 1 : 0); + if (notch_bits & UCOM_LS_BREAK) + sc->sc_callback->ucom_cfg_set_break(sc, + (prev_value & UCOM_LS_BREAK) ? 1 : 0); + if (notch_bits & UCOM_LS_RING) + sc->sc_callback->ucom_cfg_set_ring(sc, + (prev_value & UCOM_LS_RING) ? 1 : 0); + + /* set last value */ + if (any_bits & UCOM_LS_DTR) + sc->sc_callback->ucom_cfg_set_dtr(sc, + (last_value & UCOM_LS_DTR) ? 1 : 0); + if (any_bits & UCOM_LS_RTS) + sc->sc_callback->ucom_cfg_set_rts(sc, + (last_value & UCOM_LS_RTS) ? 1 : 0); + if (any_bits & UCOM_LS_BREAK) + sc->sc_callback->ucom_cfg_set_break(sc, + (last_value & UCOM_LS_BREAK) ? 1 : 0); + if (any_bits & UCOM_LS_RING) + sc->sc_callback->ucom_cfg_set_ring(sc, + (last_value & UCOM_LS_RING) ? 1 : 0); +} + +static void +ucom_line_state(struct ucom_softc *sc, + uint8_t set_bits, uint8_t clear_bits) +{ + mtx_assert(sc->sc_mtx, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + + DPRINTF("on=0x%02x, off=0x%02x\n", set_bits, clear_bits); + + /* update current programmed line state */ + sc->sc_pls_curr |= set_bits; + sc->sc_pls_curr &= ~clear_bits; + sc->sc_pls_set |= set_bits; + sc->sc_pls_clr |= clear_bits; + + /* defer driver programming */ + ucom_queue_command(sc, ucom_cfg_line_state, NULL, + &sc->sc_line_state_task[0].hdr, + &sc->sc_line_state_task[1].hdr); +} + +static void +ucom_ring(struct ucom_softc *sc, uint8_t onoff) +{ + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + ucom_line_state(sc, UCOM_LS_RING, 0); + else + ucom_line_state(sc, 0, UCOM_LS_RING); +} + +static void +ucom_break(struct ucom_softc *sc, uint8_t onoff) +{ + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + ucom_line_state(sc, UCOM_LS_BREAK, 0); + else + ucom_line_state(sc, 0, UCOM_LS_BREAK); +} + +static void +ucom_dtr(struct ucom_softc *sc, uint8_t onoff) +{ + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + ucom_line_state(sc, UCOM_LS_DTR, 0); + else + ucom_line_state(sc, 0, UCOM_LS_DTR); +} + +static void +ucom_rts(struct ucom_softc *sc, uint8_t onoff) +{ + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + ucom_line_state(sc, UCOM_LS_RTS, 0); + else + ucom_line_state(sc, 0, UCOM_LS_RTS); +} + +static void +ucom_cfg_status_change(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + struct tty *tp; + uint8_t new_msr; + uint8_t new_lsr; + uint8_t onoff; + uint8_t lsr_delta; + + tp = sc->sc_tty; + + mtx_assert(sc->sc_mtx, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + if (sc->sc_callback->ucom_cfg_get_status == NULL) { + return; + } + /* get status */ + + new_msr = 0; + new_lsr = 0; + + (sc->sc_callback->ucom_cfg_get_status) (sc, &new_lsr, &new_msr); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + /* TTY device closed */ + return; + } + onoff = ((sc->sc_msr ^ new_msr) & SER_DCD); + lsr_delta = (sc->sc_lsr ^ new_lsr); + + sc->sc_msr = new_msr; + sc->sc_lsr = new_lsr; + + if (onoff) { + + onoff = (sc->sc_msr & SER_DCD) ? 1 : 0; + + DPRINTF("DCD changed to %d\n", onoff); + + ttydisc_modem(tp, onoff); + } + + if ((lsr_delta & ULSR_BI) && (sc->sc_lsr & ULSR_BI)) { + + DPRINTF("BREAK detected\n"); + + ttydisc_rint(tp, 0, TRE_BREAK); + ttydisc_rint_done(tp); + } + + if ((lsr_delta & ULSR_FE) && (sc->sc_lsr & ULSR_FE)) { + + DPRINTF("Frame error detected\n"); + + ttydisc_rint(tp, 0, TRE_FRAMING); + ttydisc_rint_done(tp); + } + + if ((lsr_delta & ULSR_PE) && (sc->sc_lsr & ULSR_PE)) { + + DPRINTF("Parity error detected\n"); + + ttydisc_rint(tp, 0, TRE_PARITY); + ttydisc_rint_done(tp); + } +} + +void +ucom_status_change(struct ucom_softc *sc) +{ + mtx_assert(sc->sc_mtx, MA_OWNED); + + if (sc->sc_flag & UCOM_FLAG_CONSOLE) + return; /* not supported */ + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + DPRINTF("\n"); + + ucom_queue_command(sc, ucom_cfg_status_change, NULL, + &sc->sc_status_task[0].hdr, + &sc->sc_status_task[1].hdr); +} + +static void +ucom_cfg_param(struct usb_proc_msg *_task) +{ + struct ucom_param_task *task = + (struct ucom_param_task *)_task; + struct ucom_softc *sc = task->sc; + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + if (sc->sc_callback->ucom_cfg_param == NULL) { + return; + } + + (sc->sc_callback->ucom_cfg_param) (sc, &task->termios_copy); + + /* wait a little */ + usb_pause_mtx(sc->sc_mtx, hz / 10); +} + +static int +ucom_param(struct tty *tp, struct termios *t) +{ + struct ucom_softc *sc = tty_softc(tp); + uint8_t opened; + int error; + + mtx_assert(sc->sc_mtx, MA_OWNED); + + opened = 0; + error = 0; + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + + /* XXX the TTY layer should call "open()" first! */ + + error = ucom_open(tp); + if (error) { + goto done; + } + opened = 1; + } + DPRINTF("sc = %p\n", sc); + + /* Check requested parameters. */ + if (t->c_ospeed < 0) { + DPRINTF("negative ospeed\n"); + error = EINVAL; + goto done; + } + if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) { + DPRINTF("mismatch ispeed and ospeed\n"); + error = EINVAL; + goto done; + } + t->c_ispeed = t->c_ospeed; + + if (sc->sc_callback->ucom_pre_param) { + /* Let the lower layer verify the parameters */ + error = (sc->sc_callback->ucom_pre_param) (sc, t); + if (error) { + DPRINTF("callback error = %d\n", error); + goto done; + } + } + + /* Disable transfers */ + sc->sc_flag &= ~UCOM_FLAG_GP_DATA; + + /* Queue baud rate programming command first */ + ucom_queue_command(sc, ucom_cfg_param, t, + &sc->sc_param_task[0].hdr, + &sc->sc_param_task[1].hdr); + + /* Queue transfer enable command last */ + ucom_queue_command(sc, ucom_cfg_start_transfers, NULL, + &sc->sc_start_task[0].hdr, + &sc->sc_start_task[1].hdr); + + if (t->c_cflag & CRTS_IFLOW) { + sc->sc_flag |= UCOM_FLAG_RTS_IFLOW; + } else if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) { + sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW; + ucom_modem(tp, SER_RTS, 0); + } +done: + if (error) { + if (opened) { + ucom_close(tp); + } + } + return (error); +} + +static void +ucom_outwakeup(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + + mtx_assert(sc->sc_mtx, MA_OWNED); + + DPRINTF("sc = %p\n", sc); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + /* The higher layer is not ready */ + return; + } + ucom_start_transfers(sc); +} + +/*------------------------------------------------------------------------* + * ucom_get_data + * + * Return values: + * 0: No data is available. + * Else: Data is available. + *------------------------------------------------------------------------*/ +uint8_t +ucom_get_data(struct ucom_softc *sc, struct usb_page_cache *pc, + uint32_t offset, uint32_t len, uint32_t *actlen) +{ + struct usb_page_search res; + struct tty *tp = sc->sc_tty; + uint32_t cnt; + uint32_t offset_orig; + + mtx_assert(sc->sc_mtx, MA_OWNED); + + if (sc->sc_flag & UCOM_FLAG_CONSOLE) { + unsigned int temp; + + /* get total TX length */ + + temp = ucom_cons_tx_high - ucom_cons_tx_low; + temp %= UCOM_CONS_BUFSIZE; + + /* limit TX length */ + + if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_tx_low)) + temp = (UCOM_CONS_BUFSIZE - ucom_cons_tx_low); + + if (temp > len) + temp = len; + + /* copy in data */ + + usbd_copy_in(pc, offset, ucom_cons_tx_buf + ucom_cons_tx_low, temp); + + /* update counters */ + + ucom_cons_tx_low += temp; + ucom_cons_tx_low %= UCOM_CONS_BUFSIZE; + + /* store actual length */ + + *actlen = temp; + + return (temp ? 1 : 0); + } + + if (tty_gone(tp) || + !(sc->sc_flag & UCOM_FLAG_GP_DATA)) { + actlen[0] = 0; + return (0); /* multiport device polling */ + } + offset_orig = offset; + + while (len != 0) { + + usbd_get_page(pc, offset, &res); + + if (res.length > len) { + res.length = len; + } + /* copy data directly into USB buffer */ + cnt = ttydisc_getc(tp, res.buffer, res.length); + + offset += cnt; + len -= cnt; + + if (cnt < res.length) { + /* end of buffer */ + break; + } + } + + actlen[0] = offset - offset_orig; + + DPRINTF("cnt=%d\n", actlen[0]); + + if (actlen[0] == 0) { + return (0); + } + return (1); +} + +void +ucom_put_data(struct ucom_softc *sc, struct usb_page_cache *pc, + uint32_t offset, uint32_t len) +{ + struct usb_page_search res; + struct tty *tp = sc->sc_tty; + char *buf; + uint32_t cnt; + + mtx_assert(sc->sc_mtx, MA_OWNED); + + if (sc->sc_flag & UCOM_FLAG_CONSOLE) { + unsigned int temp; + + /* get maximum RX length */ + + temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_rx_high + ucom_cons_rx_low; + temp %= UCOM_CONS_BUFSIZE; + + /* limit RX length */ + + if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_rx_high)) + temp = (UCOM_CONS_BUFSIZE - ucom_cons_rx_high); + + if (temp > len) + temp = len; + + /* copy out data */ + + usbd_copy_out(pc, offset, ucom_cons_rx_buf + ucom_cons_rx_high, temp); + + /* update counters */ + + ucom_cons_rx_high += temp; + ucom_cons_rx_high %= UCOM_CONS_BUFSIZE; + + return; + } + + if (tty_gone(tp)) + return; /* multiport device polling */ + + if (len == 0) + return; /* no data */ + + /* set a flag to prevent recursation ? */ + + while (len > 0) { + + usbd_get_page(pc, offset, &res); + + if (res.length > len) { + res.length = len; + } + len -= res.length; + offset += res.length; + + /* pass characters to tty layer */ + + buf = res.buffer; + cnt = res.length; + + /* first check if we can pass the buffer directly */ + + if (ttydisc_can_bypass(tp)) { + if (ttydisc_rint_bypass(tp, buf, cnt) != cnt) { + DPRINTF("tp=%p, data lost\n", tp); + } + continue; + } + /* need to loop */ + + for (cnt = 0; cnt != res.length; cnt++) { + if (ttydisc_rint(tp, buf[cnt], 0) == -1) { + /* XXX what should we do? */ + + DPRINTF("tp=%p, lost %d " + "chars\n", tp, res.length - cnt); + break; + } + } + } + ttydisc_rint_done(tp); +} + +static void +ucom_free(void *xsc) +{ + struct ucom_softc *sc = xsc; + + mtx_lock(sc->sc_mtx); + sc->sc_ttyfreed = 1; + cv_signal(&sc->sc_cv); + mtx_unlock(sc->sc_mtx); +} + +static cn_probe_t ucom_cnprobe; +static cn_init_t ucom_cninit; +static cn_term_t ucom_cnterm; +static cn_getc_t ucom_cngetc; +static cn_putc_t ucom_cnputc; +static cn_grab_t ucom_cngrab; +static cn_ungrab_t ucom_cnungrab; + +CONSOLE_DRIVER(ucom); + +static void +ucom_cnprobe(struct consdev *cp) +{ + if (ucom_cons_unit != -1) + cp->cn_pri = CN_NORMAL; + else + cp->cn_pri = CN_DEAD; + + strlcpy(cp->cn_name, "ucom", sizeof(cp->cn_name)); +} + +static void +ucom_cninit(struct consdev *cp) +{ +} + +static void +ucom_cnterm(struct consdev *cp) +{ +} + +static void +ucom_cngrab(struct consdev *cp) +{ +} + +static void +ucom_cnungrab(struct consdev *cp) +{ +} + +static int +ucom_cngetc(struct consdev *cd) +{ + struct ucom_softc *sc = ucom_cons_softc; + int c; + + if (sc == NULL) + return (-1); + + mtx_lock(sc->sc_mtx); + + if (ucom_cons_rx_low != ucom_cons_rx_high) { + c = ucom_cons_rx_buf[ucom_cons_rx_low]; + ucom_cons_rx_low ++; + ucom_cons_rx_low %= UCOM_CONS_BUFSIZE; + } else { + c = -1; + } + + /* start USB transfers */ + ucom_outwakeup(sc->sc_tty); + + mtx_unlock(sc->sc_mtx); + + /* poll if necessary */ + if (kdb_active && sc->sc_callback->ucom_poll) + (sc->sc_callback->ucom_poll) (sc); + + return (c); +} + +static void +ucom_cnputc(struct consdev *cd, int c) +{ + struct ucom_softc *sc = ucom_cons_softc; + unsigned int temp; + + if (sc == NULL) + return; + + repeat: + + mtx_lock(sc->sc_mtx); + + /* compute maximum TX length */ + + temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_tx_high + ucom_cons_tx_low; + temp %= UCOM_CONS_BUFSIZE; + + if (temp) { + ucom_cons_tx_buf[ucom_cons_tx_high] = c; + ucom_cons_tx_high ++; + ucom_cons_tx_high %= UCOM_CONS_BUFSIZE; + } + + /* start USB transfers */ + ucom_outwakeup(sc->sc_tty); + + mtx_unlock(sc->sc_mtx); + + /* poll if necessary */ + if (kdb_active && sc->sc_callback->ucom_poll) { + (sc->sc_callback->ucom_poll) (sc); + /* simple flow control */ + if (temp == 0) + goto repeat; + } +} + +#if defined(GDB) + +#include + +static gdb_probe_f ucom_gdbprobe; +static gdb_init_f ucom_gdbinit; +static gdb_term_f ucom_gdbterm; +static gdb_getc_f ucom_gdbgetc; +static gdb_putc_f ucom_gdbputc; + +GDB_DBGPORT(sio, ucom_gdbprobe, ucom_gdbinit, ucom_gdbterm, ucom_gdbgetc, ucom_gdbputc); + +static int +ucom_gdbprobe(void) +{ + return ((ucom_cons_softc != NULL) ? 0 : -1); +} + +static void +ucom_gdbinit(void) +{ +} + +static void +ucom_gdbterm(void) +{ +} + +static void +ucom_gdbputc(int c) +{ + ucom_cnputc(NULL, c); +} + +static int +ucom_gdbgetc(void) +{ + return (ucom_cngetc(NULL)); +} + +#endif diff --git a/sys/bus/u4b/serial/usb_serial.h b/sys/bus/u4b/serial/usb_serial.h new file mode 100644 index 0000000000..ffd28597f0 --- /dev/null +++ b/sys/bus/u4b/serial/usb_serial.h @@ -0,0 +1,207 @@ +/* $NetBSD: ucomvar.h,v 1.9 2001/01/23 21:56:17 augustss Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2001-2002, Shunsuke Akiyama . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _USB_SERIAL_H_ +#define _USB_SERIAL_H_ + +#include +#include +#include +#include + +/* Module interface related macros */ +#define UCOM_MODVER 1 + +#define UCOM_MINVER 1 +#define UCOM_PREFVER UCOM_MODVER +#define UCOM_MAXVER 1 + +struct usb_device; +struct ucom_softc; +struct usb_device_request; +struct thread; + +/* + * NOTE: There is no guarantee that "ucom_cfg_close()" will + * be called after "ucom_cfg_open()" if the device is detached + * while it is open! + */ +struct ucom_callback { + void (*ucom_cfg_get_status) (struct ucom_softc *, uint8_t *plsr, uint8_t *pmsr); + void (*ucom_cfg_set_dtr) (struct ucom_softc *, uint8_t); + void (*ucom_cfg_set_rts) (struct ucom_softc *, uint8_t); + void (*ucom_cfg_set_break) (struct ucom_softc *, uint8_t); + void (*ucom_cfg_set_ring) (struct ucom_softc *, uint8_t); + void (*ucom_cfg_param) (struct ucom_softc *, struct termios *); + void (*ucom_cfg_open) (struct ucom_softc *); + void (*ucom_cfg_close) (struct ucom_softc *); + int (*ucom_pre_open) (struct ucom_softc *); + int (*ucom_pre_param) (struct ucom_softc *, struct termios *); + int (*ucom_ioctl) (struct ucom_softc *, uint32_t, caddr_t, int, struct thread *); + void (*ucom_start_read) (struct ucom_softc *); + void (*ucom_stop_read) (struct ucom_softc *); + void (*ucom_start_write) (struct ucom_softc *); + void (*ucom_stop_write) (struct ucom_softc *); + void (*ucom_tty_name) (struct ucom_softc *, char *pbuf, uint16_t buflen, uint16_t unit, uint16_t subunit); + void (*ucom_poll) (struct ucom_softc *); +}; + +/* Line status register */ +#define ULSR_RCV_FIFO 0x80 +#define ULSR_TSRE 0x40 /* Transmitter empty: byte sent */ +#define ULSR_TXRDY 0x20 /* Transmitter buffer empty */ +#define ULSR_BI 0x10 /* Break detected */ +#define ULSR_FE 0x08 /* Framing error: bad stop bit */ +#define ULSR_PE 0x04 /* Parity error */ +#define ULSR_OE 0x02 /* Overrun, lost incoming byte */ +#define ULSR_RXRDY 0x01 /* Byte ready in Receive Buffer */ +#define ULSR_RCV_MASK 0x1f /* Mask for incoming data or error */ + +struct ucom_cfg_task { + struct usb_proc_msg hdr; + struct ucom_softc *sc; +}; + +struct ucom_param_task { + struct usb_proc_msg hdr; + struct ucom_softc *sc; + struct termios termios_copy; +}; + +struct ucom_super_softc { + struct usb_process sc_tq; + int sc_unit; + int sc_subunits; + struct sysctl_oid *sc_sysctl_ttyname; + struct sysctl_oid *sc_sysctl_ttyports; + char sc_ttyname[16]; +}; + +struct ucom_softc { + /* + * NOTE: To avoid loosing level change information we use two + * tasks instead of one for all commands. + * + * Level changes are transitions like: + * + * ON->OFF + * OFF->ON + * OPEN->CLOSE + * CLOSE->OPEN + */ + struct ucom_cfg_task sc_start_task[2]; + struct ucom_cfg_task sc_open_task[2]; + struct ucom_cfg_task sc_close_task[2]; + struct ucom_cfg_task sc_line_state_task[2]; + struct ucom_cfg_task sc_status_task[2]; + struct ucom_param_task sc_param_task[2]; + struct cv sc_cv; + /* Used to set "UCOM_FLAG_GP_DATA" flag: */ + struct usb_proc_msg *sc_last_start_xfer; + const struct ucom_callback *sc_callback; + struct ucom_super_softc *sc_super; + struct tty *sc_tty; + struct mtx *sc_mtx; + void *sc_parent; + uint32_t sc_subunit; + uint16_t sc_portno; + uint16_t sc_flag; +#define UCOM_FLAG_RTS_IFLOW 0x01 /* use RTS input flow control */ +#define UCOM_FLAG_GONE 0x02 /* the device is gone */ +#define UCOM_FLAG_ATTACHED 0x04 /* set if attached */ +#define UCOM_FLAG_GP_DATA 0x08 /* set if get and put data is possible */ +#define UCOM_FLAG_LL_READY 0x20 /* set if low layer is ready */ +#define UCOM_FLAG_HL_READY 0x40 /* set if high layer is ready */ +#define UCOM_FLAG_CONSOLE 0x80 /* set if device is a console */ + uint8_t sc_lsr; + uint8_t sc_msr; + uint8_t sc_mcr; + uint8_t sc_ttyfreed; /* set when TTY has been freed */ + /* programmed line state bits */ + uint8_t sc_pls_set; /* set bits */ + uint8_t sc_pls_clr; /* cleared bits */ + uint8_t sc_pls_curr; /* last state */ +#define UCOM_LS_DTR 0x01 +#define UCOM_LS_RTS 0x02 +#define UCOM_LS_BREAK 0x04 +#define UCOM_LS_RING 0x08 +}; + +#define ucom_cfg_do_request(udev,com,req,ptr,flags,timo) \ + usbd_do_request_proc(udev,&(com)->sc_super->sc_tq,req,ptr,flags,NULL,timo) + +int ucom_attach(struct ucom_super_softc *, + struct ucom_softc *, uint32_t, void *, + const struct ucom_callback *callback, struct mtx *); +void ucom_detach(struct ucom_super_softc *, struct ucom_softc *); +void ucom_set_pnpinfo_usb(struct ucom_super_softc *, device_t); +void ucom_status_change(struct ucom_softc *); +uint8_t ucom_get_data(struct ucom_softc *, struct usb_page_cache *, + uint32_t, uint32_t, uint32_t *); +void ucom_put_data(struct ucom_softc *, struct usb_page_cache *, + uint32_t, uint32_t); +uint8_t ucom_cfg_is_gone(struct ucom_softc *); +#endif /* _USB_SERIAL_H_ */ diff --git a/sys/bus/u4b/serial/uslcom.c b/sys/bus/u4b/serial/uslcom.c new file mode 100644 index 0000000000..848f2d5ed3 --- /dev/null +++ b/sys/bus/u4b/serial/uslcom.c @@ -0,0 +1,823 @@ +/* $OpenBSD: uslcom.c,v 1.17 2007/11/24 10:52:12 jsg Exp $ */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Copyright (c) 2006 Jonathan Gray + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR uslcom_debug +#include +#include + +#include + +#ifdef USB_DEBUG +static int uslcom_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uslcom, CTLFLAG_RW, 0, "USB uslcom"); +SYSCTL_INT(_hw_usb_uslcom, OID_AUTO, debug, CTLFLAG_RW, + &uslcom_debug, 0, "Debug level"); +#endif + +#define USLCOM_BULK_BUF_SIZE 1024 +#define USLCOM_CONFIG_INDEX 0 +#define USLCOM_IFACE_INDEX 0 + +#define USLCOM_SET_DATA_BITS(x) ((x) << 8) + +/* Request types */ +#define USLCOM_WRITE 0x41 +#define USLCOM_READ 0xc1 + +/* Request codes */ +#define USLCOM_UART 0x00 +#define USLCOM_BAUD_RATE 0x01 +#define USLCOM_DATA 0x03 +#define USLCOM_BREAK 0x05 +#define USLCOM_CTRL 0x07 +#define USLCOM_RCTRL 0x08 +#define USLCOM_SET_FLOWCTRL 0x13 +#define USLCOM_VENDOR_SPECIFIC 0xff + +/* USLCOM_UART values */ +#define USLCOM_UART_DISABLE 0x00 +#define USLCOM_UART_ENABLE 0x01 + +/* USLCOM_CTRL/USLCOM_RCTRL values */ +#define USLCOM_CTRL_DTR_ON 0x0001 +#define USLCOM_CTRL_DTR_SET 0x0100 +#define USLCOM_CTRL_RTS_ON 0x0002 +#define USLCOM_CTRL_RTS_SET 0x0200 +#define USLCOM_CTRL_CTS 0x0010 +#define USLCOM_CTRL_DSR 0x0020 +#define USLCOM_CTRL_RI 0x0040 +#define USLCOM_CTRL_DCD 0x0080 + +/* USLCOM_BAUD_RATE values */ +#define USLCOM_BAUD_REF 0x384000 + +/* USLCOM_DATA values */ +#define USLCOM_STOP_BITS_1 0x00 +#define USLCOM_STOP_BITS_2 0x02 +#define USLCOM_PARITY_NONE 0x00 +#define USLCOM_PARITY_ODD 0x10 +#define USLCOM_PARITY_EVEN 0x20 + +#define USLCOM_PORT_NO 0x0000 + +/* USLCOM_BREAK values */ +#define USLCOM_BREAK_OFF 0x00 +#define USLCOM_BREAK_ON 0x01 + +/* USLCOM_SET_FLOWCTRL values - 1st word */ +#define USLCOM_FLOW_DTR_ON 0x00000001 /* DTR static active */ +#define USLCOM_FLOW_CTS_HS 0x00000008 /* CTS handshake */ +/* USLCOM_SET_FLOWCTRL values - 2nd word */ +#define USLCOM_FLOW_RTS_ON 0x00000040 /* RTS static active */ +#define USLCOM_FLOW_RTS_HS 0x00000080 /* RTS handshake */ + +/* USLCOM_VENDOR_SPECIFIC values */ +#define USLCOM_WRITE_LATCH 0x37E1 +#define USLCOM_READ_LATCH 0x00C2 + +enum { + USLCOM_BULK_DT_WR, + USLCOM_BULK_DT_RD, + USLCOM_CTRL_DT_RD, + USLCOM_N_TRANSFER, +}; + +struct uslcom_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + struct usb_callout sc_watchdog; + + struct usb_xfer *sc_xfer[USLCOM_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint8_t sc_msr; + uint8_t sc_lsr; +}; + +static device_probe_t uslcom_probe; +static device_attach_t uslcom_attach; +static device_detach_t uslcom_detach; + +static usb_callback_t uslcom_write_callback; +static usb_callback_t uslcom_read_callback; +static usb_callback_t uslcom_control_callback; + +static void uslcom_open(struct ucom_softc *); +static void uslcom_close(struct ucom_softc *); +static void uslcom_set_dtr(struct ucom_softc *, uint8_t); +static void uslcom_set_rts(struct ucom_softc *, uint8_t); +static void uslcom_set_break(struct ucom_softc *, uint8_t); +static int uslcom_ioctl(struct ucom_softc *, uint32_t, caddr_t, int, + struct thread *); +static int uslcom_pre_param(struct ucom_softc *, struct termios *); +static void uslcom_param(struct ucom_softc *, struct termios *); +static void uslcom_get_status(struct ucom_softc *, uint8_t *, uint8_t *); +static void uslcom_start_read(struct ucom_softc *); +static void uslcom_stop_read(struct ucom_softc *); +static void uslcom_start_write(struct ucom_softc *); +static void uslcom_stop_write(struct ucom_softc *); +static void uslcom_poll(struct ucom_softc *ucom); + +static const struct usb_config uslcom_config[USLCOM_N_TRANSFER] = { + + [USLCOM_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = USLCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,}, + .callback = &uslcom_write_callback, + }, + + [USLCOM_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = USLCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uslcom_read_callback, + }, + [USLCOM_CTRL_DT_RD] = { + .type = UE_CONTROL, + .endpoint = 0x00, + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request) + 8, + .flags = {.pipe_bof = 1,}, + .callback = &uslcom_control_callback, + .timeout = 1000, /* 1 second timeout */ + }, +}; + +static struct ucom_callback uslcom_callback = { + .ucom_cfg_open = &uslcom_open, + .ucom_cfg_close = &uslcom_close, + .ucom_cfg_get_status = &uslcom_get_status, + .ucom_cfg_set_dtr = &uslcom_set_dtr, + .ucom_cfg_set_rts = &uslcom_set_rts, + .ucom_cfg_set_break = &uslcom_set_break, + .ucom_ioctl = &uslcom_ioctl, + .ucom_cfg_param = &uslcom_param, + .ucom_pre_param = &uslcom_pre_param, + .ucom_start_read = &uslcom_start_read, + .ucom_stop_read = &uslcom_stop_read, + .ucom_start_write = &uslcom_start_write, + .ucom_stop_write = &uslcom_stop_write, + .ucom_poll = &uslcom_poll, +}; + +static const STRUCT_USB_HOST_ID uslcom_devs[] = { +#define USLCOM_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } + USLCOM_DEV(BALTECH, CARDREADER), + USLCOM_DEV(CLIPSAL, 5500PCU), + USLCOM_DEV(DATAAPEX, MULTICOM), + USLCOM_DEV(DELL, DW700), + USLCOM_DEV(DIGIANSWER, ZIGBEE802154), + USLCOM_DEV(DYNASTREAM, ANTDEVBOARD), + USLCOM_DEV(DYNASTREAM, ANTDEVBOARD2), + USLCOM_DEV(DYNASTREAM, ANT2USB), + USLCOM_DEV(ELV, USBI2C), + USLCOM_DEV(FOXCONN, PIRELLI_DP_L10), + USLCOM_DEV(FOXCONN, TCOM_TC_300), + USLCOM_DEV(GEMALTO, PROXPU), + USLCOM_DEV(JABLOTRON, PC60B), + USLCOM_DEV(MEI, CASHFLOW_SC), + USLCOM_DEV(MEI, S2000), + USLCOM_DEV(JABLOTRON, PC60B), + USLCOM_DEV(OWEN, AC4), + USLCOM_DEV(PHILIPS, ACE1001), + USLCOM_DEV(PLX, CA42), + USLCOM_DEV(RENESAS, RX610), + USLCOM_DEV(SILABS, AEROCOMM), + USLCOM_DEV(SILABS, AMBER_AMB2560), + USLCOM_DEV(SILABS, ARGUSISP), + USLCOM_DEV(SILABS, ARKHAM_DS101_A), + USLCOM_DEV(SILABS, ARKHAM_DS101_M), + USLCOM_DEV(SILABS, ARYGON_MIFARE), + USLCOM_DEV(SILABS, AVIT_USB_TTL), + USLCOM_DEV(SILABS, B_G_H3000), + USLCOM_DEV(SILABS, BALLUFF_RFID), + USLCOM_DEV(SILABS, BEI_VCP), + USLCOM_DEV(SILABS, BSM7DUSB), + USLCOM_DEV(SILABS, BURNSIDE), + USLCOM_DEV(SILABS, C2_EDGE_MODEM), + USLCOM_DEV(SILABS, CP2102), + USLCOM_DEV(SILABS, CP210X_2), + USLCOM_DEV(SILABS, CRUMB128), + USLCOM_DEV(SILABS, CYGNAL), + USLCOM_DEV(SILABS, CYGNAL_DEBUG), + USLCOM_DEV(SILABS, CYGNAL_GPS), + USLCOM_DEV(SILABS, DEGREE), + USLCOM_DEV(SILABS, EMS_C1007), + USLCOM_DEV(SILABS, HELICOM), + USLCOM_DEV(SILABS, IMS_USB_RS422), + USLCOM_DEV(SILABS, INFINITY_MIC), + USLCOM_DEV(SILABS, INSYS_MODEM), + USLCOM_DEV(SILABS, KYOCERA_GPS), + USLCOM_DEV(SILABS, LIPOWSKY_HARP), + USLCOM_DEV(SILABS, LIPOWSKY_JTAG), + USLCOM_DEV(SILABS, LIPOWSKY_LIN), + USLCOM_DEV(SILABS, MC35PU), + USLCOM_DEV(SILABS, MJS_TOSLINK), + USLCOM_DEV(SILABS, MSD_DASHHAWK), + USLCOM_DEV(SILABS, POLOLU), + USLCOM_DEV(SILABS, PROCYON_AVS), + USLCOM_DEV(SILABS, SB_PARAMOUNT_ME), + USLCOM_DEV(SILABS, SUUNTO), + USLCOM_DEV(SILABS, TAMSMASTER), + USLCOM_DEV(SILABS, TELEGESYS_ETRX2), + USLCOM_DEV(SILABS, TRACIENT), + USLCOM_DEV(SILABS, TRAQMATE), + USLCOM_DEV(SILABS, USBCOUNT50), + USLCOM_DEV(SILABS, USBPULSE100), + USLCOM_DEV(SILABS, USBSCOPE50), + USLCOM_DEV(SILABS, USBWAVE12), + USLCOM_DEV(SILABS, VSTABI), + USLCOM_DEV(SILABS, WAVIT), + USLCOM_DEV(SILABS, WMRBATT), + USLCOM_DEV(SILABS, WMRRIGBLASTER), + USLCOM_DEV(SILABS, WMRRIGTALK), + USLCOM_DEV(SILABS, ZEPHYR_BIO), + USLCOM_DEV(SILABS2, DCU11CLONE), + USLCOM_DEV(SILABS3, GPRS_MODEM), + USLCOM_DEV(SILABS4, 100EU_MODEM), + USLCOM_DEV(SYNTECH, CYPHERLAB100), + USLCOM_DEV(USI, MC60), + USLCOM_DEV(VAISALA, CABLE), + USLCOM_DEV(WAGO, SERVICECABLE), + USLCOM_DEV(WAVESENSE, JAZZ), + USLCOM_DEV(WIENERPLEINBAUS, PL512), + USLCOM_DEV(WIENERPLEINBAUS, RCM), + USLCOM_DEV(WIENERPLEINBAUS, MPOD), + USLCOM_DEV(WIENERPLEINBAUS, CML), +#undef USLCOM_DEV +}; + +static device_method_t uslcom_methods[] = { + DEVMETHOD(device_probe, uslcom_probe), + DEVMETHOD(device_attach, uslcom_attach), + DEVMETHOD(device_detach, uslcom_detach), + {0, 0} +}; + +static devclass_t uslcom_devclass; + +static driver_t uslcom_driver = { + .name = "uslcom", + .methods = uslcom_methods, + .size = sizeof(struct uslcom_softc), +}; + +DRIVER_MODULE(uslcom, uhub, uslcom_driver, uslcom_devclass, NULL, 0); +MODULE_DEPEND(uslcom, ucom, 1, 1, 1); +MODULE_DEPEND(uslcom, usb, 1, 1, 1); +MODULE_VERSION(uslcom, 1); + +static void +uslcom_watchdog(void *arg) +{ + struct uslcom_softc *sc = arg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + usbd_transfer_start(sc->sc_xfer[USLCOM_CTRL_DT_RD]); + + usb_callout_reset(&sc->sc_watchdog, + hz / 4, &uslcom_watchdog, sc); +} + +static int +uslcom_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + DPRINTFN(11, "\n"); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != USLCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != USLCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uslcom_devs, sizeof(uslcom_devs), uaa)); +} + +static int +uslcom_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uslcom_softc *sc = device_get_softc(dev); + int error; + + DPRINTFN(11, "\n"); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uslcom", NULL, MTX_DEF); + usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0); + + sc->sc_udev = uaa->device; + + error = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, uslcom_config, + USLCOM_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + DPRINTF("one or more missing USB endpoints, " + "error=%s\n", usbd_errstr(error)); + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[USLCOM_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[USLCOM_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uslcom_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + uslcom_detach(dev); + return (ENXIO); +} + +static int +uslcom_detach(device_t dev) +{ + struct uslcom_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, USLCOM_N_TRANSFER); + + usb_callout_drain(&sc->sc_watchdog); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +uslcom_open(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_UART; + USETW(req.wValue, USLCOM_UART_ENABLE); + USETW(req.wIndex, USLCOM_PORT_NO); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("UART enable failed (ignored)\n"); + } + + /* start polling status */ + uslcom_watchdog(sc); +} + +static void +uslcom_close(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + /* stop polling status */ + usb_callout_stop(&sc->sc_watchdog); + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_UART; + USETW(req.wValue, USLCOM_UART_DISABLE); + USETW(req.wIndex, USLCOM_PORT_NO); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("UART disable failed (ignored)\n"); + } +} + +static void +uslcom_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t ctl; + + DPRINTF("onoff = %d\n", onoff); + + ctl = onoff ? USLCOM_CTRL_DTR_ON : 0; + ctl |= USLCOM_CTRL_DTR_SET; + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_CTRL; + USETW(req.wValue, ctl); + USETW(req.wIndex, USLCOM_PORT_NO); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("Setting DTR failed (ignored)\n"); + } +} + +static void +uslcom_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t ctl; + + DPRINTF("onoff = %d\n", onoff); + + ctl = onoff ? USLCOM_CTRL_RTS_ON : 0; + ctl |= USLCOM_CTRL_RTS_SET; + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_CTRL; + USETW(req.wValue, ctl); + USETW(req.wIndex, USLCOM_PORT_NO); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("Setting DTR failed (ignored)\n"); + } +} + +static int +uslcom_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + if (t->c_ospeed <= 0 || t->c_ospeed > 921600) + return (EINVAL); + return (0); +} + +static void +uslcom_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint32_t flowctrl[4]; + uint16_t data; + + DPRINTF("\n"); + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_BAUD_RATE; + USETW(req.wValue, USLCOM_BAUD_REF / t->c_ospeed); + USETW(req.wIndex, USLCOM_PORT_NO); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("Set baudrate failed (ignored)\n"); + } + + if (t->c_cflag & CSTOPB) + data = USLCOM_STOP_BITS_2; + else + data = USLCOM_STOP_BITS_1; + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) + data |= USLCOM_PARITY_ODD; + else + data |= USLCOM_PARITY_EVEN; + } else + data |= USLCOM_PARITY_NONE; + switch (t->c_cflag & CSIZE) { + case CS5: + data |= USLCOM_SET_DATA_BITS(5); + break; + case CS6: + data |= USLCOM_SET_DATA_BITS(6); + break; + case CS7: + data |= USLCOM_SET_DATA_BITS(7); + break; + case CS8: + data |= USLCOM_SET_DATA_BITS(8); + break; + } + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_DATA; + USETW(req.wValue, data); + USETW(req.wIndex, USLCOM_PORT_NO); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("Set format failed (ignored)\n"); + } + + if (t->c_cflag & CRTSCTS) { + flowctrl[0] = htole32(USLCOM_FLOW_DTR_ON | USLCOM_FLOW_CTS_HS); + flowctrl[1] = htole32(USLCOM_FLOW_RTS_HS); + flowctrl[2] = 0; + flowctrl[3] = 0; + } else { + flowctrl[0] = htole32(USLCOM_FLOW_DTR_ON); + flowctrl[1] = htole32(USLCOM_FLOW_RTS_ON); + flowctrl[2] = 0; + flowctrl[3] = 0; + } + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_SET_FLOWCTRL; + USETW(req.wValue, 0); + USETW(req.wIndex, USLCOM_PORT_NO); + USETW(req.wLength, sizeof(flowctrl)); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, flowctrl, 0, 1000)) { + DPRINTF("Set flowcontrol failed (ignored)\n"); + } +} + +static void +uslcom_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uslcom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +uslcom_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t brk = onoff ? USLCOM_BREAK_ON : USLCOM_BREAK_OFF; + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_BREAK; + USETW(req.wValue, brk); + USETW(req.wIndex, USLCOM_PORT_NO); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("Set BREAK failed (ignored)\n"); + } +} + +static int +uslcom_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data, + int flag, struct thread *td) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + int error = 0; + uint8_t latch; + + DPRINTF("cmd=0x%08x\n", cmd); + + switch (cmd) { + case USB_GET_GPIO: + req.bmRequestType = USLCOM_READ; + req.bRequest = USLCOM_VENDOR_SPECIFIC; + USETW(req.wValue, USLCOM_READ_LATCH); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(latch)); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, &latch, 0, 1000)) { + DPRINTF("Get LATCH failed\n"); + error = EIO; + } + *(int *)data = latch; + break; + + case USB_SET_GPIO: + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_VENDOR_SPECIFIC; + USETW(req.wValue, USLCOM_WRITE_LATCH); + USETW(req.wIndex, (*(int *)data)); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("Set LATCH failed\n"); + error = EIO; + } + break; + + default: + DPRINTF("Unknown IOCTL\n"); + error = ENOIOCTL; + break; + } + return (error); +} + +static void +uslcom_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uslcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + USLCOM_BULK_BUF_SIZE, &actlen)) { + + DPRINTF("actlen = %d\n", actlen); + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uslcom_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uslcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uslcom_control_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uslcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + struct usb_device_request req; + uint8_t msr = 0; + uint8_t buf; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 1); + usbd_copy_out(pc, 0, &buf, sizeof(buf)); + if (buf & USLCOM_CTRL_CTS) + msr |= SER_CTS; + if (buf & USLCOM_CTRL_DSR) + msr |= SER_DSR; + if (buf & USLCOM_CTRL_RI) + msr |= SER_RI; + if (buf & USLCOM_CTRL_DCD) + msr |= SER_DCD; + + if (msr != sc->sc_msr) { + DPRINTF("status change msr=0x%02x " + "(was 0x%02x)\n", msr, sc->sc_msr); + sc->sc_msr = msr; + ucom_status_change(&sc->sc_ucom); + } + break; + + case USB_ST_SETUP: + req.bmRequestType = USLCOM_READ; + req.bRequest = USLCOM_RCTRL; + USETW(req.wValue, 0); + USETW(req.wIndex, USLCOM_PORT_NO); + USETW(req.wLength, sizeof(buf)); + + usbd_xfer_set_frames(xfer, 2); + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, sizeof(buf)); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + usbd_transfer_submit(xfer); + break; + + default: /* error */ + if (error != USB_ERR_CANCELLED) + DPRINTF("error=%s\n", usbd_errstr(error)); + break; + } +} + +static void +uslcom_start_read(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[USLCOM_BULK_DT_RD]); +} + +static void +uslcom_stop_read(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[USLCOM_BULK_DT_RD]); +} + +static void +uslcom_start_write(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[USLCOM_BULK_DT_WR]); +} + +static void +uslcom_stop_write(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[USLCOM_BULK_DT_WR]); +} + +static void +uslcom_poll(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, USLCOM_N_TRANSFER); +} diff --git a/sys/bus/u4b/serial/uvisor.c b/sys/bus/u4b/serial/uvisor.c new file mode 100644 index 0000000000..df96958c61 --- /dev/null +++ b/sys/bus/u4b/serial/uvisor.c @@ -0,0 +1,652 @@ +/* $NetBSD: uvisor.c,v 1.9 2001/01/23 14:04:14 augustss Exp $ */ +/* $FreeBSD$ */ + +/* Also already merged from NetBSD: + * $NetBSD: uvisor.c,v 1.12 2001/11/13 06:24:57 lukem Exp $ + * $NetBSD: uvisor.c,v 1.13 2002/02/11 15:11:49 augustss Exp $ + * $NetBSD: uvisor.c,v 1.14 2002/02/27 23:00:03 augustss Exp $ + * $NetBSD: uvisor.c,v 1.15 2002/06/16 15:01:31 augustss Exp $ + * $NetBSD: uvisor.c,v 1.16 2002/07/11 21:14:36 augustss Exp $ + * $NetBSD: uvisor.c,v 1.17 2002/08/13 11:38:15 augustss Exp $ + * $NetBSD: uvisor.c,v 1.18 2003/02/05 00:50:14 augustss Exp $ + * $NetBSD: uvisor.c,v 1.19 2003/02/07 18:12:37 augustss Exp $ + * $NetBSD: uvisor.c,v 1.20 2003/04/11 01:30:10 simonb Exp $ + */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Handspring Visor (Palmpilot compatible PDA) driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR uvisor_debug +#include +#include + +#include + +#ifdef USB_DEBUG +static int uvisor_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uvisor, CTLFLAG_RW, 0, "USB uvisor"); +SYSCTL_INT(_hw_usb_uvisor, OID_AUTO, debug, CTLFLAG_RW, + &uvisor_debug, 0, "Debug level"); +#endif + +#define UVISOR_CONFIG_INDEX 0 +#define UVISOR_IFACE_INDEX 0 + +/* + * The following buffer sizes are hardcoded due to the way the Palm + * firmware works. It looks like the device is not short terminating + * the data transferred. + */ +#define UVISORIBUFSIZE 0 /* Use wMaxPacketSize */ +#define UVISOROBUFSIZE 32 /* bytes */ +#define UVISOROFRAMES 32 /* units */ + +/* From the Linux driver */ +/* + * UVISOR_REQUEST_BYTES_AVAILABLE asks the visor for the number of bytes that + * are available to be transfered to the host for the specified endpoint. + * Currently this is not used, and always returns 0x0001 + */ +#define UVISOR_REQUEST_BYTES_AVAILABLE 0x01 + +/* + * UVISOR_CLOSE_NOTIFICATION is set to the device to notify it that the host + * is now closing the pipe. An empty packet is sent in response. + */ +#define UVISOR_CLOSE_NOTIFICATION 0x02 + +/* + * UVISOR_GET_CONNECTION_INFORMATION is sent by the host during enumeration to + * get the endpoints used by the connection. + */ +#define UVISOR_GET_CONNECTION_INFORMATION 0x03 + +/* + * UVISOR_GET_CONNECTION_INFORMATION returns data in the following format + */ +#define UVISOR_MAX_CONN 8 +struct uvisor_connection_info { + uWord num_ports; + struct { + uByte port_function_id; + uByte port; + } __packed connections[UVISOR_MAX_CONN]; +} __packed; + +#define UVISOR_CONNECTION_INFO_SIZE 18 + +/* struct uvisor_connection_info.connection[x].port defines: */ +#define UVISOR_ENDPOINT_1 0x01 +#define UVISOR_ENDPOINT_2 0x02 + +/* struct uvisor_connection_info.connection[x].port_function_id defines: */ +#define UVISOR_FUNCTION_GENERIC 0x00 +#define UVISOR_FUNCTION_DEBUGGER 0x01 +#define UVISOR_FUNCTION_HOTSYNC 0x02 +#define UVISOR_FUNCTION_CONSOLE 0x03 +#define UVISOR_FUNCTION_REMOTE_FILE_SYS 0x04 + +/* + * Unknown PalmOS stuff. + */ +#define UVISOR_GET_PALM_INFORMATION 0x04 +#define UVISOR_GET_PALM_INFORMATION_LEN 0x44 + +struct uvisor_palm_connection_info { + uByte num_ports; + uByte endpoint_numbers_different; + uWord reserved1; + struct { + uDWord port_function_id; + uByte port; + uByte end_point_info; + uWord reserved; + } __packed connections[UVISOR_MAX_CONN]; +} __packed; + +enum { + UVISOR_BULK_DT_WR, + UVISOR_BULK_DT_RD, + UVISOR_N_TRANSFER, +}; + +struct uvisor_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UVISOR_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint16_t sc_flag; +#define UVISOR_FLAG_PALM4 0x0001 +#define UVISOR_FLAG_VISOR 0x0002 +#define UVISOR_FLAG_PALM35 0x0004 +#define UVISOR_FLAG_SEND_NOTIFY 0x0008 + + uint8_t sc_iface_no; + uint8_t sc_iface_index; +}; + +/* prototypes */ + +static device_probe_t uvisor_probe; +static device_attach_t uvisor_attach; +static device_detach_t uvisor_detach; + +static usb_callback_t uvisor_write_callback; +static usb_callback_t uvisor_read_callback; + +static usb_error_t uvisor_init(struct uvisor_softc *, struct usb_device *, + struct usb_config *); +static void uvisor_cfg_open(struct ucom_softc *); +static void uvisor_cfg_close(struct ucom_softc *); +static void uvisor_start_read(struct ucom_softc *); +static void uvisor_stop_read(struct ucom_softc *); +static void uvisor_start_write(struct ucom_softc *); +static void uvisor_stop_write(struct ucom_softc *); + +static const struct usb_config uvisor_config[UVISOR_N_TRANSFER] = { + + [UVISOR_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UVISOROBUFSIZE * UVISOROFRAMES, + .frames = UVISOROFRAMES, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &uvisor_write_callback, + }, + + [UVISOR_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UVISORIBUFSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uvisor_read_callback, + }, +}; + +static const struct ucom_callback uvisor_callback = { + .ucom_cfg_open = &uvisor_cfg_open, + .ucom_cfg_close = &uvisor_cfg_close, + .ucom_start_read = &uvisor_start_read, + .ucom_stop_read = &uvisor_stop_read, + .ucom_start_write = &uvisor_start_write, + .ucom_stop_write = &uvisor_stop_write, +}; + +static device_method_t uvisor_methods[] = { + DEVMETHOD(device_probe, uvisor_probe), + DEVMETHOD(device_attach, uvisor_attach), + DEVMETHOD(device_detach, uvisor_detach), + {0, 0} +}; + +static devclass_t uvisor_devclass; + +static driver_t uvisor_driver = { + .name = "uvisor", + .methods = uvisor_methods, + .size = sizeof(struct uvisor_softc), +}; + +DRIVER_MODULE(uvisor, uhub, uvisor_driver, uvisor_devclass, NULL, 0); +MODULE_DEPEND(uvisor, ucom, 1, 1, 1); +MODULE_DEPEND(uvisor, usb, 1, 1, 1); +MODULE_VERSION(uvisor, 1); + +static const STRUCT_USB_HOST_ID uvisor_devs[] = { +#define UVISOR_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } + UVISOR_DEV(ACEECA, MEZ1000, UVISOR_FLAG_PALM4), + UVISOR_DEV(ALPHASMART, DANA_SYNC, UVISOR_FLAG_PALM4), + UVISOR_DEV(GARMIN, IQUE_3600, UVISOR_FLAG_PALM4), + UVISOR_DEV(FOSSIL, WRISTPDA, UVISOR_FLAG_PALM4), + UVISOR_DEV(HANDSPRING, VISOR, UVISOR_FLAG_VISOR), + UVISOR_DEV(HANDSPRING, TREO, UVISOR_FLAG_PALM4), + UVISOR_DEV(HANDSPRING, TREO600, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, M500, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, M505, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, M515, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, I705, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, M125, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, M130, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, TUNGSTEN_Z, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, TUNGSTEN_T, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, ZIRE, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, ZIRE31, UVISOR_FLAG_PALM4), + UVISOR_DEV(SAMSUNG, I500, UVISOR_FLAG_PALM4), + UVISOR_DEV(SONY, CLIE_40, 0), + UVISOR_DEV(SONY, CLIE_41, 0), + UVISOR_DEV(SONY, CLIE_S360, UVISOR_FLAG_PALM4), + UVISOR_DEV(SONY, CLIE_NX60, UVISOR_FLAG_PALM4), + UVISOR_DEV(SONY, CLIE_35, UVISOR_FLAG_PALM35), +/* UVISOR_DEV(SONY, CLIE_25, UVISOR_FLAG_PALM4 ), */ + UVISOR_DEV(SONY, CLIE_TJ37, UVISOR_FLAG_PALM4), +/* UVISOR_DEV(SONY, CLIE_TH55, UVISOR_FLAG_PALM4 ), See PR 80935 */ + UVISOR_DEV(TAPWAVE, ZODIAC, UVISOR_FLAG_PALM4), +#undef UVISOR_DEV +}; + +static int +uvisor_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UVISOR_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UVISOR_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uvisor_devs, sizeof(uvisor_devs), uaa)); +} + +static int +uvisor_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uvisor_softc *sc = device_get_softc(dev); + struct usb_config uvisor_config_copy[UVISOR_N_TRANSFER]; + int error; + + DPRINTF("sc=%p\n", sc); + memcpy(uvisor_config_copy, uvisor_config, + sizeof(uvisor_config_copy)); + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, "uvisor", NULL, MTX_DEF); + + sc->sc_udev = uaa->device; + + /* configure the device */ + + sc->sc_flag = USB_GET_DRIVER_INFO(uaa); + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = UVISOR_IFACE_INDEX; + + error = uvisor_init(sc, uaa->device, uvisor_config_copy); + + if (error) { + DPRINTF("init failed, error=%s\n", + usbd_errstr(error)); + goto detach; + } + error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, + sc->sc_xfer, uvisor_config_copy, UVISOR_N_TRANSFER, + sc, &sc->sc_mtx); + if (error) { + DPRINTF("could not allocate all pipes\n"); + goto detach; + } + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uvisor_callback, &sc->sc_mtx); + if (error) { + DPRINTF("ucom_attach failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + uvisor_detach(dev); + return (ENXIO); +} + +static int +uvisor_detach(device_t dev) +{ + struct uvisor_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UVISOR_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static usb_error_t +uvisor_init(struct uvisor_softc *sc, struct usb_device *udev, struct usb_config *config) +{ + usb_error_t err = 0; + struct usb_device_request req; + struct uvisor_connection_info coninfo; + struct uvisor_palm_connection_info pconinfo; + uint16_t actlen; + uint8_t buffer[256]; + + if (sc->sc_flag & UVISOR_FLAG_VISOR) { + DPRINTF("getting connection info\n"); + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_GET_CONNECTION_INFORMATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_CONNECTION_INFO_SIZE); + err = usbd_do_request_flags(udev, NULL, + &req, &coninfo, USB_SHORT_XFER_OK, + &actlen, USB_DEFAULT_TIMEOUT); + + if (err) { + goto done; + } + } +#ifdef USB_DEBUG + if (sc->sc_flag & UVISOR_FLAG_VISOR) { + uint16_t i, np; + const char *desc; + + np = UGETW(coninfo.num_ports); + if (np > UVISOR_MAX_CONN) { + np = UVISOR_MAX_CONN; + } + DPRINTF("Number of ports: %d\n", np); + + for (i = 0; i < np; ++i) { + switch (coninfo.connections[i].port_function_id) { + case UVISOR_FUNCTION_GENERIC: + desc = "Generic"; + break; + case UVISOR_FUNCTION_DEBUGGER: + desc = "Debugger"; + break; + case UVISOR_FUNCTION_HOTSYNC: + desc = "HotSync"; + break; + case UVISOR_FUNCTION_REMOTE_FILE_SYS: + desc = "Remote File System"; + break; + default: + desc = "unknown"; + break; + } + DPRINTF("Port %d is for %s\n", + coninfo.connections[i].port, desc); + } + } +#endif + + if (sc->sc_flag & UVISOR_FLAG_PALM4) { + uint8_t port; + + /* Palm OS 4.0 Hack */ + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_GET_PALM_INFORMATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_GET_PALM_INFORMATION_LEN); + + err = usbd_do_request_flags + (udev, NULL, &req, &pconinfo, USB_SHORT_XFER_OK, + &actlen, USB_DEFAULT_TIMEOUT); + + if (err) { + goto done; + } + if (actlen < 12) { + DPRINTF("too little data\n"); + err = USB_ERR_INVAL; + goto done; + } + if (pconinfo.endpoint_numbers_different) { + port = pconinfo.connections[0].end_point_info; + config[0].endpoint = (port & 0xF); /* output */ + config[1].endpoint = (port >> 4); /* input */ + } else { + port = pconinfo.connections[0].port; + config[0].endpoint = (port & 0xF); /* output */ + config[1].endpoint = (port & 0xF); /* input */ + } +#if 0 + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_GET_PALM_INFORMATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_GET_PALM_INFORMATION_LEN); + err = usbd_do_request(udev, &req, buffer); + if (err) { + goto done; + } +#endif + } + if (sc->sc_flag & UVISOR_FLAG_PALM35) { + /* get the config number */ + DPRINTF("getting config info\n"); + req.bmRequestType = UT_READ; + req.bRequest = UR_GET_CONFIG; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + + err = usbd_do_request(udev, NULL, &req, buffer); + if (err) { + goto done; + } + /* get the interface number */ + DPRINTF("get the interface number\n"); + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_INTERFACE; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + err = usbd_do_request(udev, NULL, &req, buffer); + if (err) { + goto done; + } + } +#if 0 + uWord wAvail; + + DPRINTF("getting available bytes\n"); + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_REQUEST_BYTES_AVAILABLE; + USETW(req.wValue, 0); + USETW(req.wIndex, 5); + USETW(req.wLength, sizeof(wAvail)); + err = usbd_do_request(udev, NULL, &req, &wAvail); + if (err) { + goto done; + } + DPRINTF("avail=%d\n", UGETW(wAvail)); +#endif + + DPRINTF("done\n"); +done: + return (err); +} + +static void +uvisor_cfg_open(struct ucom_softc *ucom) +{ + return; +} + +static void +uvisor_cfg_close(struct ucom_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + uint8_t buffer[UVISOR_CONNECTION_INFO_SIZE]; + struct usb_device_request req; + usb_error_t err; + + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; /* XXX read? */ + req.bRequest = UVISOR_CLOSE_NOTIFICATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_CONNECTION_INFO_SIZE); + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, buffer, 0, 1000); + if (err) { + DPRINTFN(0, "close notification failed, error=%s\n", + usbd_errstr(err)); + } +} + +static void +uvisor_start_read(struct ucom_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UVISOR_BULK_DT_RD]); +} + +static void +uvisor_stop_read(struct ucom_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UVISOR_BULK_DT_RD]); +} + +static void +uvisor_start_write(struct ucom_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UVISOR_BULK_DT_WR]); +} + +static void +uvisor_stop_write(struct ucom_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UVISOR_BULK_DT_WR]); +} + +static void +uvisor_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uvisor_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + uint8_t x; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + for (x = 0; x != UVISOROFRAMES; x++) { + + usbd_xfer_set_frame_offset(xfer, + x * UVISOROBUFSIZE, x); + + pc = usbd_xfer_get_frame(xfer, x); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UVISOROBUFSIZE, &actlen)) { + usbd_xfer_set_frame_len(xfer, x, actlen); + } else { + break; + } + } + /* check for data */ + if (x != 0) { + usbd_xfer_set_frames(xfer, x); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +uvisor_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uvisor_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} diff --git a/sys/bus/u4b/serial/uvscom.c b/sys/bus/u4b/serial/uvscom.c new file mode 100644 index 0000000000..f5dc2d59fc --- /dev/null +++ b/sys/bus/u4b/serial/uvscom.c @@ -0,0 +1,746 @@ +/* $NetBSD: usb/uvscom.c,v 1.1 2002/03/19 15:08:42 augustss Exp $ */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2001-2003, 2005 Shunsuke Akiyama . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/* + * uvscom: SUNTAC Slipper U VS-10U driver. + * Slipper U is a PC Card to USB converter for data communication card + * adapter. It supports DDI Pocket's Air H" C@rd, C@rd H" 64, NTT's P-in, + * P-in m@ater and various data communication card adapters. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR uvscom_debug +#include +#include + +#include + +#ifdef USB_DEBUG +static int uvscom_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uvscom, CTLFLAG_RW, 0, "USB uvscom"); +SYSCTL_INT(_hw_usb_uvscom, OID_AUTO, debug, CTLFLAG_RW, + &uvscom_debug, 0, "Debug level"); +#endif + +#define UVSCOM_MODVER 1 /* module version */ + +#define UVSCOM_CONFIG_INDEX 0 +#define UVSCOM_IFACE_INDEX 0 + +/* Request */ +#define UVSCOM_SET_SPEED 0x10 +#define UVSCOM_LINE_CTL 0x11 +#define UVSCOM_SET_PARAM 0x12 +#define UVSCOM_READ_STATUS 0xd0 +#define UVSCOM_SHUTDOWN 0xe0 + +/* UVSCOM_SET_SPEED parameters */ +#define UVSCOM_SPEED_150BPS 0x00 +#define UVSCOM_SPEED_300BPS 0x01 +#define UVSCOM_SPEED_600BPS 0x02 +#define UVSCOM_SPEED_1200BPS 0x03 +#define UVSCOM_SPEED_2400BPS 0x04 +#define UVSCOM_SPEED_4800BPS 0x05 +#define UVSCOM_SPEED_9600BPS 0x06 +#define UVSCOM_SPEED_19200BPS 0x07 +#define UVSCOM_SPEED_38400BPS 0x08 +#define UVSCOM_SPEED_57600BPS 0x09 +#define UVSCOM_SPEED_115200BPS 0x0a + +/* UVSCOM_LINE_CTL parameters */ +#define UVSCOM_BREAK 0x40 +#define UVSCOM_RTS 0x02 +#define UVSCOM_DTR 0x01 +#define UVSCOM_LINE_INIT 0x08 + +/* UVSCOM_SET_PARAM parameters */ +#define UVSCOM_DATA_MASK 0x03 +#define UVSCOM_DATA_BIT_8 0x03 +#define UVSCOM_DATA_BIT_7 0x02 +#define UVSCOM_DATA_BIT_6 0x01 +#define UVSCOM_DATA_BIT_5 0x00 + +#define UVSCOM_STOP_MASK 0x04 +#define UVSCOM_STOP_BIT_2 0x04 +#define UVSCOM_STOP_BIT_1 0x00 + +#define UVSCOM_PARITY_MASK 0x18 +#define UVSCOM_PARITY_EVEN 0x18 +#define UVSCOM_PARITY_ODD 0x08 +#define UVSCOM_PARITY_NONE 0x00 + +/* Status bits */ +#define UVSCOM_TXRDY 0x04 +#define UVSCOM_RXRDY 0x01 + +#define UVSCOM_DCD 0x08 +#define UVSCOM_NOCARD 0x04 +#define UVSCOM_DSR 0x02 +#define UVSCOM_CTS 0x01 +#define UVSCOM_USTAT_MASK (UVSCOM_NOCARD | UVSCOM_DSR | UVSCOM_CTS) + +#define UVSCOM_BULK_BUF_SIZE 1024 /* bytes */ + +enum { + UVSCOM_BULK_DT_WR, + UVSCOM_BULK_DT_RD, + UVSCOM_INTR_DT_RD, + UVSCOM_N_TRANSFER, +}; + +struct uvscom_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UVSCOM_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint16_t sc_line; /* line control register */ + + uint8_t sc_iface_no; /* interface number */ + uint8_t sc_iface_index; /* interface index */ + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* uvscom status register */ + uint8_t sc_unit_status; /* unit status */ +}; + +/* prototypes */ + +static device_probe_t uvscom_probe; +static device_attach_t uvscom_attach; +static device_detach_t uvscom_detach; + +static usb_callback_t uvscom_write_callback; +static usb_callback_t uvscom_read_callback; +static usb_callback_t uvscom_intr_callback; + +static void uvscom_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void uvscom_cfg_set_rts(struct ucom_softc *, uint8_t); +static void uvscom_cfg_set_break(struct ucom_softc *, uint8_t); +static int uvscom_pre_param(struct ucom_softc *, struct termios *); +static void uvscom_cfg_param(struct ucom_softc *, struct termios *); +static int uvscom_pre_open(struct ucom_softc *); +static void uvscom_cfg_open(struct ucom_softc *); +static void uvscom_cfg_close(struct ucom_softc *); +static void uvscom_start_read(struct ucom_softc *); +static void uvscom_stop_read(struct ucom_softc *); +static void uvscom_start_write(struct ucom_softc *); +static void uvscom_stop_write(struct ucom_softc *); +static void uvscom_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void uvscom_cfg_write(struct uvscom_softc *, uint8_t, uint16_t); +static uint16_t uvscom_cfg_read_status(struct uvscom_softc *); +static void uvscom_poll(struct ucom_softc *ucom); + +static const struct usb_config uvscom_config[UVSCOM_N_TRANSFER] = { + + [UVSCOM_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UVSCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &uvscom_write_callback, + }, + + [UVSCOM_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UVSCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uvscom_read_callback, + }, + + [UVSCOM_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &uvscom_intr_callback, + }, +}; + +static const struct ucom_callback uvscom_callback = { + .ucom_cfg_get_status = &uvscom_cfg_get_status, + .ucom_cfg_set_dtr = &uvscom_cfg_set_dtr, + .ucom_cfg_set_rts = &uvscom_cfg_set_rts, + .ucom_cfg_set_break = &uvscom_cfg_set_break, + .ucom_cfg_param = &uvscom_cfg_param, + .ucom_cfg_open = &uvscom_cfg_open, + .ucom_cfg_close = &uvscom_cfg_close, + .ucom_pre_open = &uvscom_pre_open, + .ucom_pre_param = &uvscom_pre_param, + .ucom_start_read = &uvscom_start_read, + .ucom_stop_read = &uvscom_stop_read, + .ucom_start_write = &uvscom_start_write, + .ucom_stop_write = &uvscom_stop_write, + .ucom_poll = &uvscom_poll, +}; + +static const STRUCT_USB_HOST_ID uvscom_devs[] = { + /* SUNTAC U-Cable type A4 */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_AS144L4, 0)}, + /* SUNTAC U-Cable type D2 */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_DS96L, 0)}, + /* SUNTAC Ir-Trinity */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_IS96U, 0)}, + /* SUNTAC U-Cable type P1 */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_PS64P1, 0)}, + /* SUNTAC Slipper U */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_VS10U, 0)}, +}; + +static device_method_t uvscom_methods[] = { + DEVMETHOD(device_probe, uvscom_probe), + DEVMETHOD(device_attach, uvscom_attach), + DEVMETHOD(device_detach, uvscom_detach), + {0, 0} +}; + +static devclass_t uvscom_devclass; + +static driver_t uvscom_driver = { + .name = "uvscom", + .methods = uvscom_methods, + .size = sizeof(struct uvscom_softc), +}; + +DRIVER_MODULE(uvscom, uhub, uvscom_driver, uvscom_devclass, NULL, 0); +MODULE_DEPEND(uvscom, ucom, 1, 1, 1); +MODULE_DEPEND(uvscom, usb, 1, 1, 1); +MODULE_VERSION(uvscom, UVSCOM_MODVER); + +static int +uvscom_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UVSCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UVSCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uvscom_devs, sizeof(uvscom_devs), uaa)); +} + +static int +uvscom_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uvscom_softc *sc = device_get_softc(dev); + int error; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uvscom", NULL, MTX_DEF); + + sc->sc_udev = uaa->device; + + DPRINTF("sc=%p\n", sc); + + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = UVSCOM_IFACE_INDEX; + + error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, + sc->sc_xfer, uvscom_config, UVSCOM_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + DPRINTF("could not allocate all USB transfers!\n"); + goto detach; + } + sc->sc_line = UVSCOM_LINE_INIT; + + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UVSCOM_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UVSCOM_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uvscom_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + /* start interrupt pipe */ + mtx_lock(&sc->sc_mtx); + usbd_transfer_start(sc->sc_xfer[UVSCOM_INTR_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + return (0); + +detach: + uvscom_detach(dev); + return (ENXIO); +} + +static int +uvscom_detach(device_t dev) +{ + struct uvscom_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + /* stop interrupt pipe */ + + if (sc->sc_xfer[UVSCOM_INTR_DT_RD]) + usbd_transfer_stop(sc->sc_xfer[UVSCOM_INTR_DT_RD]); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UVSCOM_N_TRANSFER); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +uvscom_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uvscom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UVSCOM_BULK_BUF_SIZE, &actlen)) { + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uvscom_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uvscom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uvscom_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uvscom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[2]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen >= 2) { + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, sizeof(buf)); + + sc->sc_lsr = 0; + sc->sc_msr = 0; + sc->sc_unit_status = buf[1]; + + if (buf[0] & UVSCOM_TXRDY) { + sc->sc_lsr |= ULSR_TXRDY; + } + if (buf[0] & UVSCOM_RXRDY) { + sc->sc_lsr |= ULSR_RXRDY; + } + if (buf[1] & UVSCOM_CTS) { + sc->sc_msr |= SER_CTS; + } + if (buf[1] & UVSCOM_DSR) { + sc->sc_msr |= SER_DSR; + } + if (buf[1] & UVSCOM_DCD) { + sc->sc_msr |= SER_DCD; + } + /* + * the UCOM layer will ignore this call if the TTY + * device is closed! + */ + ucom_status_change(&sc->sc_ucom); + } + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uvscom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UVSCOM_DTR; + else + sc->sc_line &= ~UVSCOM_DTR; + + uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line); +} + +static void +uvscom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UVSCOM_RTS; + else + sc->sc_line &= ~UVSCOM_RTS; + + uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line); +} + +static void +uvscom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UVSCOM_BREAK; + else + sc->sc_line &= ~UVSCOM_BREAK; + + uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line); +} + +static int +uvscom_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + switch (t->c_ospeed) { + case B150: + case B300: + case B600: + case B1200: + case B2400: + case B4800: + case B9600: + case B19200: + case B38400: + case B57600: + case B115200: + default: + return (EINVAL); + } + return (0); +} + +static void +uvscom_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uvscom_softc *sc = ucom->sc_parent; + uint16_t value; + + DPRINTF("\n"); + + switch (t->c_ospeed) { + case B150: + value = UVSCOM_SPEED_150BPS; + break; + case B300: + value = UVSCOM_SPEED_300BPS; + break; + case B600: + value = UVSCOM_SPEED_600BPS; + break; + case B1200: + value = UVSCOM_SPEED_1200BPS; + break; + case B2400: + value = UVSCOM_SPEED_2400BPS; + break; + case B4800: + value = UVSCOM_SPEED_4800BPS; + break; + case B9600: + value = UVSCOM_SPEED_9600BPS; + break; + case B19200: + value = UVSCOM_SPEED_19200BPS; + break; + case B38400: + value = UVSCOM_SPEED_38400BPS; + break; + case B57600: + value = UVSCOM_SPEED_57600BPS; + break; + case B115200: + value = UVSCOM_SPEED_115200BPS; + break; + default: + return; + } + + uvscom_cfg_write(sc, UVSCOM_SET_SPEED, value); + + value = 0; + + if (t->c_cflag & CSTOPB) { + value |= UVSCOM_STOP_BIT_2; + } + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) { + value |= UVSCOM_PARITY_ODD; + } else { + value |= UVSCOM_PARITY_EVEN; + } + } else { + value |= UVSCOM_PARITY_NONE; + } + + switch (t->c_cflag & CSIZE) { + case CS5: + value |= UVSCOM_DATA_BIT_5; + break; + case CS6: + value |= UVSCOM_DATA_BIT_6; + break; + case CS7: + value |= UVSCOM_DATA_BIT_7; + break; + default: + case CS8: + value |= UVSCOM_DATA_BIT_8; + break; + } + + uvscom_cfg_write(sc, UVSCOM_SET_PARAM, value); +} + +static int +uvscom_pre_open(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("sc = %p\n", sc); + + /* check if PC card was inserted */ + + if (sc->sc_unit_status & UVSCOM_NOCARD) { + DPRINTF("no PC card!\n"); + return (ENXIO); + } + return (0); +} + +static void +uvscom_cfg_open(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("sc = %p\n", sc); + + uvscom_cfg_read_status(sc); +} + +static void +uvscom_cfg_close(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("sc=%p\n", sc); + + uvscom_cfg_write(sc, UVSCOM_SHUTDOWN, 0); +} + +static void +uvscom_start_read(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UVSCOM_BULK_DT_RD]); +} + +static void +uvscom_stop_read(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UVSCOM_BULK_DT_RD]); +} + +static void +uvscom_start_write(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UVSCOM_BULK_DT_WR]); +} + +static void +uvscom_stop_write(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UVSCOM_BULK_DT_WR]); +} + +static void +uvscom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +uvscom_cfg_write(struct uvscom_softc *sc, uint8_t index, uint16_t value) +{ + struct usb_device_request req; + usb_error_t err; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = index; + USETW(req.wValue, value); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usbd_errstr(err)); + } +} + +static uint16_t +uvscom_cfg_read_status(struct uvscom_softc *sc) +{ + struct usb_device_request req; + usb_error_t err; + uint8_t data[2]; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UVSCOM_READ_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 2); + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, data, 0, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usbd_errstr(err)); + } + return (data[0] | (data[1] << 8)); +} + +static void +uvscom_poll(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UVSCOM_N_TRANSFER); +} diff --git a/sys/bus/u4b/storage/rio500_usb.h b/sys/bus/u4b/storage/rio500_usb.h new file mode 100644 index 0000000000..5b53e2c249 --- /dev/null +++ b/sys/bus/u4b/storage/rio500_usb.h @@ -0,0 +1,48 @@ +/*- + ---------------------------------------------------------------------- + + Copyright (C) 2000 Cesar Miquel (miquel@df.uba.ar) + + Redistribution and use in source and binary forms, with or without + modification, are permitted under any licence of your choise which + meets the open source licence definiton + http://www.opensource.org/opd.html such as the GNU licence or the + BSD licence. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License or the BSD license for more details. + + ---------------------------------------------------------------------- + + Modified for FreeBSD by Iwasa Kazmi + + ---------------------------------------------------------------------- */ + +/* $FreeBSD$ */ + +#include +#ifndef USB_VENDOR_DIAMOND +#define USB_VENDOR_DIAMOND 0x841 +#endif +#ifndef USB_PRODUCT_DIAMOND_RIO500USB +#define USB_PRODUCT_DIAMOND_RIO500USB 0x1 +#endif + +struct RioCommand +{ + uint16_t length; + int request; + int requesttype; + int value; + int index; + void *buffer; + int timeout; +}; + +#define RIO_SEND_COMMAND _IOWR('U', 200, struct RioCommand) +#define RIO_RECV_COMMAND _IOWR('U', 201, struct RioCommand) + +#define RIO_DIR_OUT 0x0 +#define RIO_DIR_IN 0x1 diff --git a/sys/bus/u4b/storage/umass.c b/sys/bus/u4b/storage/umass.c new file mode 100644 index 0000000000..3f2bd11f88 --- /dev/null +++ b/sys/bus/u4b/storage/umass.c @@ -0,0 +1,3116 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 1999 MAEKAWA Masahide , + * Nick Hibma + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * $NetBSD: umass.c,v 1.28 2000/04/02 23:46:53 augustss Exp $ + */ + +/* Also already merged from NetBSD: + * $NetBSD: umass.c,v 1.67 2001/11/25 19:05:22 augustss Exp $ + * $NetBSD: umass.c,v 1.90 2002/11/04 19:17:33 pooka Exp $ + * $NetBSD: umass.c,v 1.108 2003/11/07 17:03:25 wiz Exp $ + * $NetBSD: umass.c,v 1.109 2003/12/04 13:57:31 keihan Exp $ + */ + +/* + * Universal Serial Bus Mass Storage Class specs: + * http://www.usb.org/developers/devclass_docs/usb_msc_overview_1.2.pdf + * http://www.usb.org/developers/devclass_docs/usbmassbulk_10.pdf + * http://www.usb.org/developers/devclass_docs/usb_msc_cbi_1.1.pdf + * http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf + */ + +/* + * Ported to NetBSD by Lennart Augustsson . + * Parts of the code written by Jason R. Thorpe . + */ + +/* + * The driver handles 3 Wire Protocols + * - Command/Bulk/Interrupt (CBI) + * - Command/Bulk/Interrupt with Command Completion Interrupt (CBI with CCI) + * - Mass Storage Bulk-Only (BBB) + * (BBB refers Bulk/Bulk/Bulk for Command/Data/Status phases) + * + * Over these wire protocols it handles the following command protocols + * - SCSI + * - UFI (floppy command set) + * - 8070i (ATAPI) + * + * UFI and 8070i (ATAPI) are transformed versions of the SCSI command set. The + * sc->sc_transform method is used to convert the commands into the appropriate + * format (if at all necessary). For example, UFI requires all commands to be + * 12 bytes in length amongst other things. + * + * The source code below is marked and can be split into a number of pieces + * (in this order): + * + * - probe/attach/detach + * - generic transfer routines + * - BBB + * - CBI + * - CBI_I (in addition to functions from CBI) + * - CAM (Common Access Method) + * - SCSI + * - UFI + * - 8070i (ATAPI) + * + * The protocols are implemented using a state machine, for the transfers as + * well as for the resets. The state machine is contained in umass_t_*_callback. + * The state machine is started through either umass_command_start() or + * umass_reset(). + * + * The reason for doing this is a) CAM performs a lot better this way and b) it + * avoids using tsleep from interrupt context (for example after a failed + * transfer). + */ + +/* + * The SCSI related part of this driver has been derived from the + * dev/ppbus/vpo.c driver, by Nicolas Souchu (nsouch@FreeBSD.org). + * + * The CAM layer uses so called actions which are messages sent to the host + * adapter for completion. The actions come in through umass_cam_action. The + * appropriate block of routines is called depending on the transport protocol + * in use. When the transfer has finished, these routines call + * umass_cam_cb again to complete the CAM command. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#define UMASS_EXT_BUFFER +#ifdef UMASS_EXT_BUFFER +/* this enables loading of virtual buffers into DMA */ +#define UMASS_USB_FLAGS .ext_buffer=1, +#else +#define UMASS_USB_FLAGS +#endif + +#ifdef USB_DEBUG +#define DIF(m, x) \ + do { \ + if (umass_debug & (m)) { x ; } \ + } while (0) + +#define DPRINTF(sc, m, fmt, ...) \ + do { \ + if (umass_debug & (m)) { \ + printf("%s:%s: " fmt, \ + (sc) ? (const char *)(sc)->sc_name : \ + (const char *)"umassX", \ + __FUNCTION__ ,## __VA_ARGS__); \ + } \ + } while (0) + +#define UDMASS_GEN 0x00010000 /* general */ +#define UDMASS_SCSI 0x00020000 /* scsi */ +#define UDMASS_UFI 0x00040000 /* ufi command set */ +#define UDMASS_ATAPI 0x00080000 /* 8070i command set */ +#define UDMASS_CMD (UDMASS_SCSI|UDMASS_UFI|UDMASS_ATAPI) +#define UDMASS_USB 0x00100000 /* USB general */ +#define UDMASS_BBB 0x00200000 /* Bulk-Only transfers */ +#define UDMASS_CBI 0x00400000 /* CBI transfers */ +#define UDMASS_WIRE (UDMASS_BBB|UDMASS_CBI) +#define UDMASS_ALL 0xffff0000 /* all of the above */ +static int umass_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, umass, CTLFLAG_RW, 0, "USB umass"); +SYSCTL_INT(_hw_usb_umass, OID_AUTO, debug, CTLFLAG_RW, + &umass_debug, 0, "umass debug level"); + +TUNABLE_INT("hw.usb.umass.debug", &umass_debug); +#else +#define DIF(...) do { } while (0) +#define DPRINTF(...) do { } while (0) +#endif + +#define UMASS_GONE ((struct umass_softc *)1) + +#define UMASS_BULK_SIZE (1 << 17) +#define UMASS_CBI_DIAGNOSTIC_CMDLEN 12 /* bytes */ +#define UMASS_MAX_CMDLEN MAX(12, CAM_MAX_CDBLEN) /* bytes */ + +/* USB transfer definitions */ + +#define UMASS_T_BBB_RESET1 0 /* Bulk-Only */ +#define UMASS_T_BBB_RESET2 1 +#define UMASS_T_BBB_RESET3 2 +#define UMASS_T_BBB_COMMAND 3 +#define UMASS_T_BBB_DATA_READ 4 +#define UMASS_T_BBB_DATA_RD_CS 5 +#define UMASS_T_BBB_DATA_WRITE 6 +#define UMASS_T_BBB_DATA_WR_CS 7 +#define UMASS_T_BBB_STATUS 8 +#define UMASS_T_BBB_MAX 9 + +#define UMASS_T_CBI_RESET1 0 /* CBI */ +#define UMASS_T_CBI_RESET2 1 +#define UMASS_T_CBI_RESET3 2 +#define UMASS_T_CBI_COMMAND 3 +#define UMASS_T_CBI_DATA_READ 4 +#define UMASS_T_CBI_DATA_RD_CS 5 +#define UMASS_T_CBI_DATA_WRITE 6 +#define UMASS_T_CBI_DATA_WR_CS 7 +#define UMASS_T_CBI_STATUS 8 +#define UMASS_T_CBI_RESET4 9 +#define UMASS_T_CBI_MAX 10 + +#define UMASS_T_MAX MAX(UMASS_T_CBI_MAX, UMASS_T_BBB_MAX) + +/* Generic definitions */ + +/* Direction for transfer */ +#define DIR_NONE 0 +#define DIR_IN 1 +#define DIR_OUT 2 + +/* device name */ +#define DEVNAME "umass" +#define DEVNAME_SIM "umass-sim" + +/* Approximate maximum transfer speeds (assumes 33% overhead). */ +#define UMASS_FULL_TRANSFER_SPEED 1000 +#define UMASS_HIGH_TRANSFER_SPEED 40000 +#define UMASS_SUPER_TRANSFER_SPEED 400000 +#define UMASS_FLOPPY_TRANSFER_SPEED 20 + +#define UMASS_TIMEOUT 5000 /* ms */ + +/* CAM specific definitions */ + +#define UMASS_SCSIID_MAX 1 /* maximum number of drives expected */ +#define UMASS_SCSIID_HOST UMASS_SCSIID_MAX + +/* Bulk-Only features */ + +#define UR_BBB_RESET 0xff /* Bulk-Only reset */ +#define UR_BBB_GET_MAX_LUN 0xfe /* Get maximum lun */ + +/* Command Block Wrapper */ +typedef struct { + uDWord dCBWSignature; +#define CBWSIGNATURE 0x43425355 + uDWord dCBWTag; + uDWord dCBWDataTransferLength; + uByte bCBWFlags; +#define CBWFLAGS_OUT 0x00 +#define CBWFLAGS_IN 0x80 + uByte bCBWLUN; + uByte bCDBLength; +#define CBWCDBLENGTH 16 + uByte CBWCDB[CBWCDBLENGTH]; +} __packed umass_bbb_cbw_t; + +#define UMASS_BBB_CBW_SIZE 31 + +/* Command Status Wrapper */ +typedef struct { + uDWord dCSWSignature; +#define CSWSIGNATURE 0x53425355 +#define CSWSIGNATURE_IMAGINATION_DBX1 0x43425355 +#define CSWSIGNATURE_OLYMPUS_C1 0x55425355 + uDWord dCSWTag; + uDWord dCSWDataResidue; + uByte bCSWStatus; +#define CSWSTATUS_GOOD 0x0 +#define CSWSTATUS_FAILED 0x1 +#define CSWSTATUS_PHASE 0x2 +} __packed umass_bbb_csw_t; + +#define UMASS_BBB_CSW_SIZE 13 + +/* CBI features */ + +#define UR_CBI_ADSC 0x00 + +typedef union { + struct { + uint8_t type; +#define IDB_TYPE_CCI 0x00 + uint8_t value; +#define IDB_VALUE_PASS 0x00 +#define IDB_VALUE_FAIL 0x01 +#define IDB_VALUE_PHASE 0x02 +#define IDB_VALUE_PERSISTENT 0x03 +#define IDB_VALUE_STATUS_MASK 0x03 + } __packed common; + + struct { + uint8_t asc; + uint8_t ascq; + } __packed ufi; +} __packed umass_cbi_sbl_t; + +struct umass_softc; /* see below */ + +typedef void (umass_callback_t)(struct umass_softc *sc, union ccb *ccb, + uint32_t residue, uint8_t status); + +#define STATUS_CMD_OK 0 /* everything ok */ +#define STATUS_CMD_UNKNOWN 1 /* will have to fetch sense */ +#define STATUS_CMD_FAILED 2 /* transfer was ok, command failed */ +#define STATUS_WIRE_FAILED 3 /* couldn't even get command across */ + +typedef uint8_t (umass_transform_t)(struct umass_softc *sc, uint8_t *cmd_ptr, + uint8_t cmd_len); + +/* Wire and command protocol */ +#define UMASS_PROTO_BBB 0x0001 /* USB wire protocol */ +#define UMASS_PROTO_CBI 0x0002 +#define UMASS_PROTO_CBI_I 0x0004 +#define UMASS_PROTO_WIRE 0x00ff /* USB wire protocol mask */ +#define UMASS_PROTO_SCSI 0x0100 /* command protocol */ +#define UMASS_PROTO_ATAPI 0x0200 +#define UMASS_PROTO_UFI 0x0400 +#define UMASS_PROTO_RBC 0x0800 +#define UMASS_PROTO_COMMAND 0xff00 /* command protocol mask */ + +/* Device specific quirks */ +#define NO_QUIRKS 0x0000 + /* + * The drive does not support Test Unit Ready. Convert to Start Unit + */ +#define NO_TEST_UNIT_READY 0x0001 + /* + * The drive does not reset the Unit Attention state after REQUEST + * SENSE has been sent. The INQUIRY command does not reset the UA + * either, and so CAM runs in circles trying to retrieve the initial + * INQUIRY data. + */ +#define RS_NO_CLEAR_UA 0x0002 + /* The drive does not support START STOP. */ +#define NO_START_STOP 0x0004 + /* Don't ask for full inquiry data (255b). */ +#define FORCE_SHORT_INQUIRY 0x0008 + /* Needs to be initialised the Shuttle way */ +#define SHUTTLE_INIT 0x0010 + /* Drive needs to be switched to alternate iface 1 */ +#define ALT_IFACE_1 0x0020 + /* Drive does not do 1Mb/s, but just floppy speeds (20kb/s) */ +#define FLOPPY_SPEED 0x0040 + /* The device can't count and gets the residue of transfers wrong */ +#define IGNORE_RESIDUE 0x0080 + /* No GetMaxLun call */ +#define NO_GETMAXLUN 0x0100 + /* The device uses a weird CSWSIGNATURE. */ +#define WRONG_CSWSIG 0x0200 + /* Device cannot handle INQUIRY so fake a generic response */ +#define NO_INQUIRY 0x0400 + /* Device cannot handle INQUIRY EVPD, return CHECK CONDITION */ +#define NO_INQUIRY_EVPD 0x0800 + /* Pad all RBC requests to 12 bytes. */ +#define RBC_PAD_TO_12 0x1000 + /* + * Device reports number of sectors from READ_CAPACITY, not max + * sector number. + */ +#define READ_CAPACITY_OFFBY1 0x2000 + /* + * Device cannot handle a SCSI synchronize cache command. Normally + * this quirk would be handled in the cam layer, but for IDE bridges + * we need to associate the quirk with the bridge and not the + * underlying disk device. This is handled by faking a success + * result. + */ +#define NO_SYNCHRONIZE_CACHE 0x4000 + +struct umass_softc { + + struct scsi_sense cam_scsi_sense; + struct scsi_test_unit_ready cam_scsi_test_unit_ready; + struct mtx sc_mtx; + struct { + uint8_t *data_ptr; + union ccb *ccb; + umass_callback_t *callback; + + uint32_t data_len; /* bytes */ + uint32_t data_rem; /* bytes */ + uint32_t data_timeout; /* ms */ + uint32_t actlen; /* bytes */ + + uint8_t cmd_data[UMASS_MAX_CMDLEN]; + uint8_t cmd_len; /* bytes */ + uint8_t dir; + uint8_t lun; + } sc_transfer; + + /* Bulk specific variables for transfers in progress */ + umass_bbb_cbw_t cbw; /* command block wrapper */ + umass_bbb_csw_t csw; /* command status wrapper */ + + /* CBI specific variables for transfers in progress */ + umass_cbi_sbl_t sbl; /* status block */ + + device_t sc_dev; + struct usb_device *sc_udev; + struct cam_sim *sc_sim; /* SCSI Interface Module */ + struct usb_xfer *sc_xfer[UMASS_T_MAX]; + + /* + * The command transform function is used to convert the SCSI + * commands into their derivatives, like UFI, ATAPI, and friends. + */ + umass_transform_t *sc_transform; + + uint32_t sc_unit; + uint32_t sc_quirks; /* they got it almost right */ + uint32_t sc_proto; /* wire and cmd protocol */ + + uint8_t sc_name[16]; + uint8_t sc_iface_no; /* interface number */ + uint8_t sc_maxlun; /* maximum LUN number, inclusive */ + uint8_t sc_last_xfer_index; + uint8_t sc_status_try; +}; + +struct umass_probe_proto { + uint32_t quirks; + uint32_t proto; + + int error; +}; + +/* prototypes */ + +static device_probe_t umass_probe; +static device_attach_t umass_attach; +static device_detach_t umass_detach; + +static usb_callback_t umass_tr_error; +static usb_callback_t umass_t_bbb_reset1_callback; +static usb_callback_t umass_t_bbb_reset2_callback; +static usb_callback_t umass_t_bbb_reset3_callback; +static usb_callback_t umass_t_bbb_command_callback; +static usb_callback_t umass_t_bbb_data_read_callback; +static usb_callback_t umass_t_bbb_data_rd_cs_callback; +static usb_callback_t umass_t_bbb_data_write_callback; +static usb_callback_t umass_t_bbb_data_wr_cs_callback; +static usb_callback_t umass_t_bbb_status_callback; +static usb_callback_t umass_t_cbi_reset1_callback; +static usb_callback_t umass_t_cbi_reset2_callback; +static usb_callback_t umass_t_cbi_reset3_callback; +static usb_callback_t umass_t_cbi_reset4_callback; +static usb_callback_t umass_t_cbi_command_callback; +static usb_callback_t umass_t_cbi_data_read_callback; +static usb_callback_t umass_t_cbi_data_rd_cs_callback; +static usb_callback_t umass_t_cbi_data_write_callback; +static usb_callback_t umass_t_cbi_data_wr_cs_callback; +static usb_callback_t umass_t_cbi_status_callback; + +static void umass_cancel_ccb(struct umass_softc *); +static void umass_init_shuttle(struct umass_softc *); +static void umass_reset(struct umass_softc *); +static void umass_t_bbb_data_clear_stall_callback(struct usb_xfer *, + uint8_t, uint8_t, usb_error_t); +static void umass_command_start(struct umass_softc *, uint8_t, void *, + uint32_t, uint32_t, umass_callback_t *, union ccb *); +static uint8_t umass_bbb_get_max_lun(struct umass_softc *); +static void umass_cbi_start_status(struct umass_softc *); +static void umass_t_cbi_data_clear_stall_callback(struct usb_xfer *, + uint8_t, uint8_t, usb_error_t); +static int umass_cam_attach_sim(struct umass_softc *); +static void umass_cam_attach(struct umass_softc *); +static void umass_cam_detach_sim(struct umass_softc *); +static void umass_cam_action(struct cam_sim *, union ccb *); +static void umass_cam_poll(struct cam_sim *); +static void umass_cam_cb(struct umass_softc *, union ccb *, uint32_t, + uint8_t); +static void umass_cam_sense_cb(struct umass_softc *, union ccb *, uint32_t, + uint8_t); +static void umass_cam_quirk_cb(struct umass_softc *, union ccb *, uint32_t, + uint8_t); +static uint8_t umass_scsi_transform(struct umass_softc *, uint8_t *, uint8_t); +static uint8_t umass_rbc_transform(struct umass_softc *, uint8_t *, uint8_t); +static uint8_t umass_ufi_transform(struct umass_softc *, uint8_t *, uint8_t); +static uint8_t umass_atapi_transform(struct umass_softc *, uint8_t *, + uint8_t); +static uint8_t umass_no_transform(struct umass_softc *, uint8_t *, uint8_t); +static uint8_t umass_std_transform(struct umass_softc *, union ccb *, uint8_t + *, uint8_t); + +#ifdef USB_DEBUG +static void umass_bbb_dump_cbw(struct umass_softc *, umass_bbb_cbw_t *); +static void umass_bbb_dump_csw(struct umass_softc *, umass_bbb_csw_t *); +static void umass_cbi_dump_cmd(struct umass_softc *, void *, uint8_t); +static void umass_dump_buffer(struct umass_softc *, uint8_t *, uint32_t, + uint32_t); +#endif + +static struct usb_config umass_bbb_config[UMASS_T_BBB_MAX] = { + + [UMASS_T_BBB_RESET1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_bbb_reset1_callback, + .timeout = 5000, /* 5 seconds */ + .interval = 500, /* 500 milliseconds */ + }, + + [UMASS_T_BBB_RESET2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_bbb_reset2_callback, + .timeout = 5000, /* 5 seconds */ + .interval = 50, /* 50 milliseconds */ + }, + + [UMASS_T_BBB_RESET3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_bbb_reset3_callback, + .timeout = 5000, /* 5 seconds */ + .interval = 50, /* 50 milliseconds */ + }, + + [UMASS_T_BBB_COMMAND] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = sizeof(umass_bbb_cbw_t), + .callback = &umass_t_bbb_command_callback, + .timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_BBB_DATA_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UMASS_BULK_SIZE, + .flags = {.proxy_buffer = 1,.short_xfer_ok = 1, UMASS_USB_FLAGS}, + .callback = &umass_t_bbb_data_read_callback, + .timeout = 0, /* overwritten later */ + }, + + [UMASS_T_BBB_DATA_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_bbb_data_rd_cs_callback, + .timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_BBB_DATA_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UMASS_BULK_SIZE, + .flags = {.proxy_buffer = 1,.short_xfer_ok = 1, UMASS_USB_FLAGS}, + .callback = &umass_t_bbb_data_write_callback, + .timeout = 0, /* overwritten later */ + }, + + [UMASS_T_BBB_DATA_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_bbb_data_wr_cs_callback, + .timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_BBB_STATUS] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = sizeof(umass_bbb_csw_t), + .flags = {.short_xfer_ok = 1,}, + .callback = &umass_t_bbb_status_callback, + .timeout = 5000, /* ms */ + }, +}; + +static struct usb_config umass_cbi_config[UMASS_T_CBI_MAX] = { + + [UMASS_T_CBI_RESET1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = (sizeof(struct usb_device_request) + + UMASS_CBI_DIAGNOSTIC_CMDLEN), + .callback = &umass_t_cbi_reset1_callback, + .timeout = 5000, /* 5 seconds */ + .interval = 500, /* 500 milliseconds */ + }, + + [UMASS_T_CBI_RESET2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_cbi_reset2_callback, + .timeout = 5000, /* 5 seconds */ + .interval = 50, /* 50 milliseconds */ + }, + + [UMASS_T_CBI_RESET3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_cbi_reset3_callback, + .timeout = 5000, /* 5 seconds */ + .interval = 50, /* 50 milliseconds */ + }, + + [UMASS_T_CBI_COMMAND] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = (sizeof(struct usb_device_request) + + UMASS_MAX_CMDLEN), + .callback = &umass_t_cbi_command_callback, + .timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_CBI_DATA_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UMASS_BULK_SIZE, + .flags = {.proxy_buffer = 1,.short_xfer_ok = 1, UMASS_USB_FLAGS}, + .callback = &umass_t_cbi_data_read_callback, + .timeout = 0, /* overwritten later */ + }, + + [UMASS_T_CBI_DATA_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_cbi_data_rd_cs_callback, + .timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_CBI_DATA_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UMASS_BULK_SIZE, + .flags = {.proxy_buffer = 1,.short_xfer_ok = 1, UMASS_USB_FLAGS}, + .callback = &umass_t_cbi_data_write_callback, + .timeout = 0, /* overwritten later */ + }, + + [UMASS_T_CBI_DATA_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_cbi_data_wr_cs_callback, + .timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_CBI_STATUS] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.short_xfer_ok = 1,.no_pipe_ok = 1,}, + .bufsize = sizeof(umass_cbi_sbl_t), + .callback = &umass_t_cbi_status_callback, + .timeout = 5000, /* ms */ + }, + + [UMASS_T_CBI_RESET4] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_cbi_reset4_callback, + .timeout = 5000, /* ms */ + }, +}; + +/* If device cannot return valid inquiry data, fake it */ +static const uint8_t fake_inq_data[SHORT_INQUIRY_LENGTH] = { + 0, /* removable */ 0x80, SCSI_REV_2, SCSI_REV_2, + /* additional_length */ 31, 0, 0, 0 +}; + +#define UFI_COMMAND_LENGTH 12 /* UFI commands are always 12 bytes */ +#define ATAPI_COMMAND_LENGTH 12 /* ATAPI commands are always 12 bytes */ + +static devclass_t umass_devclass; + +static device_method_t umass_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, umass_probe), + DEVMETHOD(device_attach, umass_attach), + DEVMETHOD(device_detach, umass_detach), + {0, 0} +}; + +static driver_t umass_driver = { + .name = "umass", + .methods = umass_methods, + .size = sizeof(struct umass_softc), +}; + +DRIVER_MODULE(umass, uhub, umass_driver, umass_devclass, NULL, 0); +MODULE_DEPEND(umass, usb, 1, 1, 1); +MODULE_DEPEND(umass, cam, 1, 1, 1); +MODULE_VERSION(umass, 1); + +/* + * USB device probe/attach/detach + */ + +static const STRUCT_USB_HOST_ID __used umass_devs[] = { + /* generic mass storage class */ + {USB_IFACE_CLASS(UICLASS_MASS),}, +}; + +static uint16_t +umass_get_proto(struct usb_interface *iface) +{ + struct usb_interface_descriptor *id; + uint16_t retval; + + retval = 0; + + /* Check for a standards compliant device */ + id = usbd_get_interface_descriptor(iface); + if ((id == NULL) || + (id->bInterfaceClass != UICLASS_MASS)) { + goto done; + } + switch (id->bInterfaceSubClass) { + case UISUBCLASS_SCSI: + retval |= UMASS_PROTO_SCSI; + break; + case UISUBCLASS_UFI: + retval |= UMASS_PROTO_UFI; + break; + case UISUBCLASS_RBC: + retval |= UMASS_PROTO_RBC; + break; + case UISUBCLASS_SFF8020I: + case UISUBCLASS_SFF8070I: + retval |= UMASS_PROTO_ATAPI; + break; + default: + goto done; + } + + switch (id->bInterfaceProtocol) { + case UIPROTO_MASS_CBI: + retval |= UMASS_PROTO_CBI; + break; + case UIPROTO_MASS_CBI_I: + retval |= UMASS_PROTO_CBI_I; + break; + case UIPROTO_MASS_BBB_OLD: + case UIPROTO_MASS_BBB: + retval |= UMASS_PROTO_BBB; + break; + default: + goto done; + } +done: + return (retval); +} + +/* + * Match the device we are seeing with the devices supported. + */ +static struct umass_probe_proto +umass_probe_proto(device_t dev, struct usb_attach_arg *uaa) +{ + struct umass_probe_proto ret; + uint32_t quirks = NO_QUIRKS; + uint32_t proto = umass_get_proto(uaa->iface); + + memset(&ret, 0, sizeof(ret)); + ret.error = BUS_PROBE_GENERIC; + + /* Search for protocol enforcement */ + + if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_BBB)) { + proto &= ~UMASS_PROTO_WIRE; + proto |= UMASS_PROTO_BBB; + } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_CBI)) { + proto &= ~UMASS_PROTO_WIRE; + proto |= UMASS_PROTO_CBI; + } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_CBI_I)) { + proto &= ~UMASS_PROTO_WIRE; + proto |= UMASS_PROTO_CBI_I; + } + + if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_SCSI)) { + proto &= ~UMASS_PROTO_COMMAND; + proto |= UMASS_PROTO_SCSI; + } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_ATAPI)) { + proto &= ~UMASS_PROTO_COMMAND; + proto |= UMASS_PROTO_ATAPI; + } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_UFI)) { + proto &= ~UMASS_PROTO_COMMAND; + proto |= UMASS_PROTO_UFI; + } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_RBC)) { + proto &= ~UMASS_PROTO_COMMAND; + proto |= UMASS_PROTO_RBC; + } + + /* Check if the protocol is invalid */ + + if ((proto & UMASS_PROTO_COMMAND) == 0) { + ret.error = ENXIO; + goto done; + } + + if ((proto & UMASS_PROTO_WIRE) == 0) { + ret.error = ENXIO; + goto done; + } + + /* Search for quirks */ + + if (usb_test_quirk(uaa, UQ_MSC_NO_TEST_UNIT_READY)) + quirks |= NO_TEST_UNIT_READY; + if (usb_test_quirk(uaa, UQ_MSC_NO_RS_CLEAR_UA)) + quirks |= RS_NO_CLEAR_UA; + if (usb_test_quirk(uaa, UQ_MSC_NO_START_STOP)) + quirks |= NO_START_STOP; + if (usb_test_quirk(uaa, UQ_MSC_NO_GETMAXLUN)) + quirks |= NO_GETMAXLUN; + if (usb_test_quirk(uaa, UQ_MSC_NO_INQUIRY)) + quirks |= NO_INQUIRY; + if (usb_test_quirk(uaa, UQ_MSC_NO_INQUIRY_EVPD)) + quirks |= NO_INQUIRY_EVPD; + if (usb_test_quirk(uaa, UQ_MSC_NO_SYNC_CACHE)) + quirks |= NO_SYNCHRONIZE_CACHE; + if (usb_test_quirk(uaa, UQ_MSC_SHUTTLE_INIT)) + quirks |= SHUTTLE_INIT; + if (usb_test_quirk(uaa, UQ_MSC_ALT_IFACE_1)) + quirks |= ALT_IFACE_1; + if (usb_test_quirk(uaa, UQ_MSC_FLOPPY_SPEED)) + quirks |= FLOPPY_SPEED; + if (usb_test_quirk(uaa, UQ_MSC_IGNORE_RESIDUE)) + quirks |= IGNORE_RESIDUE; + if (usb_test_quirk(uaa, UQ_MSC_WRONG_CSWSIG)) + quirks |= WRONG_CSWSIG; + if (usb_test_quirk(uaa, UQ_MSC_RBC_PAD_TO_12)) + quirks |= RBC_PAD_TO_12; + if (usb_test_quirk(uaa, UQ_MSC_READ_CAP_OFFBY1)) + quirks |= READ_CAPACITY_OFFBY1; + if (usb_test_quirk(uaa, UQ_MSC_FORCE_SHORT_INQ)) + quirks |= FORCE_SHORT_INQUIRY; + +done: + ret.quirks = quirks; + ret.proto = proto; + return (ret); +} + +static int +umass_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct umass_probe_proto temp; + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + temp = umass_probe_proto(dev, uaa); + + return (temp.error); +} + +static int +umass_attach(device_t dev) +{ + struct umass_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct umass_probe_proto temp = umass_probe_proto(dev, uaa); + struct usb_interface_descriptor *id; + int32_t err; + + /* + * NOTE: the softc struct is cleared in device_set_driver. + * We can safely call umass_detach without specifically + * initializing the struct. + */ + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + sc->sc_proto = temp.proto; + sc->sc_quirks = temp.quirks; + sc->sc_unit = device_get_unit(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), + NULL, MTX_DEF | MTX_RECURSE); + + /* get interface index */ + + id = usbd_get_interface_descriptor(uaa->iface); + if (id == NULL) { + device_printf(dev, "failed to get " + "interface number\n"); + goto detach; + } + sc->sc_iface_no = id->bInterfaceNumber; + +#ifdef USB_DEBUG + device_printf(dev, " "); + + switch (sc->sc_proto & UMASS_PROTO_COMMAND) { + case UMASS_PROTO_SCSI: + printf("SCSI"); + break; + case UMASS_PROTO_ATAPI: + printf("8070i (ATAPI)"); + break; + case UMASS_PROTO_UFI: + printf("UFI"); + break; + case UMASS_PROTO_RBC: + printf("RBC"); + break; + default: + printf("(unknown 0x%02x)", + sc->sc_proto & UMASS_PROTO_COMMAND); + break; + } + + printf(" over "); + + switch (sc->sc_proto & UMASS_PROTO_WIRE) { + case UMASS_PROTO_BBB: + printf("Bulk-Only"); + break; + case UMASS_PROTO_CBI: /* uses Comand/Bulk pipes */ + printf("CBI"); + break; + case UMASS_PROTO_CBI_I: /* uses Comand/Bulk/Interrupt pipes */ + printf("CBI with CCI"); + break; + default: + printf("(unknown 0x%02x)", + sc->sc_proto & UMASS_PROTO_WIRE); + } + + printf("; quirks = 0x%04x\n", sc->sc_quirks); +#endif + + if (sc->sc_quirks & ALT_IFACE_1) { + err = usbd_set_alt_interface_index + (uaa->device, uaa->info.bIfaceIndex, 1); + + if (err) { + DPRINTF(sc, UDMASS_USB, "could not switch to " + "Alt Interface 1\n"); + goto detach; + } + } + /* allocate all required USB transfers */ + + if (sc->sc_proto & UMASS_PROTO_BBB) { + + err = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, umass_bbb_config, + UMASS_T_BBB_MAX, sc, &sc->sc_mtx); + + /* skip reset first time */ + sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; + + } else if (sc->sc_proto & (UMASS_PROTO_CBI | UMASS_PROTO_CBI_I)) { + + err = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, umass_cbi_config, + UMASS_T_CBI_MAX, sc, &sc->sc_mtx); + + /* skip reset first time */ + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + + } else { + err = USB_ERR_INVAL; + } + + if (err) { + device_printf(dev, "could not setup required " + "transfers, %s\n", usbd_errstr(err)); + goto detach; + } + sc->sc_transform = + (sc->sc_proto & UMASS_PROTO_SCSI) ? &umass_scsi_transform : + (sc->sc_proto & UMASS_PROTO_UFI) ? &umass_ufi_transform : + (sc->sc_proto & UMASS_PROTO_ATAPI) ? &umass_atapi_transform : + (sc->sc_proto & UMASS_PROTO_RBC) ? &umass_rbc_transform : + &umass_no_transform; + + /* from here onwards the device can be used. */ + + if (sc->sc_quirks & SHUTTLE_INIT) { + umass_init_shuttle(sc); + } + /* get the maximum LUN supported by the device */ + + if (((sc->sc_proto & UMASS_PROTO_WIRE) == UMASS_PROTO_BBB) && + !(sc->sc_quirks & NO_GETMAXLUN)) + sc->sc_maxlun = umass_bbb_get_max_lun(sc); + else + sc->sc_maxlun = 0; + + /* Prepare the SCSI command block */ + sc->cam_scsi_sense.opcode = REQUEST_SENSE; + sc->cam_scsi_test_unit_ready.opcode = TEST_UNIT_READY; + + /* register the SIM */ + err = umass_cam_attach_sim(sc); + if (err) { + goto detach; + } + /* scan the SIM */ + umass_cam_attach(sc); + + DPRINTF(sc, UDMASS_GEN, "Attach finished\n"); + + return (0); /* success */ + +detach: + umass_detach(dev); + return (ENXIO); /* failure */ +} + +static int +umass_detach(device_t dev) +{ + struct umass_softc *sc = device_get_softc(dev); + + DPRINTF(sc, UDMASS_USB, "\n"); + + /* teardown our statemachine */ + + usbd_transfer_unsetup(sc->sc_xfer, UMASS_T_MAX); + +#if (__FreeBSD_version >= 700037) + mtx_lock(&sc->sc_mtx); +#endif + umass_cam_detach_sim(sc); + +#if (__FreeBSD_version >= 700037) + mtx_unlock(&sc->sc_mtx); +#endif + mtx_destroy(&sc->sc_mtx); + + return (0); /* success */ +} + +static void +umass_init_shuttle(struct umass_softc *sc) +{ + struct usb_device_request req; + usb_error_t err; + uint8_t status[2] = {0, 0}; + + /* + * The Linux driver does this, but no one can tell us what the + * command does. + */ + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = 1; /* XXX unknown command */ + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, sizeof(status)); + err = usbd_do_request(sc->sc_udev, NULL, &req, &status); + + DPRINTF(sc, UDMASS_GEN, "Shuttle init returned 0x%02x%02x\n", + status[0], status[1]); +} + +/* + * Generic functions to handle transfers + */ + +static void +umass_transfer_start(struct umass_softc *sc, uint8_t xfer_index) +{ + DPRINTF(sc, UDMASS_GEN, "transfer index = " + "%d\n", xfer_index); + + if (sc->sc_xfer[xfer_index]) { + sc->sc_last_xfer_index = xfer_index; + usbd_transfer_start(sc->sc_xfer[xfer_index]); + } else { + umass_cancel_ccb(sc); + } +} + +static void +umass_reset(struct umass_softc *sc) +{ + DPRINTF(sc, UDMASS_GEN, "resetting device\n"); + + /* + * stop the last transfer, if not already stopped: + */ + usbd_transfer_stop(sc->sc_xfer[sc->sc_last_xfer_index]); + umass_transfer_start(sc, 0); +} + +static void +umass_cancel_ccb(struct umass_softc *sc) +{ + union ccb *ccb; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + ccb = sc->sc_transfer.ccb; + sc->sc_transfer.ccb = NULL; + sc->sc_last_xfer_index = 0; + + if (ccb) { + (sc->sc_transfer.callback) + (sc, ccb, (sc->sc_transfer.data_len - + sc->sc_transfer.actlen), STATUS_WIRE_FAILED); + } +} + +static void +umass_tr_error(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + + if (error != USB_ERR_CANCELLED) { + + DPRINTF(sc, UDMASS_GEN, "transfer error, %s -> " + "reset\n", usbd_errstr(error)); + } + umass_cancel_ccb(sc); +} + +/* + * BBB protocol specific functions + */ + +static void +umass_t_bbb_reset1_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + umass_transfer_start(sc, UMASS_T_BBB_RESET2); + return; + + case USB_ST_SETUP: + /* + * Reset recovery (5.3.4 in Universal Serial Bus Mass Storage Class) + * + * For Reset Recovery the host shall issue in the following order: + * a) a Bulk-Only Mass Storage Reset + * b) a Clear Feature HALT to the Bulk-In endpoint + * c) a Clear Feature HALT to the Bulk-Out endpoint + * + * This is done in 3 steps, using 3 transfers: + * UMASS_T_BBB_RESET1 + * UMASS_T_BBB_RESET2 + * UMASS_T_BBB_RESET3 + */ + + DPRINTF(sc, UDMASS_BBB, "BBB reset!\n"); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_BBB_RESET; /* bulk only reset */ + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frames(xfer, 1); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + umass_tr_error(xfer, error); + return; + + } +} + +static void +umass_t_bbb_reset2_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_RESET3, + UMASS_T_BBB_DATA_READ, error); +} + +static void +umass_t_bbb_reset3_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_COMMAND, + UMASS_T_BBB_DATA_WRITE, error); +} + +static void +umass_t_bbb_data_clear_stall_callback(struct usb_xfer *xfer, + uint8_t next_xfer, uint8_t stall_xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + umass_transfer_start(sc, next_xfer); + return; + + case USB_ST_SETUP: + if (usbd_clear_stall_callback(xfer, sc->sc_xfer[stall_xfer])) { + goto tr_transferred; + } + return; + + default: /* Error */ + umass_tr_error(xfer, error); + return; + + } +} + +static void +umass_t_bbb_command_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + union ccb *ccb = sc->sc_transfer.ccb; + struct usb_page_cache *pc; + uint32_t tag; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + umass_transfer_start + (sc, ((sc->sc_transfer.dir == DIR_IN) ? UMASS_T_BBB_DATA_READ : + (sc->sc_transfer.dir == DIR_OUT) ? UMASS_T_BBB_DATA_WRITE : + UMASS_T_BBB_STATUS)); + return; + + case USB_ST_SETUP: + + sc->sc_status_try = 0; + + if (ccb) { + + /* + * the initial value is not important, + * as long as the values are unique: + */ + tag = UGETDW(sc->cbw.dCBWTag) + 1; + + USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE); + USETDW(sc->cbw.dCBWTag, tag); + + /* + * dCBWDataTransferLength: + * This field indicates the number of bytes of data that the host + * intends to transfer on the IN or OUT Bulk endpoint(as indicated by + * the Direction bit) during the execution of this command. If this + * field is set to 0, the device will expect that no data will be + * transferred IN or OUT during this command, regardless of the value + * of the Direction bit defined in dCBWFlags. + */ + USETDW(sc->cbw.dCBWDataTransferLength, sc->sc_transfer.data_len); + + /* + * dCBWFlags: + * The bits of the Flags field are defined as follows: + * Bits 0-6 reserved + * Bit 7 Direction - this bit shall be ignored if the + * dCBWDataTransferLength field is zero. + * 0 = data Out from host to device + * 1 = data In from device to host + */ + sc->cbw.bCBWFlags = ((sc->sc_transfer.dir == DIR_IN) ? + CBWFLAGS_IN : CBWFLAGS_OUT); + sc->cbw.bCBWLUN = sc->sc_transfer.lun; + + if (sc->sc_transfer.cmd_len > sizeof(sc->cbw.CBWCDB)) { + sc->sc_transfer.cmd_len = sizeof(sc->cbw.CBWCDB); + DPRINTF(sc, UDMASS_BBB, "Truncating long command!\n"); + } + sc->cbw.bCDBLength = sc->sc_transfer.cmd_len; + + memcpy(sc->cbw.CBWCDB, sc->sc_transfer.cmd_data, + sc->sc_transfer.cmd_len); + + memset(sc->sc_transfer.cmd_data + + sc->sc_transfer.cmd_len, 0, + sizeof(sc->cbw.CBWCDB) - + sc->sc_transfer.cmd_len); + + DIF(UDMASS_BBB, umass_bbb_dump_cbw(sc, &sc->cbw)); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &sc->cbw, sizeof(sc->cbw)); + usbd_xfer_set_frame_len(xfer, 0, sizeof(sc->cbw)); + + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + umass_tr_error(xfer, error); + return; + + } +} + +static void +umass_t_bbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + uint32_t max_bulk = usbd_xfer_max_len(xfer); +#ifndef UMASS_EXT_BUFFER + struct usb_page_cache *pc; +#endif + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +#ifndef UMASS_EXT_BUFFER + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, sc->sc_transfer.data_ptr, actlen); +#endif + sc->sc_transfer.data_rem -= actlen; + sc->sc_transfer.data_ptr += actlen; + sc->sc_transfer.actlen += actlen; + + if (actlen < sumlen) { + /* short transfer */ + sc->sc_transfer.data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF(sc, UDMASS_BBB, "max_bulk=%d, data_rem=%d\n", + max_bulk, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_rem == 0) { + umass_transfer_start(sc, UMASS_T_BBB_STATUS); + return; + } + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); + +#ifdef UMASS_EXT_BUFFER + usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, + max_bulk); +#else + usbd_xfer_set_frame_len(xfer, 0, max_bulk); +#endif + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) { + umass_tr_error(xfer, error); + } else { + umass_transfer_start(sc, UMASS_T_BBB_DATA_RD_CS); + } + return; + + } +} + +static void +umass_t_bbb_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_STATUS, + UMASS_T_BBB_DATA_READ, error); +} + +static void +umass_t_bbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + uint32_t max_bulk = usbd_xfer_max_len(xfer); +#ifndef UMASS_EXT_BUFFER + struct usb_page_cache *pc; +#endif + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->sc_transfer.data_rem -= actlen; + sc->sc_transfer.data_ptr += actlen; + sc->sc_transfer.actlen += actlen; + + if (actlen < sumlen) { + /* short transfer */ + sc->sc_transfer.data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF(sc, UDMASS_BBB, "max_bulk=%d, data_rem=%d\n", + max_bulk, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_rem == 0) { + umass_transfer_start(sc, UMASS_T_BBB_STATUS); + return; + } + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); + +#ifdef UMASS_EXT_BUFFER + usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, + max_bulk); +#else + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, sc->sc_transfer.data_ptr, max_bulk); + usbd_xfer_set_frame_len(xfer, 0, max_bulk); +#endif + + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) { + umass_tr_error(xfer, error); + } else { + umass_transfer_start(sc, UMASS_T_BBB_DATA_WR_CS); + } + return; + + } +} + +static void +umass_t_bbb_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_STATUS, + UMASS_T_BBB_DATA_WRITE, error); +} + +static void +umass_t_bbb_status_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + union ccb *ccb = sc->sc_transfer.ccb; + struct usb_page_cache *pc; + uint32_t residue; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + /* + * Do a full reset if there is something wrong with the CSW: + */ + sc->sc_status_try = 1; + + /* Zero missing parts of the CSW: */ + + if (actlen < sizeof(sc->csw)) + memset(&sc->csw, 0, sizeof(sc->csw)); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &sc->csw, actlen); + + DIF(UDMASS_BBB, umass_bbb_dump_csw(sc, &sc->csw)); + + residue = UGETDW(sc->csw.dCSWDataResidue); + + if ((!residue) || (sc->sc_quirks & IGNORE_RESIDUE)) { + residue = (sc->sc_transfer.data_len - + sc->sc_transfer.actlen); + } + if (residue > sc->sc_transfer.data_len) { + DPRINTF(sc, UDMASS_BBB, "truncating residue from %d " + "to %d bytes\n", residue, sc->sc_transfer.data_len); + residue = sc->sc_transfer.data_len; + } + /* translate weird command-status signatures: */ + if (sc->sc_quirks & WRONG_CSWSIG) { + + uint32_t temp = UGETDW(sc->csw.dCSWSignature); + + if ((temp == CSWSIGNATURE_OLYMPUS_C1) || + (temp == CSWSIGNATURE_IMAGINATION_DBX1)) { + USETDW(sc->csw.dCSWSignature, CSWSIGNATURE); + } + } + /* check CSW and handle eventual error */ + if (UGETDW(sc->csw.dCSWSignature) != CSWSIGNATURE) { + DPRINTF(sc, UDMASS_BBB, "bad CSW signature 0x%08x != 0x%08x\n", + UGETDW(sc->csw.dCSWSignature), CSWSIGNATURE); + /* + * Invalid CSW: Wrong signature or wrong tag might + * indicate that we lost synchronization. Reset the + * device. + */ + goto tr_error; + } else if (UGETDW(sc->csw.dCSWTag) != UGETDW(sc->cbw.dCBWTag)) { + DPRINTF(sc, UDMASS_BBB, "Invalid CSW: tag 0x%08x should be " + "0x%08x\n", UGETDW(sc->csw.dCSWTag), + UGETDW(sc->cbw.dCBWTag)); + goto tr_error; + } else if (sc->csw.bCSWStatus > CSWSTATUS_PHASE) { + DPRINTF(sc, UDMASS_BBB, "Invalid CSW: status %d > %d\n", + sc->csw.bCSWStatus, CSWSTATUS_PHASE); + goto tr_error; + } else if (sc->csw.bCSWStatus == CSWSTATUS_PHASE) { + DPRINTF(sc, UDMASS_BBB, "Phase error, residue = " + "%d\n", residue); + goto tr_error; + } else if (sc->sc_transfer.actlen > sc->sc_transfer.data_len) { + DPRINTF(sc, UDMASS_BBB, "Buffer overrun %d > %d\n", + sc->sc_transfer.actlen, sc->sc_transfer.data_len); + goto tr_error; + } else if (sc->csw.bCSWStatus == CSWSTATUS_FAILED) { + DPRINTF(sc, UDMASS_BBB, "Command failed, residue = " + "%d\n", residue); + + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, residue, STATUS_CMD_FAILED); + } else { + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, residue, STATUS_CMD_OK); + } + return; + + case USB_ST_SETUP: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: +tr_error: + DPRINTF(sc, UDMASS_BBB, "Failed to read CSW: %s, try %d\n", + usbd_errstr(error), sc->sc_status_try); + + if ((error == USB_ERR_CANCELLED) || + (sc->sc_status_try)) { + umass_tr_error(xfer, error); + } else { + sc->sc_status_try = 1; + umass_transfer_start(sc, UMASS_T_BBB_DATA_RD_CS); + } + return; + + } +} + +static void +umass_command_start(struct umass_softc *sc, uint8_t dir, + void *data_ptr, uint32_t data_len, + uint32_t data_timeout, umass_callback_t *callback, + union ccb *ccb) +{ + sc->sc_transfer.lun = ccb->ccb_h.target_lun; + + /* + * NOTE: assumes that "sc->sc_transfer.cmd_data" and + * "sc->sc_transfer.cmd_len" has been properly + * initialized. + */ + + sc->sc_transfer.dir = data_len ? dir : DIR_NONE; + sc->sc_transfer.data_ptr = data_ptr; + sc->sc_transfer.data_len = data_len; + sc->sc_transfer.data_rem = data_len; + sc->sc_transfer.data_timeout = (data_timeout + UMASS_TIMEOUT); + + sc->sc_transfer.actlen = 0; + sc->sc_transfer.callback = callback; + sc->sc_transfer.ccb = ccb; + + if (sc->sc_xfer[sc->sc_last_xfer_index]) { + usbd_transfer_start(sc->sc_xfer[sc->sc_last_xfer_index]); + } else { + ccb->ccb_h.status = CAM_TID_INVALID; + xpt_done(ccb); + } +} + +static uint8_t +umass_bbb_get_max_lun(struct umass_softc *sc) +{ + struct usb_device_request req; + usb_error_t err; + uint8_t buf = 0; + + /* The Get Max Lun command is a class-specific request. */ + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_BBB_GET_MAX_LUN; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + + err = usbd_do_request(sc->sc_udev, NULL, &req, &buf); + if (err) { + buf = 0; + + /* Device doesn't support Get Max Lun request. */ + printf("%s: Get Max Lun not supported (%s)\n", + sc->sc_name, usbd_errstr(err)); + } + return (buf); +} + +/* + * Command/Bulk/Interrupt (CBI) specific functions + */ + +static void +umass_cbi_start_status(struct umass_softc *sc) +{ + if (sc->sc_xfer[UMASS_T_CBI_STATUS]) { + umass_transfer_start(sc, UMASS_T_CBI_STATUS); + } else { + union ccb *ccb = sc->sc_transfer.ccb; + + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, (sc->sc_transfer.data_len - + sc->sc_transfer.actlen), STATUS_CMD_UNKNOWN); + } +} + +static void +umass_t_cbi_reset1_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc; + uint8_t buf[UMASS_CBI_DIAGNOSTIC_CMDLEN]; + + uint8_t i; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + umass_transfer_start(sc, UMASS_T_CBI_RESET2); + break; + + case USB_ST_SETUP: + /* + * Command Block Reset Protocol + * + * First send a reset request to the device. Then clear + * any possibly stalled bulk endpoints. + * + * This is done in 3 steps, using 3 transfers: + * UMASS_T_CBI_RESET1 + * UMASS_T_CBI_RESET2 + * UMASS_T_CBI_RESET3 + * UMASS_T_CBI_RESET4 (only if there is an interrupt endpoint) + */ + + DPRINTF(sc, UDMASS_CBI, "CBI reset!\n"); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_CBI_ADSC; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, UMASS_CBI_DIAGNOSTIC_CMDLEN); + + /* + * The 0x1d code is the SEND DIAGNOSTIC command. To + * distinguish between the two, the last 10 bytes of the CBL + * is filled with 0xff (section 2.2 of the CBI + * specification) + */ + buf[0] = 0x1d; /* Command Block Reset */ + buf[1] = 0x04; + + for (i = 2; i < UMASS_CBI_DIAGNOSTIC_CMDLEN; i++) { + buf[i] = 0xff; + } + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + pc = usbd_xfer_get_frame(xfer, 1); + usbd_copy_in(pc, 0, buf, sizeof(buf)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, sizeof(buf)); + usbd_xfer_set_frames(xfer, 2); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) + umass_tr_error(xfer, error); + else + umass_transfer_start(sc, UMASS_T_CBI_RESET2); + break; + + } +} + +static void +umass_t_cbi_reset2_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_RESET3, + UMASS_T_CBI_DATA_READ, error); +} + +static void +umass_t_cbi_reset3_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + + umass_t_cbi_data_clear_stall_callback + (xfer, (sc->sc_xfer[UMASS_T_CBI_RESET4] && + sc->sc_xfer[UMASS_T_CBI_STATUS]) ? + UMASS_T_CBI_RESET4 : UMASS_T_CBI_COMMAND, + UMASS_T_CBI_DATA_WRITE, error); +} + +static void +umass_t_cbi_reset4_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_COMMAND, + UMASS_T_CBI_STATUS, error); +} + +static void +umass_t_cbi_data_clear_stall_callback(struct usb_xfer *xfer, + uint8_t next_xfer, uint8_t stall_xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + if (next_xfer == UMASS_T_CBI_STATUS) { + umass_cbi_start_status(sc); + } else { + umass_transfer_start(sc, next_xfer); + } + break; + + case USB_ST_SETUP: + if (usbd_clear_stall_callback(xfer, sc->sc_xfer[stall_xfer])) { + goto tr_transferred; /* should not happen */ + } + break; + + default: /* Error */ + umass_tr_error(xfer, error); + break; + + } +} + +static void +umass_t_cbi_command_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + union ccb *ccb = sc->sc_transfer.ccb; + struct usb_device_request req; + struct usb_page_cache *pc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (sc->sc_transfer.dir == DIR_NONE) { + umass_cbi_start_status(sc); + } else { + umass_transfer_start + (sc, (sc->sc_transfer.dir == DIR_IN) ? + UMASS_T_CBI_DATA_READ : UMASS_T_CBI_DATA_WRITE); + } + break; + + case USB_ST_SETUP: + + if (ccb) { + + /* + * do a CBI transfer with cmd_len bytes from + * cmd_data, possibly a data phase of data_len + * bytes from/to the device and finally a status + * read phase. + */ + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_CBI_ADSC; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + req.wLength[0] = sc->sc_transfer.cmd_len; + req.wLength[1] = 0; + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + pc = usbd_xfer_get_frame(xfer, 1); + usbd_copy_in(pc, 0, sc->sc_transfer.cmd_data, + sc->sc_transfer.cmd_len); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, sc->sc_transfer.cmd_len); + usbd_xfer_set_frames(xfer, + sc->sc_transfer.cmd_len ? 2 : 1); + + DIF(UDMASS_CBI, + umass_cbi_dump_cmd(sc, + sc->sc_transfer.cmd_data, + sc->sc_transfer.cmd_len)); + + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + /* + * STALL on the control pipe can be result of the command error. + * Attempt to clear this STALL same as for bulk pipe also + * results in command completion interrupt, but ASC/ASCQ there + * look like not always valid, so don't bother about it. + */ + if ((error == USB_ERR_STALLED) || + (sc->sc_transfer.callback == &umass_cam_cb)) { + sc->sc_transfer.ccb = NULL; + (sc->sc_transfer.callback) + (sc, ccb, sc->sc_transfer.data_len, + STATUS_CMD_UNKNOWN); + } else { + umass_tr_error(xfer, error); + /* skip reset */ + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + } + break; + } +} + +static void +umass_t_cbi_data_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + uint32_t max_bulk = usbd_xfer_max_len(xfer); +#ifndef UMASS_EXT_BUFFER + struct usb_page_cache *pc; +#endif + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +#ifndef UMASS_EXT_BUFFER + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, sc->sc_transfer.data_ptr, actlen); +#endif + sc->sc_transfer.data_rem -= actlen; + sc->sc_transfer.data_ptr += actlen; + sc->sc_transfer.actlen += actlen; + + if (actlen < sumlen) { + /* short transfer */ + sc->sc_transfer.data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF(sc, UDMASS_CBI, "max_bulk=%d, data_rem=%d\n", + max_bulk, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_rem == 0) { + umass_cbi_start_status(sc); + break; + } + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); + +#ifdef UMASS_EXT_BUFFER + usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, + max_bulk); +#else + usbd_xfer_set_frame_len(xfer, 0, max_bulk); +#endif + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if ((error == USB_ERR_CANCELLED) || + (sc->sc_transfer.callback != &umass_cam_cb)) { + umass_tr_error(xfer, error); + } else { + umass_transfer_start(sc, UMASS_T_CBI_DATA_RD_CS); + } + break; + + } +} + +static void +umass_t_cbi_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_STATUS, + UMASS_T_CBI_DATA_READ, error); +} + +static void +umass_t_cbi_data_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + uint32_t max_bulk = usbd_xfer_max_len(xfer); +#ifndef UMASS_EXT_BUFFER + struct usb_page_cache *pc; +#endif + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->sc_transfer.data_rem -= actlen; + sc->sc_transfer.data_ptr += actlen; + sc->sc_transfer.actlen += actlen; + + if (actlen < sumlen) { + /* short transfer */ + sc->sc_transfer.data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF(sc, UDMASS_CBI, "max_bulk=%d, data_rem=%d\n", + max_bulk, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_rem == 0) { + umass_cbi_start_status(sc); + break; + } + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); + +#ifdef UMASS_EXT_BUFFER + usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, + max_bulk); +#else + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, sc->sc_transfer.data_ptr, max_bulk); + usbd_xfer_set_frame_len(xfer, 0, max_bulk); +#endif + + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if ((error == USB_ERR_CANCELLED) || + (sc->sc_transfer.callback != &umass_cam_cb)) { + umass_tr_error(xfer, error); + } else { + umass_transfer_start(sc, UMASS_T_CBI_DATA_WR_CS); + } + break; + + } +} + +static void +umass_t_cbi_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_STATUS, + UMASS_T_CBI_DATA_WRITE, error); +} + +static void +umass_t_cbi_status_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + union ccb *ccb = sc->sc_transfer.ccb; + struct usb_page_cache *pc; + uint32_t residue; + uint8_t status; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen < sizeof(sc->sbl)) { + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &sc->sbl, sizeof(sc->sbl)); + + residue = (sc->sc_transfer.data_len - + sc->sc_transfer.actlen); + + /* dissect the information in the buffer */ + + if (sc->sc_proto & UMASS_PROTO_UFI) { + + /* + * Section 3.4.3.1.3 specifies that the UFI command + * protocol returns an ASC and ASCQ in the interrupt + * data block. + */ + + DPRINTF(sc, UDMASS_CBI, "UFI CCI, ASC = 0x%02x, " + "ASCQ = 0x%02x\n", sc->sbl.ufi.asc, + sc->sbl.ufi.ascq); + + status = (((sc->sbl.ufi.asc == 0) && + (sc->sbl.ufi.ascq == 0)) ? + STATUS_CMD_OK : STATUS_CMD_FAILED); + + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, residue, status); + + break; + + } else { + + /* Command Interrupt Data Block */ + + DPRINTF(sc, UDMASS_CBI, "type=0x%02x, value=0x%02x\n", + sc->sbl.common.type, sc->sbl.common.value); + + if (sc->sbl.common.type == IDB_TYPE_CCI) { + + status = (sc->sbl.common.value & IDB_VALUE_STATUS_MASK); + + status = ((status == IDB_VALUE_PASS) ? STATUS_CMD_OK : + (status == IDB_VALUE_FAIL) ? STATUS_CMD_FAILED : + (status == IDB_VALUE_PERSISTENT) ? STATUS_CMD_FAILED : + STATUS_WIRE_FAILED); + + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, residue, status); + + break; + } + } + + /* fallthrough */ + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTF(sc, UDMASS_CBI, "Failed to read CSW: %s\n", + usbd_errstr(error)); + umass_tr_error(xfer, error); + break; + + } +} + +/* + * CAM specific functions (used by SCSI, UFI, 8070i (ATAPI)) + */ + +static int +umass_cam_attach_sim(struct umass_softc *sc) +{ + struct cam_devq *devq; /* Per device Queue */ + + /* + * A HBA is attached to the CAM layer. + * + * The CAM layer will then after a while start probing for devices on + * the bus. The number of SIMs is limited to one. + */ + + devq = cam_simq_alloc(1 /* maximum openings */ ); + if (devq == NULL) { + return (ENOMEM); + } + sc->sc_sim = cam_sim_alloc + (&umass_cam_action, &umass_cam_poll, + DEVNAME_SIM, + sc /* priv */ , + sc->sc_unit /* unit number */ , +#if (__FreeBSD_version >= 700037) + &sc->sc_mtx /* mutex */ , +#endif + 1 /* maximum device openings */ , + 0 /* maximum tagged device openings */ , + devq); + + if (sc->sc_sim == NULL) { + cam_simq_free(devq); + return (ENOMEM); + } + +#if (__FreeBSD_version >= 700037) + mtx_lock(&sc->sc_mtx); +#endif + +#if (__FreeBSD_version >= 700048) + if (xpt_bus_register(sc->sc_sim, sc->sc_dev, sc->sc_unit) != CAM_SUCCESS) { + mtx_unlock(&sc->sc_mtx); + return (ENOMEM); + } +#else + if (xpt_bus_register(sc->sc_sim, sc->sc_unit) != CAM_SUCCESS) { +#if (__FreeBSD_version >= 700037) + mtx_unlock(&sc->sc_mtx); +#endif + return (ENOMEM); + } +#endif + +#if (__FreeBSD_version >= 700037) + mtx_unlock(&sc->sc_mtx); +#endif + return (0); +} + +static void +umass_cam_attach(struct umass_softc *sc) +{ +#ifndef USB_DEBUG + if (bootverbose) +#endif + printf("%s:%d:%d:%d: Attached to scbus%d\n", + sc->sc_name, cam_sim_path(sc->sc_sim), + sc->sc_unit, CAM_LUN_WILDCARD, + cam_sim_path(sc->sc_sim)); +} + +/* umass_cam_detach + * detach from the CAM layer + */ + +static void +umass_cam_detach_sim(struct umass_softc *sc) +{ + if (sc->sc_sim != NULL) { + if (xpt_bus_deregister(cam_sim_path(sc->sc_sim))) { + /* accessing the softc is not possible after this */ + sc->sc_sim->softc = UMASS_GONE; + cam_sim_free(sc->sc_sim, /* free_devq */ TRUE); + } else { + panic("%s: CAM layer is busy\n", + sc->sc_name); + } + sc->sc_sim = NULL; + } +} + +/* umass_cam_action + * CAM requests for action come through here + */ + +static void +umass_cam_action(struct cam_sim *sim, union ccb *ccb) +{ + struct umass_softc *sc = (struct umass_softc *)sim->softc; + + if (sc == UMASS_GONE || + (sc != NULL && !usbd_device_attached(sc->sc_udev))) { + ccb->ccb_h.status = CAM_SEL_TIMEOUT; + xpt_done(ccb); + return; + } + if (sc) { +#if (__FreeBSD_version < 700037) + mtx_lock(&sc->sc_mtx); +#endif + } + /* + * Verify, depending on the operation to perform, that we either got + * a valid sc, because an existing target was referenced, or + * otherwise the SIM is addressed. + * + * This avoids bombing out at a printf and does give the CAM layer some + * sensible feedback on errors. + */ + switch (ccb->ccb_h.func_code) { + case XPT_SCSI_IO: + case XPT_RESET_DEV: + case XPT_GET_TRAN_SETTINGS: + case XPT_SET_TRAN_SETTINGS: + case XPT_CALC_GEOMETRY: + /* the opcodes requiring a target. These should never occur. */ + if (sc == NULL) { + DPRINTF(sc, UDMASS_GEN, "%s:%d:%d:%d:func_code 0x%04x: " + "Invalid target (target needed)\n", + DEVNAME_SIM, cam_sim_path(sc->sc_sim), + ccb->ccb_h.target_id, ccb->ccb_h.target_lun, + ccb->ccb_h.func_code); + + ccb->ccb_h.status = CAM_TID_INVALID; + xpt_done(ccb); + goto done; + } + break; + case XPT_PATH_INQ: + case XPT_NOOP: + /* + * The opcodes sometimes aimed at a target (sc is valid), + * sometimes aimed at the SIM (sc is invalid and target is + * CAM_TARGET_WILDCARD) + */ + if ((sc == NULL) && + (ccb->ccb_h.target_id != CAM_TARGET_WILDCARD)) { + DPRINTF(sc, UDMASS_SCSI, "%s:%d:%d:%d:func_code 0x%04x: " + "Invalid target (no wildcard)\n", + DEVNAME_SIM, cam_sim_path(sc->sc_sim), + ccb->ccb_h.target_id, ccb->ccb_h.target_lun, + ccb->ccb_h.func_code); + + ccb->ccb_h.status = CAM_TID_INVALID; + xpt_done(ccb); + goto done; + } + break; + default: + /* XXX Hm, we should check the input parameters */ + break; + } + + /* Perform the requested action */ + switch (ccb->ccb_h.func_code) { + case XPT_SCSI_IO: + { + uint8_t *cmd; + uint8_t dir; + + if (ccb->csio.ccb_h.flags & CAM_CDB_POINTER) { + cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_ptr); + } else { + cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_bytes); + } + + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_SCSI_IO: " + "cmd: 0x%02x, flags: 0x%02x, " + "%db cmd/%db data/%db sense\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun, cmd[0], + ccb->ccb_h.flags & CAM_DIR_MASK, ccb->csio.cdb_len, + ccb->csio.dxfer_len, ccb->csio.sense_len); + + if (sc->sc_transfer.ccb) { + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_SCSI_IO: " + "I/O in progress, deferring\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + ccb->ccb_h.status = CAM_SCSI_BUSY; + xpt_done(ccb); + goto done; + } + switch (ccb->ccb_h.flags & CAM_DIR_MASK) { + case CAM_DIR_IN: + dir = DIR_IN; + break; + case CAM_DIR_OUT: + dir = DIR_OUT; + DIF(UDMASS_SCSI, + umass_dump_buffer(sc, ccb->csio.data_ptr, + ccb->csio.dxfer_len, 48)); + break; + default: + dir = DIR_NONE; + } + + ccb->ccb_h.status = CAM_REQ_INPROG | CAM_SIM_QUEUED; + + /* + * sc->sc_transform will convert the command to the + * command format needed by the specific command set + * and return the converted command in + * "sc->sc_transfer.cmd_data" + */ + if (umass_std_transform(sc, ccb, cmd, ccb->csio.cdb_len)) { + + if (sc->sc_transfer.cmd_data[0] == INQUIRY) { + const char *pserial; + + pserial = usb_get_serial(sc->sc_udev); + + /* + * Umass devices don't generally report their serial numbers + * in the usual SCSI way. Emulate it here. + */ + if ((sc->sc_transfer.cmd_data[1] & SI_EVPD) && + (sc->sc_transfer.cmd_data[2] == SVPD_UNIT_SERIAL_NUMBER) && + (pserial[0] != '\0')) { + struct scsi_vpd_unit_serial_number *vpd_serial; + + vpd_serial = (struct scsi_vpd_unit_serial_number *)ccb->csio.data_ptr; + vpd_serial->length = strlen(pserial); + if (vpd_serial->length > sizeof(vpd_serial->serial_num)) + vpd_serial->length = sizeof(vpd_serial->serial_num); + memcpy(vpd_serial->serial_num, pserial, vpd_serial->length); + ccb->csio.scsi_status = SCSI_STATUS_OK; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + goto done; + } + + /* + * Handle EVPD inquiry for broken devices first + * NO_INQUIRY also implies NO_INQUIRY_EVPD + */ + if ((sc->sc_quirks & (NO_INQUIRY_EVPD | NO_INQUIRY)) && + (sc->sc_transfer.cmd_data[1] & SI_EVPD)) { + + scsi_set_sense_data(&ccb->csio.sense_data, + /*sense_format*/ SSD_TYPE_NONE, + /*current_error*/ 1, + /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, + /*asc*/ 0x24, + /*ascq*/ 0x00, + /*extra args*/ SSD_ELEM_NONE); + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | + CAM_AUTOSNS_VALID; + xpt_done(ccb); + goto done; + } + /* + * Return fake inquiry data for + * broken devices + */ + if (sc->sc_quirks & NO_INQUIRY) { + memcpy(ccb->csio.data_ptr, &fake_inq_data, + sizeof(fake_inq_data)); + ccb->csio.scsi_status = SCSI_STATUS_OK; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + goto done; + } + if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { + ccb->csio.dxfer_len = SHORT_INQUIRY_LENGTH; + } + } else if (sc->sc_transfer.cmd_data[0] == SYNCHRONIZE_CACHE) { + if (sc->sc_quirks & NO_SYNCHRONIZE_CACHE) { + ccb->csio.scsi_status = SCSI_STATUS_OK; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + goto done; + } + } + umass_command_start(sc, dir, ccb->csio.data_ptr, + ccb->csio.dxfer_len, + ccb->ccb_h.timeout, + &umass_cam_cb, ccb); + } + break; + } + case XPT_PATH_INQ: + { + struct ccb_pathinq *cpi = &ccb->cpi; + + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_PATH_INQ:.\n", + sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + + /* host specific information */ + cpi->version_num = 1; + cpi->hba_inquiry = 0; + cpi->target_sprt = 0; + cpi->hba_misc = PIM_NO_6_BYTE; + cpi->hba_eng_cnt = 0; + cpi->max_target = UMASS_SCSIID_MAX; /* one target */ + cpi->initiator_id = UMASS_SCSIID_HOST; + strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strlcpy(cpi->hba_vid, "USB SCSI", HBA_IDLEN); + strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(sim); + cpi->bus_id = sc->sc_unit; +#if (__FreeBSD_version >= 700025) + cpi->protocol = PROTO_SCSI; + cpi->protocol_version = SCSI_REV_2; + cpi->transport = XPORT_USB; + cpi->transport_version = 0; +#endif + if (sc == NULL) { + cpi->base_transfer_speed = 0; + cpi->max_lun = 0; + } else { + if (sc->sc_quirks & FLOPPY_SPEED) { + cpi->base_transfer_speed = + UMASS_FLOPPY_TRANSFER_SPEED; + } else { + switch (usbd_get_speed(sc->sc_udev)) { + case USB_SPEED_SUPER: + cpi->base_transfer_speed = + UMASS_SUPER_TRANSFER_SPEED; + cpi->maxio = MAXPHYS; + break; + case USB_SPEED_HIGH: + cpi->base_transfer_speed = + UMASS_HIGH_TRANSFER_SPEED; + break; + default: + cpi->base_transfer_speed = + UMASS_FULL_TRANSFER_SPEED; + break; + } + } + cpi->max_lun = sc->sc_maxlun; + } + + cpi->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_RESET_DEV: + { + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_RESET_DEV:.\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + + umass_reset(sc); + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_GET_TRAN_SETTINGS: + { + struct ccb_trans_settings *cts = &ccb->cts; + + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_GET_TRAN_SETTINGS:.\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + +#if (__FreeBSD_version >= 700025) + cts->protocol = PROTO_SCSI; + cts->protocol_version = SCSI_REV_2; + cts->transport = XPORT_USB; + cts->transport_version = 0; + cts->xport_specific.valid = 0; +#else + cts->valid = 0; + cts->flags = 0; /* no disconnection, tagging */ +#endif + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_SET_TRAN_SETTINGS: + { + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_SET_TRAN_SETTINGS:.\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + + ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; + xpt_done(ccb); + break; + } + case XPT_CALC_GEOMETRY: + { + cam_calc_geometry(&ccb->ccg, /* extended */ 1); + xpt_done(ccb); + break; + } + case XPT_NOOP: + { + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_NOOP:.\n", + sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + default: + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:func_code 0x%04x: " + "Not implemented\n", + sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, + ccb->ccb_h.target_lun, ccb->ccb_h.func_code); + + ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; + xpt_done(ccb); + break; + } + +done: +#if (__FreeBSD_version < 700037) + if (sc) { + mtx_unlock(&sc->sc_mtx); + } +#endif + return; +} + +static void +umass_cam_poll(struct cam_sim *sim) +{ + struct umass_softc *sc = (struct umass_softc *)sim->softc; + + if (sc == UMASS_GONE) + return; + + DPRINTF(sc, UDMASS_SCSI, "CAM poll\n"); + + usbd_transfer_poll(sc->sc_xfer, UMASS_T_MAX); +} + + +/* umass_cam_cb + * finalise a completed CAM command + */ + +static void +umass_cam_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, + uint8_t status) +{ + ccb->csio.resid = residue; + + switch (status) { + case STATUS_CMD_OK: + ccb->ccb_h.status = CAM_REQ_CMP; + if ((sc->sc_quirks & READ_CAPACITY_OFFBY1) && + (ccb->ccb_h.func_code == XPT_SCSI_IO) && + (ccb->csio.cdb_io.cdb_bytes[0] == READ_CAPACITY)) { + struct scsi_read_capacity_data *rcap; + uint32_t maxsector; + + rcap = (void *)(ccb->csio.data_ptr); + maxsector = scsi_4btoul(rcap->addr) - 1; + scsi_ulto4b(maxsector, rcap->addr); + } + /* + * We have to add SVPD_UNIT_SERIAL_NUMBER to the list + * of pages supported by the device - otherwise, CAM + * will never ask us for the serial number if the + * device cannot handle that by itself. + */ + if (ccb->ccb_h.func_code == XPT_SCSI_IO && + sc->sc_transfer.cmd_data[0] == INQUIRY && + (sc->sc_transfer.cmd_data[1] & SI_EVPD) && + sc->sc_transfer.cmd_data[2] == SVPD_SUPPORTED_PAGE_LIST && + (usb_get_serial(sc->sc_udev)[0] != '\0')) { + struct ccb_scsiio *csio; + struct scsi_vpd_supported_page_list *page_list; + + csio = &ccb->csio; + page_list = (struct scsi_vpd_supported_page_list *)csio->data_ptr; + if (page_list->length + 1 < SVPD_SUPPORTED_PAGES_SIZE) { + page_list->list[page_list->length] = SVPD_UNIT_SERIAL_NUMBER; + page_list->length++; + } + } + xpt_done(ccb); + break; + + case STATUS_CMD_UNKNOWN: + case STATUS_CMD_FAILED: + + /* fetch sense data */ + + /* the rest of the command was filled in at attach */ + sc->cam_scsi_sense.length = ccb->csio.sense_len; + + DPRINTF(sc, UDMASS_SCSI, "Fetching %d bytes of " + "sense data\n", ccb->csio.sense_len); + + if (umass_std_transform(sc, ccb, &sc->cam_scsi_sense.opcode, + sizeof(sc->cam_scsi_sense))) { + + if ((sc->sc_quirks & FORCE_SHORT_INQUIRY) && + (sc->sc_transfer.cmd_data[0] == INQUIRY)) { + ccb->csio.sense_len = SHORT_INQUIRY_LENGTH; + } + umass_command_start(sc, DIR_IN, &ccb->csio.sense_data.error_code, + ccb->csio.sense_len, ccb->ccb_h.timeout, + &umass_cam_sense_cb, ccb); + } + break; + + default: + /* + * The wire protocol failed and will hopefully have + * recovered. We return an error to CAM and let CAM + * retry the command if necessary. + */ + ccb->ccb_h.status = CAM_REQ_CMP_ERR; + xpt_done(ccb); + break; + } +} + +/* + * Finalise a completed autosense operation + */ +static void +umass_cam_sense_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, + uint8_t status) +{ + uint8_t *cmd; + + switch (status) { + case STATUS_CMD_OK: + case STATUS_CMD_UNKNOWN: + case STATUS_CMD_FAILED: { + int key, sense_len; + + ccb->csio.sense_resid = residue; + sense_len = ccb->csio.sense_len - ccb->csio.sense_resid; + key = scsi_get_sense_key(&ccb->csio.sense_data, sense_len, + /*show_errors*/ 1); + + if (ccb->csio.ccb_h.flags & CAM_CDB_POINTER) { + cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_ptr); + } else { + cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_bytes); + } + + /* + * Getting sense data always succeeds (apart from wire + * failures): + */ + if ((sc->sc_quirks & RS_NO_CLEAR_UA) && + (cmd[0] == INQUIRY) && + (key == SSD_KEY_UNIT_ATTENTION)) { + /* + * Ignore unit attention errors in the case where + * the Unit Attention state is not cleared on + * REQUEST SENSE. They will appear again at the next + * command. + */ + ccb->ccb_h.status = CAM_REQ_CMP; + } else if (key == SSD_KEY_NO_SENSE) { + /* + * No problem after all (in the case of CBI without + * CCI) + */ + ccb->ccb_h.status = CAM_REQ_CMP; + } else if ((sc->sc_quirks & RS_NO_CLEAR_UA) && + (cmd[0] == READ_CAPACITY) && + (key == SSD_KEY_UNIT_ATTENTION)) { + /* + * Some devices do not clear the unit attention error + * on request sense. We insert a test unit ready + * command to make sure we clear the unit attention + * condition, then allow the retry to proceed as + * usual. + */ + + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR + | CAM_AUTOSNS_VALID; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + +#if 0 + DELAY(300000); +#endif + DPRINTF(sc, UDMASS_SCSI, "Doing a sneaky" + "TEST_UNIT_READY\n"); + + /* the rest of the command was filled in at attach */ + + if (umass_std_transform(sc, ccb, + &sc->cam_scsi_test_unit_ready.opcode, + sizeof(sc->cam_scsi_test_unit_ready))) { + umass_command_start(sc, DIR_NONE, NULL, 0, + ccb->ccb_h.timeout, + &umass_cam_quirk_cb, ccb); + } + break; + } else { + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR + | CAM_AUTOSNS_VALID; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + } + xpt_done(ccb); + break; + } + default: + DPRINTF(sc, UDMASS_SCSI, "Autosense failed, " + "status %d\n", status); + ccb->ccb_h.status = CAM_AUTOSENSE_FAIL; + xpt_done(ccb); + } +} + +/* + * This completion code just handles the fact that we sent a test-unit-ready + * after having previously failed a READ CAPACITY with CHECK_COND. Even + * though this command succeeded, we have to tell CAM to retry. + */ +static void +umass_cam_quirk_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, + uint8_t status) +{ + DPRINTF(sc, UDMASS_SCSI, "Test unit ready " + "returned status %d\n", status); + + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR + | CAM_AUTOSNS_VALID; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + xpt_done(ccb); +} + +/* + * SCSI specific functions + */ + +static uint8_t +umass_scsi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, + uint8_t cmd_len) +{ + if ((cmd_len == 0) || + (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { + DPRINTF(sc, UDMASS_SCSI, "Invalid command " + "length: %d bytes\n", cmd_len); + return (0); /* failure */ + } + sc->sc_transfer.cmd_len = cmd_len; + + switch (cmd_ptr[0]) { + case TEST_UNIT_READY: + if (sc->sc_quirks & NO_TEST_UNIT_READY) { + DPRINTF(sc, UDMASS_SCSI, "Converted TEST_UNIT_READY " + "to START_UNIT\n"); + memset(sc->sc_transfer.cmd_data, 0, cmd_len); + sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; + sc->sc_transfer.cmd_data[4] = SSS_START; + return (1); + } + break; + + case INQUIRY: + /* + * some drives wedge when asked for full inquiry + * information. + */ + if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { + memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); + sc->sc_transfer.cmd_data[4] = SHORT_INQUIRY_LENGTH; + return (1); + } + break; + } + + memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); + return (1); +} + +static uint8_t +umass_rbc_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len) +{ + if ((cmd_len == 0) || + (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { + DPRINTF(sc, UDMASS_SCSI, "Invalid command " + "length: %d bytes\n", cmd_len); + return (0); /* failure */ + } + switch (cmd_ptr[0]) { + /* these commands are defined in RBC: */ + case READ_10: + case READ_CAPACITY: + case START_STOP_UNIT: + case SYNCHRONIZE_CACHE: + case WRITE_10: + case 0x2f: /* VERIFY_10 is absent from + * scsi_all.h??? */ + case INQUIRY: + case MODE_SELECT_10: + case MODE_SENSE_10: + case TEST_UNIT_READY: + case WRITE_BUFFER: + /* + * The following commands are not listed in my copy of the + * RBC specs. CAM however seems to want those, and at least + * the Sony DSC device appears to support those as well + */ + case REQUEST_SENSE: + case PREVENT_ALLOW: + + memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); + + if ((sc->sc_quirks & RBC_PAD_TO_12) && (cmd_len < 12)) { + memset(sc->sc_transfer.cmd_data + cmd_len, + 0, 12 - cmd_len); + cmd_len = 12; + } + sc->sc_transfer.cmd_len = cmd_len; + return (1); /* sucess */ + + /* All other commands are not legal in RBC */ + default: + DPRINTF(sc, UDMASS_SCSI, "Unsupported RBC " + "command 0x%02x\n", cmd_ptr[0]); + return (0); /* failure */ + } +} + +static uint8_t +umass_ufi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, + uint8_t cmd_len) +{ + if ((cmd_len == 0) || + (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { + DPRINTF(sc, UDMASS_SCSI, "Invalid command " + "length: %d bytes\n", cmd_len); + return (0); /* failure */ + } + /* An UFI command is always 12 bytes in length */ + sc->sc_transfer.cmd_len = UFI_COMMAND_LENGTH; + + /* Zero the command data */ + memset(sc->sc_transfer.cmd_data, 0, UFI_COMMAND_LENGTH); + + switch (cmd_ptr[0]) { + /* + * Commands of which the format has been verified. They + * should work. Copy the command into the (zeroed out) + * destination buffer. + */ + case TEST_UNIT_READY: + if (sc->sc_quirks & NO_TEST_UNIT_READY) { + /* + * Some devices do not support this command. Start + * Stop Unit should give the same results + */ + DPRINTF(sc, UDMASS_UFI, "Converted TEST_UNIT_READY " + "to START_UNIT\n"); + + sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; + sc->sc_transfer.cmd_data[4] = SSS_START; + return (1); + } + break; + + case REZERO_UNIT: + case REQUEST_SENSE: + case FORMAT_UNIT: + case INQUIRY: + case START_STOP_UNIT: + case SEND_DIAGNOSTIC: + case PREVENT_ALLOW: + case READ_CAPACITY: + case READ_10: + case WRITE_10: + case POSITION_TO_ELEMENT: /* SEEK_10 */ + case WRITE_AND_VERIFY: + case VERIFY: + case MODE_SELECT_10: + case MODE_SENSE_10: + case READ_12: + case WRITE_12: + case READ_FORMAT_CAPACITIES: + break; + + /* + * SYNCHRONIZE_CACHE isn't supported by UFI, nor should it be + * required for UFI devices, so it is appropriate to fake + * success. + */ + case SYNCHRONIZE_CACHE: + return (2); + + default: + DPRINTF(sc, UDMASS_SCSI, "Unsupported UFI " + "command 0x%02x\n", cmd_ptr[0]); + return (0); /* failure */ + } + + memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); + return (1); /* success */ +} + +/* + * 8070i (ATAPI) specific functions + */ +static uint8_t +umass_atapi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, + uint8_t cmd_len) +{ + if ((cmd_len == 0) || + (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { + DPRINTF(sc, UDMASS_SCSI, "Invalid command " + "length: %d bytes\n", cmd_len); + return (0); /* failure */ + } + /* An ATAPI command is always 12 bytes in length. */ + sc->sc_transfer.cmd_len = ATAPI_COMMAND_LENGTH; + + /* Zero the command data */ + memset(sc->sc_transfer.cmd_data, 0, ATAPI_COMMAND_LENGTH); + + switch (cmd_ptr[0]) { + /* + * Commands of which the format has been verified. They + * should work. Copy the command into the destination + * buffer. + */ + case INQUIRY: + /* + * some drives wedge when asked for full inquiry + * information. + */ + if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { + memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); + + sc->sc_transfer.cmd_data[4] = SHORT_INQUIRY_LENGTH; + return (1); + } + break; + + case TEST_UNIT_READY: + if (sc->sc_quirks & NO_TEST_UNIT_READY) { + DPRINTF(sc, UDMASS_SCSI, "Converted TEST_UNIT_READY " + "to START_UNIT\n"); + sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; + sc->sc_transfer.cmd_data[4] = SSS_START; + return (1); + } + break; + + case REZERO_UNIT: + case REQUEST_SENSE: + case START_STOP_UNIT: + case SEND_DIAGNOSTIC: + case PREVENT_ALLOW: + case READ_CAPACITY: + case READ_10: + case WRITE_10: + case POSITION_TO_ELEMENT: /* SEEK_10 */ + case SYNCHRONIZE_CACHE: + case MODE_SELECT_10: + case MODE_SENSE_10: + case READ_BUFFER: + case 0x42: /* READ_SUBCHANNEL */ + case 0x43: /* READ_TOC */ + case 0x44: /* READ_HEADER */ + case 0x47: /* PLAY_MSF (Play Minute/Second/Frame) */ + case 0x48: /* PLAY_TRACK */ + case 0x49: /* PLAY_TRACK_REL */ + case 0x4b: /* PAUSE */ + case 0x51: /* READ_DISK_INFO */ + case 0x52: /* READ_TRACK_INFO */ + case 0x54: /* SEND_OPC */ + case 0x59: /* READ_MASTER_CUE */ + case 0x5b: /* CLOSE_TR_SESSION */ + case 0x5c: /* READ_BUFFER_CAP */ + case 0x5d: /* SEND_CUE_SHEET */ + case 0xa1: /* BLANK */ + case 0xa5: /* PLAY_12 */ + case 0xa6: /* EXCHANGE_MEDIUM */ + case 0xad: /* READ_DVD_STRUCTURE */ + case 0xbb: /* SET_CD_SPEED */ + case 0xe5: /* READ_TRACK_INFO_PHILIPS */ + break; + + case READ_12: + case WRITE_12: + default: + DPRINTF(sc, UDMASS_SCSI, "Unsupported ATAPI " + "command 0x%02x - trying anyway\n", + cmd_ptr[0]); + break; + } + + memcpy(sc->sc_transfer.cmd_data, cmd_ptr, cmd_len); + return (1); /* success */ +} + +static uint8_t +umass_no_transform(struct umass_softc *sc, uint8_t *cmd, + uint8_t cmdlen) +{ + return (0); /* failure */ +} + +static uint8_t +umass_std_transform(struct umass_softc *sc, union ccb *ccb, + uint8_t *cmd, uint8_t cmdlen) +{ + uint8_t retval; + + retval = (sc->sc_transform) (sc, cmd, cmdlen); + + if (retval == 2) { + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return (0); + } else if (retval == 0) { + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + return (0); + } + /* Command should be executed */ + return (1); +} + +#ifdef USB_DEBUG +static void +umass_bbb_dump_cbw(struct umass_softc *sc, umass_bbb_cbw_t *cbw) +{ + uint8_t *c = cbw->CBWCDB; + + uint32_t dlen = UGETDW(cbw->dCBWDataTransferLength); + uint32_t tag = UGETDW(cbw->dCBWTag); + + uint8_t clen = cbw->bCDBLength; + uint8_t flags = cbw->bCBWFlags; + uint8_t lun = cbw->bCBWLUN; + + DPRINTF(sc, UDMASS_BBB, "CBW %d: cmd = %db " + "(0x%02x%02x%02x%02x%02x%02x%s), " + "data = %db, lun = %d, dir = %s\n", + tag, clen, + c[0], c[1], c[2], c[3], c[4], c[5], (clen > 6 ? "..." : ""), + dlen, lun, (flags == CBWFLAGS_IN ? "in" : + (flags == CBWFLAGS_OUT ? "out" : ""))); +} + +static void +umass_bbb_dump_csw(struct umass_softc *sc, umass_bbb_csw_t *csw) +{ + uint32_t sig = UGETDW(csw->dCSWSignature); + uint32_t tag = UGETDW(csw->dCSWTag); + uint32_t res = UGETDW(csw->dCSWDataResidue); + uint8_t status = csw->bCSWStatus; + + DPRINTF(sc, UDMASS_BBB, "CSW %d: sig = 0x%08x (%s), tag = 0x%08x, " + "res = %d, status = 0x%02x (%s)\n", + tag, sig, (sig == CSWSIGNATURE ? "valid" : "invalid"), + tag, res, + status, (status == CSWSTATUS_GOOD ? "good" : + (status == CSWSTATUS_FAILED ? "failed" : + (status == CSWSTATUS_PHASE ? "phase" : "")))); +} + +static void +umass_cbi_dump_cmd(struct umass_softc *sc, void *cmd, uint8_t cmdlen) +{ + uint8_t *c = cmd; + uint8_t dir = sc->sc_transfer.dir; + + DPRINTF(sc, UDMASS_BBB, "cmd = %db " + "(0x%02x%02x%02x%02x%02x%02x%s), " + "data = %db, dir = %s\n", + cmdlen, + c[0], c[1], c[2], c[3], c[4], c[5], (cmdlen > 6 ? "..." : ""), + sc->sc_transfer.data_len, + (dir == DIR_IN ? "in" : + (dir == DIR_OUT ? "out" : + (dir == DIR_NONE ? "no data phase" : "")))); +} + +static void +umass_dump_buffer(struct umass_softc *sc, uint8_t *buffer, uint32_t buflen, + uint32_t printlen) +{ + uint32_t i, j; + char s1[40]; + char s2[40]; + char s3[5]; + + s1[0] = '\0'; + s3[0] = '\0'; + + sprintf(s2, " buffer=%p, buflen=%d", buffer, buflen); + for (i = 0; (i < buflen) && (i < printlen); i++) { + j = i % 16; + if (j == 0 && i != 0) { + DPRINTF(sc, UDMASS_GEN, "0x %s%s\n", + s1, s2); + s2[0] = '\0'; + } + sprintf(&s1[j * 2], "%02x", buffer[i] & 0xff); + } + if (buflen > printlen) + sprintf(s3, " ..."); + DPRINTF(sc, UDMASS_GEN, "0x %s%s%s\n", + s1, s2, s3); +} + +#endif diff --git a/sys/bus/u4b/storage/urio.c b/sys/bus/u4b/storage/urio.c new file mode 100644 index 0000000000..e79ae8720a --- /dev/null +++ b/sys/bus/u4b/storage/urio.c @@ -0,0 +1,495 @@ +/*- + * Copyright (c) 2000 Iwasa Kazmi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This code is based on ugen.c and ulpt.c developed by Lennart Augustsson. + * This code includes software developed by the NetBSD Foundation, Inc. and + * its contributors. + */ + +#include +__FBSDID("$FreeBSD$"); + + +/* + * 2000/3/24 added NetBSD/OpenBSD support (from Alex Nemirovsky) + * 2000/3/07 use two bulk-pipe handles for read and write (Dirk) + * 2000/3/06 change major number(143), and copyright header + * some fix for 4.0 (Dirk) + * 2000/3/05 codes for FreeBSD 4.x - CURRENT (Thanks to Dirk-Willem van Gulik) + * 2000/3/01 remove retry code from urioioctl() + * change method of bulk transfer (no interrupt) + * 2000/2/28 small fixes for new rio_usb.h + * 2000/2/24 first version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "usbdevs.h" + +#include +#include + +#define USB_DEBUG_VAR urio_debug +#include + +#include + +#ifdef USB_DEBUG +static int urio_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, urio, CTLFLAG_RW, 0, "USB urio"); +SYSCTL_INT(_hw_usb_urio, OID_AUTO, debug, CTLFLAG_RW, + &urio_debug, 0, "urio debug level"); +#endif + +#define URIO_T_WR 0 +#define URIO_T_RD 1 +#define URIO_T_WR_CS 2 +#define URIO_T_RD_CS 3 +#define URIO_T_MAX 4 + +#define URIO_BSIZE (1<<12) /* bytes */ +#define URIO_IFQ_MAXLEN 2 /* units */ + +struct urio_softc { + struct usb_fifo_sc sc_fifo; + struct mtx sc_mtx; + + struct usb_device *sc_udev; + struct usb_xfer *sc_xfer[URIO_T_MAX]; + + uint8_t sc_flags; +#define URIO_FLAG_READ_STALL 0x01 /* read transfer stalled */ +#define URIO_FLAG_WRITE_STALL 0x02 /* write transfer stalled */ + + uint8_t sc_name[16]; +}; + +/* prototypes */ + +static device_probe_t urio_probe; +static device_attach_t urio_attach; +static device_detach_t urio_detach; + +static usb_callback_t urio_write_callback; +static usb_callback_t urio_write_clear_stall_callback; +static usb_callback_t urio_read_callback; +static usb_callback_t urio_read_clear_stall_callback; + +static usb_fifo_close_t urio_close; +static usb_fifo_cmd_t urio_start_read; +static usb_fifo_cmd_t urio_start_write; +static usb_fifo_cmd_t urio_stop_read; +static usb_fifo_cmd_t urio_stop_write; +static usb_fifo_ioctl_t urio_ioctl; +static usb_fifo_open_t urio_open; + +static struct usb_fifo_methods urio_fifo_methods = { + .f_close = &urio_close, + .f_ioctl = &urio_ioctl, + .f_open = &urio_open, + .f_start_read = &urio_start_read, + .f_start_write = &urio_start_write, + .f_stop_read = &urio_stop_read, + .f_stop_write = &urio_stop_write, + .basename[0] = "urio", +}; + +static const struct usb_config urio_config[URIO_T_MAX] = { + [URIO_T_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = URIO_BSIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,.proxy_buffer = 1,}, + .callback = &urio_write_callback, + }, + + [URIO_T_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = URIO_BSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1,}, + .callback = &urio_read_callback, + }, + + [URIO_T_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &urio_write_clear_stall_callback, + .timeout = 1000, /* 1 second */ + .interval = 50, /* 50ms */ + }, + + [URIO_T_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &urio_read_clear_stall_callback, + .timeout = 1000, /* 1 second */ + .interval = 50, /* 50ms */ + }, +}; + +static devclass_t urio_devclass; + +static device_method_t urio_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, urio_probe), + DEVMETHOD(device_attach, urio_attach), + DEVMETHOD(device_detach, urio_detach), + {0, 0} +}; + +static driver_t urio_driver = { + .name = "urio", + .methods = urio_methods, + .size = sizeof(struct urio_softc), +}; + +DRIVER_MODULE(urio, uhub, urio_driver, urio_devclass, NULL, 0); +MODULE_DEPEND(urio, usb, 1, 1, 1); +MODULE_VERSION(urio, 1); + +static const STRUCT_USB_HOST_ID urio_devs[] = { + {USB_VPI(USB_VENDOR_DIAMOND, USB_PRODUCT_DIAMOND_RIO500USB, 0)}, + {USB_VPI(USB_VENDOR_DIAMOND2, USB_PRODUCT_DIAMOND2_RIO600USB, 0)}, + {USB_VPI(USB_VENDOR_DIAMOND2, USB_PRODUCT_DIAMOND2_RIO800USB, 0)}, +}; + +static int +urio_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != 0) + return (ENXIO); + if (uaa->info.bIfaceIndex != 0) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(urio_devs, sizeof(urio_devs), uaa)); +} + +static int +urio_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct urio_softc *sc = device_get_softc(dev); + int error; + + device_set_usb_desc(dev); + + sc->sc_udev = uaa->device; + + mtx_init(&sc->sc_mtx, "urio lock", NULL, MTX_DEF | MTX_RECURSE); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + error = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, + urio_config, URIO_T_MAX, sc, &sc->sc_mtx); + + if (error) { + DPRINTF("error=%s\n", usbd_errstr(error)); + goto detach; + } + + error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &urio_fifo_methods, &sc->sc_fifo, + device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + if (error) { + goto detach; + } + return (0); /* success */ + +detach: + urio_detach(dev); + return (ENOMEM); /* failure */ +} + +static void +urio_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct urio_softc *sc = usbd_xfer_softc(xfer); + struct usb_fifo *f = sc->sc_fifo.fp[USB_FIFO_TX]; + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: + if (sc->sc_flags & URIO_FLAG_WRITE_STALL) { + usbd_transfer_start(sc->sc_xfer[URIO_T_WR_CS]); + return; + } + pc = usbd_xfer_get_frame(xfer, 0); + if (usb_fifo_get_data(f, pc, 0, + usbd_xfer_max_len(xfer), &actlen, 0)) { + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= URIO_FLAG_WRITE_STALL; + usbd_transfer_start(sc->sc_xfer[URIO_T_WR_CS]); + } + return; + } +} + +static void +urio_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct urio_softc *sc = usbd_xfer_softc(xfer); + struct usb_xfer *xfer_other = sc->sc_xfer[URIO_T_WR]; + + if (usbd_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~URIO_FLAG_WRITE_STALL; + usbd_transfer_start(xfer_other); + } +} + +static void +urio_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct urio_softc *sc = usbd_xfer_softc(xfer); + struct usb_fifo *f = sc->sc_fifo.fp[USB_FIFO_RX]; + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + usb_fifo_put_data(f, pc, 0, actlen, 1); + + case USB_ST_SETUP: + if (sc->sc_flags & URIO_FLAG_READ_STALL) { + usbd_transfer_start(sc->sc_xfer[URIO_T_RD_CS]); + return; + } + if (usb_fifo_put_bytes_max(f) != 0) { + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + sc->sc_flags |= URIO_FLAG_READ_STALL; + usbd_transfer_start(sc->sc_xfer[URIO_T_RD_CS]); + } + return; + } +} + +static void +urio_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct urio_softc *sc = usbd_xfer_softc(xfer); + struct usb_xfer *xfer_other = sc->sc_xfer[URIO_T_RD]; + + if (usbd_clear_stall_callback(xfer, xfer_other)) { + DPRINTF("stall cleared\n"); + sc->sc_flags &= ~URIO_FLAG_READ_STALL; + usbd_transfer_start(xfer_other); + } +} + +static void +urio_start_read(struct usb_fifo *fifo) +{ + struct urio_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_start(sc->sc_xfer[URIO_T_RD]); +} + +static void +urio_stop_read(struct usb_fifo *fifo) +{ + struct urio_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_stop(sc->sc_xfer[URIO_T_RD_CS]); + usbd_transfer_stop(sc->sc_xfer[URIO_T_RD]); +} + +static void +urio_start_write(struct usb_fifo *fifo) +{ + struct urio_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_start(sc->sc_xfer[URIO_T_WR]); +} + +static void +urio_stop_write(struct usb_fifo *fifo) +{ + struct urio_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_stop(sc->sc_xfer[URIO_T_WR_CS]); + usbd_transfer_stop(sc->sc_xfer[URIO_T_WR]); +} + +static int +urio_open(struct usb_fifo *fifo, int fflags) +{ + struct urio_softc *sc = usb_fifo_softc(fifo); + + if (fflags & FREAD) { + /* clear stall first */ + mtx_lock(&sc->sc_mtx); + sc->sc_flags |= URIO_FLAG_READ_STALL; + mtx_unlock(&sc->sc_mtx); + + if (usb_fifo_alloc_buffer(fifo, + usbd_xfer_max_len(sc->sc_xfer[URIO_T_RD]), + URIO_IFQ_MAXLEN)) { + return (ENOMEM); + } + } + if (fflags & FWRITE) { + /* clear stall first */ + sc->sc_flags |= URIO_FLAG_WRITE_STALL; + + if (usb_fifo_alloc_buffer(fifo, + usbd_xfer_max_len(sc->sc_xfer[URIO_T_WR]), + URIO_IFQ_MAXLEN)) { + return (ENOMEM); + } + } + return (0); /* success */ +} + +static void +urio_close(struct usb_fifo *fifo, int fflags) +{ + if (fflags & (FREAD | FWRITE)) { + usb_fifo_free_buffer(fifo); + } +} + +static int +urio_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, + int fflags) +{ + struct usb_ctl_request ur; + struct RioCommand *rio_cmd; + int error; + + switch (cmd) { + case RIO_RECV_COMMAND: + if (!(fflags & FWRITE)) { + error = EPERM; + goto done; + } + memset(&ur, 0, sizeof(ur)); + rio_cmd = addr; + ur.ucr_request.bmRequestType = + rio_cmd->requesttype | UT_READ_VENDOR_DEVICE; + break; + + case RIO_SEND_COMMAND: + if (!(fflags & FWRITE)) { + error = EPERM; + goto done; + } + memset(&ur, 0, sizeof(ur)); + rio_cmd = addr; + ur.ucr_request.bmRequestType = + rio_cmd->requesttype | UT_WRITE_VENDOR_DEVICE; + break; + + default: + error = EINVAL; + goto done; + } + + DPRINTFN(2, "Sending command\n"); + + /* Send rio control message */ + ur.ucr_request.bRequest = rio_cmd->request; + USETW(ur.ucr_request.wValue, rio_cmd->value); + USETW(ur.ucr_request.wIndex, rio_cmd->index); + USETW(ur.ucr_request.wLength, rio_cmd->length); + ur.ucr_data = rio_cmd->buffer; + + /* reuse generic USB code */ + error = ugen_do_request(fifo, &ur); + +done: + return (error); +} + +static int +urio_detach(device_t dev) +{ + struct urio_softc *sc = device_get_softc(dev); + + DPRINTF("\n"); + + usb_fifo_detach(&sc->sc_fifo); + + usbd_transfer_unsetup(sc->sc_xfer, URIO_T_MAX); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} diff --git a/sys/bus/u4b/storage/ustorage_fs.c b/sys/bus/u4b/storage/ustorage_fs.c new file mode 100644 index 0000000000..dd6b413d34 --- /dev/null +++ b/sys/bus/u4b/storage/ustorage_fs.c @@ -0,0 +1,1947 @@ +/* $FreeBSD$ */ +/*- + * Copyright (C) 2003-2005 Alan Stern + * Copyright (C) 2008 Hans Petter Selasky + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * NOTE: Much of the SCSI statemachine handling code derives from the + * Linux USB gadget stack. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "usbdevs.h" +#include "usb_if.h" + +#define USB_DEBUG_VAR ustorage_fs_debug +#include + +#ifdef USB_DEBUG +static int ustorage_fs_debug = 0; + +SYSCTL_NODE(_hw_usb, OID_AUTO, ustorage_fs, CTLFLAG_RW, 0, "USB ustorage_fs"); +SYSCTL_INT(_hw_usb_ustorage_fs, OID_AUTO, debug, CTLFLAG_RW, + &ustorage_fs_debug, 0, "ustorage_fs debug level"); +#endif + +/* Define some limits */ + +#ifndef USTORAGE_FS_BULK_SIZE +#define USTORAGE_FS_BULK_SIZE (1UL << 17) /* bytes */ +#endif + +#ifndef USTORAGE_FS_MAX_LUN +#define USTORAGE_FS_MAX_LUN 8 /* units */ +#endif + +#ifndef USTORAGE_QDATA_MAX +#define USTORAGE_QDATA_MAX 40 /* bytes */ +#endif + +#define sc_cmd_data sc_cbw.CBWCDB + +/* + * The SCSI ID string must be exactly 28 characters long + * exluding the terminating zero. + */ +#ifndef USTORAGE_FS_ID_STRING +#define USTORAGE_FS_ID_STRING \ + "FreeBSD " /* 8 */ \ + "File-Stor Gadget" /* 16 */ \ + "0101" /* 4 */ +#endif + +/* + * The following macro defines the number of + * sectors to be allocated for the RAM disk: + */ +#ifndef USTORAGE_FS_RAM_SECT +#define USTORAGE_FS_RAM_SECT (1UL << 13) +#endif + +static uint8_t *ustorage_fs_ramdisk; + +/* USB transfer definitions */ + +#define USTORAGE_FS_T_BBB_COMMAND 0 +#define USTORAGE_FS_T_BBB_DATA_DUMP 1 +#define USTORAGE_FS_T_BBB_DATA_READ 2 +#define USTORAGE_FS_T_BBB_DATA_WRITE 3 +#define USTORAGE_FS_T_BBB_STATUS 4 +#define USTORAGE_FS_T_BBB_MAX 5 + +/* USB data stage direction */ + +#define DIR_NONE 0 +#define DIR_READ 1 +#define DIR_WRITE 2 + +/* USB interface specific control request */ + +#define UR_BBB_RESET 0xff /* Bulk-Only reset */ +#define UR_BBB_GET_MAX_LUN 0xfe /* Get maximum lun */ + +/* Command Block Wrapper */ +typedef struct { + uDWord dCBWSignature; +#define CBWSIGNATURE 0x43425355 + uDWord dCBWTag; + uDWord dCBWDataTransferLength; + uByte bCBWFlags; +#define CBWFLAGS_OUT 0x00 +#define CBWFLAGS_IN 0x80 + uByte bCBWLUN; + uByte bCDBLength; +#define CBWCDBLENGTH 16 + uByte CBWCDB[CBWCDBLENGTH]; +} __packed ustorage_fs_bbb_cbw_t; + +#define USTORAGE_FS_BBB_CBW_SIZE 31 + +/* Command Status Wrapper */ +typedef struct { + uDWord dCSWSignature; +#define CSWSIGNATURE 0x53425355 + uDWord dCSWTag; + uDWord dCSWDataResidue; + uByte bCSWStatus; +#define CSWSTATUS_GOOD 0x0 +#define CSWSTATUS_FAILED 0x1 +#define CSWSTATUS_PHASE 0x2 +} __packed ustorage_fs_bbb_csw_t; + +#define USTORAGE_FS_BBB_CSW_SIZE 13 + +struct ustorage_fs_lun { + + uint8_t *memory_image; + + uint32_t num_sectors; + uint32_t sense_data; + uint32_t sense_data_info; + uint32_t unit_attention_data; + + uint8_t read_only:1; + uint8_t prevent_medium_removal:1; + uint8_t info_valid:1; + uint8_t removable:1; +}; + +struct ustorage_fs_softc { + + ustorage_fs_bbb_cbw_t sc_cbw; /* Command Wrapper Block */ + ustorage_fs_bbb_csw_t sc_csw; /* Command Status Block */ + + struct mtx sc_mtx; + + struct ustorage_fs_lun sc_lun[USTORAGE_FS_MAX_LUN]; + + struct { + uint8_t *data_ptr; + struct ustorage_fs_lun *currlun; + + uint32_t data_rem; /* bytes, as reported by the command + * block wrapper */ + uint32_t offset; /* bytes */ + + uint8_t cbw_dir; + uint8_t cmd_dir; + uint8_t lun; + uint8_t cmd_len; + uint8_t data_short:1; + uint8_t data_error:1; + } sc_transfer; + + device_t sc_dev; + struct usb_device *sc_udev; + struct usb_xfer *sc_xfer[USTORAGE_FS_T_BBB_MAX]; + + uint8_t sc_iface_no; /* interface number */ + uint8_t sc_last_lun; + uint8_t sc_last_xfer_index; + uint8_t sc_qdata[USTORAGE_QDATA_MAX]; +}; + +/* prototypes */ + +static device_probe_t ustorage_fs_probe; +static device_attach_t ustorage_fs_attach; +static device_detach_t ustorage_fs_detach; +static device_suspend_t ustorage_fs_suspend; +static device_resume_t ustorage_fs_resume; +static usb_handle_request_t ustorage_fs_handle_request; + +static usb_callback_t ustorage_fs_t_bbb_command_callback; +static usb_callback_t ustorage_fs_t_bbb_data_dump_callback; +static usb_callback_t ustorage_fs_t_bbb_data_read_callback; +static usb_callback_t ustorage_fs_t_bbb_data_write_callback; +static usb_callback_t ustorage_fs_t_bbb_status_callback; + +static void ustorage_fs_transfer_start(struct ustorage_fs_softc *sc, uint8_t xfer_index); +static void ustorage_fs_transfer_stop(struct ustorage_fs_softc *sc); + +static uint8_t ustorage_fs_verify(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_inquiry(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_request_sense(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_read_capacity(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_mode_sense(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_start_stop(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_prevent_allow(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_read_format_capacities(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_mode_select(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_min_len(struct ustorage_fs_softc *sc, uint32_t len, uint32_t mask); +static uint8_t ustorage_fs_read(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_write(struct ustorage_fs_softc *sc); +static uint8_t ustorage_fs_check_cmd(struct ustorage_fs_softc *sc, uint8_t cmd_size, uint16_t mask, uint8_t needs_medium); +static uint8_t ustorage_fs_do_cmd(struct ustorage_fs_softc *sc); + +static device_method_t ustorage_fs_methods[] = { + /* USB interface */ + DEVMETHOD(usb_handle_request, ustorage_fs_handle_request), + + /* Device interface */ + DEVMETHOD(device_probe, ustorage_fs_probe), + DEVMETHOD(device_attach, ustorage_fs_attach), + DEVMETHOD(device_detach, ustorage_fs_detach), + DEVMETHOD(device_suspend, ustorage_fs_suspend), + DEVMETHOD(device_resume, ustorage_fs_resume), + + {0, 0} +}; + +static driver_t ustorage_fs_driver = { + .name = "ustorage_fs", + .methods = ustorage_fs_methods, + .size = sizeof(struct ustorage_fs_softc), +}; + +static devclass_t ustorage_fs_devclass; + +DRIVER_MODULE(ustorage_fs, uhub, ustorage_fs_driver, ustorage_fs_devclass, NULL, 0); +MODULE_VERSION(ustorage_fs, 0); +MODULE_DEPEND(ustorage_fs, usb, 1, 1, 1); + +static struct usb_config ustorage_fs_bbb_config[USTORAGE_FS_T_BBB_MAX] = { + + [USTORAGE_FS_T_BBB_COMMAND] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = sizeof(ustorage_fs_bbb_cbw_t), + .flags = {.ext_buffer = 1,}, + .callback = &ustorage_fs_t_bbb_command_callback, + .usb_mode = USB_MODE_DEVICE, + }, + + [USTORAGE_FS_T_BBB_DATA_DUMP] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = 0, /* use wMaxPacketSize */ + .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,}, + .callback = &ustorage_fs_t_bbb_data_dump_callback, + .usb_mode = USB_MODE_DEVICE, + }, + + [USTORAGE_FS_T_BBB_DATA_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = USTORAGE_FS_BULK_SIZE, + .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer = 1}, + .callback = &ustorage_fs_t_bbb_data_read_callback, + .usb_mode = USB_MODE_DEVICE, + }, + + [USTORAGE_FS_T_BBB_DATA_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = USTORAGE_FS_BULK_SIZE, + .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer = 1}, + .callback = &ustorage_fs_t_bbb_data_write_callback, + .usb_mode = USB_MODE_DEVICE, + }, + + [USTORAGE_FS_T_BBB_STATUS] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = sizeof(ustorage_fs_bbb_csw_t), + .flags = {.short_xfer_ok = 1,.ext_buffer = 1,}, + .callback = &ustorage_fs_t_bbb_status_callback, + .usb_mode = USB_MODE_DEVICE, + }, +}; + +/* + * USB device probe/attach/detach + */ + +static int +ustorage_fs_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct usb_interface_descriptor *id; + + if (uaa->usb_mode != USB_MODE_DEVICE) { + return (ENXIO); + } + /* Check for a standards compliant device */ + id = usbd_get_interface_descriptor(uaa->iface); + if ((id == NULL) || + (id->bInterfaceClass != UICLASS_MASS) || + (id->bInterfaceSubClass != UISUBCLASS_SCSI) || + (id->bInterfaceProtocol != UIPROTO_MASS_BBB)) { + return (ENXIO); + } + return (BUS_PROBE_GENERIC); +} + +static int +ustorage_fs_attach(device_t dev) +{ + struct ustorage_fs_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct usb_interface_descriptor *id; + int err; + int unit; + + /* + * NOTE: the softc struct is cleared in device_set_driver. + * We can safely call ustorage_fs_detach without specifically + * initializing the struct. + */ + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + unit = device_get_unit(dev); + + /* enable power saving mode */ + usbd_set_power_mode(uaa->device, USB_POWER_MODE_SAVE); + + if (unit == 0) { + if (ustorage_fs_ramdisk == NULL) { + /* + * allocate a memory image for our ramdisk until + * further + */ + ustorage_fs_ramdisk = + malloc(USTORAGE_FS_RAM_SECT << 9, M_USB, + M_ZERO | M_WAITOK); + + if (ustorage_fs_ramdisk == NULL) { + return (ENOMEM); + } + } + sc->sc_lun[0].memory_image = ustorage_fs_ramdisk; + sc->sc_lun[0].num_sectors = USTORAGE_FS_RAM_SECT; + sc->sc_lun[0].removable = 1; + } + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, "USTORAGE_FS lock", + NULL, (MTX_DEF | MTX_RECURSE)); + + /* get interface index */ + + id = usbd_get_interface_descriptor(uaa->iface); + if (id == NULL) { + device_printf(dev, "failed to get " + "interface number\n"); + goto detach; + } + sc->sc_iface_no = id->bInterfaceNumber; + + err = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, ustorage_fs_bbb_config, + USTORAGE_FS_T_BBB_MAX, sc, &sc->sc_mtx); + if (err) { + device_printf(dev, "could not setup required " + "transfers, %s\n", usbd_errstr(err)); + goto detach; + } + /* start Mass Storage State Machine */ + + mtx_lock(&sc->sc_mtx); + ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_COMMAND); + mtx_unlock(&sc->sc_mtx); + + return (0); /* success */ + +detach: + ustorage_fs_detach(dev); + return (ENXIO); /* failure */ +} + +static int +ustorage_fs_detach(device_t dev) +{ + struct ustorage_fs_softc *sc = device_get_softc(dev); + + /* teardown our statemachine */ + + usbd_transfer_unsetup(sc->sc_xfer, USTORAGE_FS_T_BBB_MAX); + + mtx_destroy(&sc->sc_mtx); + + return (0); /* success */ +} + +static int +ustorage_fs_suspend(device_t dev) +{ + device_printf(dev, "suspending\n"); + return (0); /* success */ +} + +static int +ustorage_fs_resume(device_t dev) +{ + device_printf(dev, "resuming\n"); + return (0); /* success */ +} + +/* + * Generic functions to handle transfers + */ + +static void +ustorage_fs_transfer_start(struct ustorage_fs_softc *sc, uint8_t xfer_index) +{ + if (sc->sc_xfer[xfer_index]) { + sc->sc_last_xfer_index = xfer_index; + usbd_transfer_start(sc->sc_xfer[xfer_index]); + } +} + +static void +ustorage_fs_transfer_stop(struct ustorage_fs_softc *sc) +{ + usbd_transfer_stop(sc->sc_xfer[sc->sc_last_xfer_index]); + mtx_unlock(&sc->sc_mtx); + usbd_transfer_drain(sc->sc_xfer[sc->sc_last_xfer_index]); + mtx_lock(&sc->sc_mtx); +} + +static int +ustorage_fs_handle_request(device_t dev, + const void *preq, void **pptr, uint16_t *plen, + uint16_t offset, uint8_t *pstate) +{ + struct ustorage_fs_softc *sc = device_get_softc(dev); + const struct usb_device_request *req = preq; + uint8_t is_complete = *pstate; + + if (!is_complete) { + if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && + (req->bRequest == UR_BBB_RESET)) { + *plen = 0; + mtx_lock(&sc->sc_mtx); + ustorage_fs_transfer_stop(sc); + sc->sc_transfer.data_error = 1; + ustorage_fs_transfer_start(sc, + USTORAGE_FS_T_BBB_COMMAND); + mtx_unlock(&sc->sc_mtx); + return (0); + } else if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) && + (req->bRequest == UR_BBB_GET_MAX_LUN)) { + if (offset == 0) { + *plen = 1; + *pptr = &sc->sc_last_lun; + } else { + *plen = 0; + } + return (0); + } + } + return (ENXIO); /* use builtin handler */ +} + +static void +ustorage_fs_t_bbb_command_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer); + uint32_t tag; + uint8_t err = 0; + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + tag = UGETDW(sc->sc_cbw.dCBWSignature); + + if (tag != CBWSIGNATURE) { + /* do nothing */ + DPRINTF("invalid signature 0x%08x\n", tag); + break; + } + tag = UGETDW(sc->sc_cbw.dCBWTag); + + /* echo back tag */ + USETDW(sc->sc_csw.dCSWTag, tag); + + /* reset status */ + sc->sc_csw.bCSWStatus = 0; + + /* reset data offset, data length and data remainder */ + sc->sc_transfer.offset = 0; + sc->sc_transfer.data_rem = + UGETDW(sc->sc_cbw.dCBWDataTransferLength); + + /* reset data flags */ + sc->sc_transfer.data_short = 0; + + /* extract LUN */ + sc->sc_transfer.lun = sc->sc_cbw.bCBWLUN; + + if (sc->sc_transfer.data_rem == 0) { + sc->sc_transfer.cbw_dir = DIR_NONE; + } else { + if (sc->sc_cbw.bCBWFlags & CBWFLAGS_IN) { + sc->sc_transfer.cbw_dir = DIR_WRITE; + } else { + sc->sc_transfer.cbw_dir = DIR_READ; + } + } + + sc->sc_transfer.cmd_len = sc->sc_cbw.bCDBLength; + if ((sc->sc_transfer.cmd_len > sizeof(sc->sc_cbw.CBWCDB)) || + (sc->sc_transfer.cmd_len == 0)) { + /* just halt - this is invalid */ + DPRINTF("invalid command length %d bytes\n", + sc->sc_transfer.cmd_len); + break; + } + + err = ustorage_fs_do_cmd(sc); + if (err) { + /* got an error */ + DPRINTF("command failed\n"); + break; + } + if ((sc->sc_transfer.data_rem > 0) && + (sc->sc_transfer.cbw_dir != sc->sc_transfer.cmd_dir)) { + /* contradicting data transfer direction */ + err = 1; + DPRINTF("data direction mismatch\n"); + break; + } + switch (sc->sc_transfer.cbw_dir) { + case DIR_READ: + ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_DATA_READ); + break; + case DIR_WRITE: + ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_DATA_WRITE); + break; + default: + ustorage_fs_transfer_start(sc, + USTORAGE_FS_T_BBB_STATUS); + break; + } + break; + + case USB_ST_SETUP: +tr_setup: + if (sc->sc_transfer.data_error) { + sc->sc_transfer.data_error = 0; + usbd_xfer_set_stall(xfer); + DPRINTF("stall pipe\n"); + } + + usbd_xfer_set_frame_data(xfer, 0, &sc->sc_cbw, + sizeof(sc->sc_cbw)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTF("error\n"); + if (error == USB_ERR_CANCELLED) { + break; + } + /* If the pipe is already stalled, don't do another stall */ + if (!usbd_xfer_is_stalled(xfer)) + sc->sc_transfer.data_error = 1; + + /* try again */ + goto tr_setup; + } + if (err) { + if (sc->sc_csw.bCSWStatus == 0) { + /* set some default error code */ + sc->sc_csw.bCSWStatus = CSWSTATUS_FAILED; + } + if (sc->sc_transfer.cbw_dir == DIR_READ) { + /* dump all data */ + ustorage_fs_transfer_start(sc, + USTORAGE_FS_T_BBB_DATA_DUMP); + return; + } + if (sc->sc_transfer.cbw_dir == DIR_WRITE) { + /* need to stall before status */ + sc->sc_transfer.data_error = 1; + } + ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_STATUS); + } +} + +static void +ustorage_fs_t_bbb_data_dump_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer); + uint32_t max_bulk = usbd_xfer_max_len(xfer); + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->sc_transfer.data_rem -= actlen; + sc->sc_transfer.offset += actlen; + + if (actlen != sumlen || sc->sc_transfer.data_rem == 0) { + /* short transfer or end of data */ + ustorage_fs_transfer_start(sc, + USTORAGE_FS_T_BBB_STATUS); + break; + } + /* Fallthrough */ + + case USB_ST_SETUP: +tr_setup: + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + if (sc->sc_transfer.data_error) { + sc->sc_transfer.data_error = 0; + usbd_xfer_set_stall(xfer); + } + usbd_xfer_set_frame_len(xfer, 0, max_bulk); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) { + break; + } + /* + * If the pipe is already stalled, don't do another stall: + */ + if (!usbd_xfer_is_stalled(xfer)) + sc->sc_transfer.data_error = 1; + + /* try again */ + goto tr_setup; + } +} + +static void +ustorage_fs_t_bbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer); + uint32_t max_bulk = usbd_xfer_max_len(xfer); + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->sc_transfer.data_rem -= actlen; + sc->sc_transfer.data_ptr += actlen; + sc->sc_transfer.offset += actlen; + + if (actlen != sumlen || sc->sc_transfer.data_rem == 0) { + /* short transfer or end of data */ + ustorage_fs_transfer_start(sc, + USTORAGE_FS_T_BBB_STATUS); + break; + } + /* Fallthrough */ + + case USB_ST_SETUP: +tr_setup: + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + if (sc->sc_transfer.data_error) { + sc->sc_transfer.data_error = 0; + usbd_xfer_set_stall(xfer); + } + + usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, + max_bulk); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) { + break; + } + /* If the pipe is already stalled, don't do another stall */ + if (!usbd_xfer_is_stalled(xfer)) + sc->sc_transfer.data_error = 1; + + /* try again */ + goto tr_setup; + } +} + +static void +ustorage_fs_t_bbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer); + uint32_t max_bulk = usbd_xfer_max_len(xfer); + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->sc_transfer.data_rem -= actlen; + sc->sc_transfer.data_ptr += actlen; + sc->sc_transfer.offset += actlen; + + if (actlen != sumlen || sc->sc_transfer.data_rem == 0) { + /* short transfer or end of data */ + ustorage_fs_transfer_start(sc, + USTORAGE_FS_T_BBB_STATUS); + break; + } + case USB_ST_SETUP: +tr_setup: + if (max_bulk >= sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + if (sc->sc_transfer.data_short) + usbd_xfer_set_flag(xfer, USB_FORCE_SHORT_XFER); + else + usbd_xfer_clr_flag(xfer, USB_FORCE_SHORT_XFER); + } else + usbd_xfer_clr_flag(xfer, USB_FORCE_SHORT_XFER); + + if (sc->sc_transfer.data_error) { + sc->sc_transfer.data_error = 0; + usbd_xfer_set_stall(xfer); + } + + usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, + max_bulk); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) { + break; + } + /* + * If the pipe is already stalled, don't do another + * stall + */ + if (!usbd_xfer_is_stalled(xfer)) + sc->sc_transfer.data_error = 1; + + /* try again */ + goto tr_setup; + } +} + +static void +ustorage_fs_t_bbb_status_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer); + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_COMMAND); + break; + + case USB_ST_SETUP: +tr_setup: + USETDW(sc->sc_csw.dCSWSignature, CSWSIGNATURE); + USETDW(sc->sc_csw.dCSWDataResidue, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_error) { + sc->sc_transfer.data_error = 0; + usbd_xfer_set_stall(xfer); + } + + usbd_xfer_set_frame_data(xfer, 0, &sc->sc_csw, + sizeof(sc->sc_csw)); + usbd_transfer_submit(xfer); + break; + + default: + if (error == USB_ERR_CANCELLED) { + break; + } + /* If the pipe is already stalled, don't do another stall */ + if (!usbd_xfer_is_stalled(xfer)) + sc->sc_transfer.data_error = 1; + + /* try again */ + goto tr_setup; + } +} + +/* SCSI commands that we recognize */ +#define SC_FORMAT_UNIT 0x04 +#define SC_INQUIRY 0x12 +#define SC_MODE_SELECT_6 0x15 +#define SC_MODE_SELECT_10 0x55 +#define SC_MODE_SENSE_6 0x1a +#define SC_MODE_SENSE_10 0x5a +#define SC_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1e +#define SC_READ_6 0x08 +#define SC_READ_10 0x28 +#define SC_READ_12 0xa8 +#define SC_READ_CAPACITY 0x25 +#define SC_READ_FORMAT_CAPACITIES 0x23 +#define SC_RELEASE 0x17 +#define SC_REQUEST_SENSE 0x03 +#define SC_RESERVE 0x16 +#define SC_SEND_DIAGNOSTIC 0x1d +#define SC_START_STOP_UNIT 0x1b +#define SC_SYNCHRONIZE_CACHE 0x35 +#define SC_TEST_UNIT_READY 0x00 +#define SC_VERIFY 0x2f +#define SC_WRITE_6 0x0a +#define SC_WRITE_10 0x2a +#define SC_WRITE_12 0xaa + +/* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */ +#define SS_NO_SENSE 0 +#define SS_COMMUNICATION_FAILURE 0x040800 +#define SS_INVALID_COMMAND 0x052000 +#define SS_INVALID_FIELD_IN_CDB 0x052400 +#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x052100 +#define SS_LOGICAL_UNIT_NOT_SUPPORTED 0x052500 +#define SS_MEDIUM_NOT_PRESENT 0x023a00 +#define SS_MEDIUM_REMOVAL_PREVENTED 0x055302 +#define SS_NOT_READY_TO_READY_TRANSITION 0x062800 +#define SS_RESET_OCCURRED 0x062900 +#define SS_SAVING_PARAMETERS_NOT_SUPPORTED 0x053900 +#define SS_UNRECOVERED_READ_ERROR 0x031100 +#define SS_WRITE_ERROR 0x030c02 +#define SS_WRITE_PROTECTED 0x072700 + +#define SK(x) ((uint8_t) ((x) >> 16)) /* Sense Key byte, etc. */ +#define ASC(x) ((uint8_t) ((x) >> 8)) +#define ASCQ(x) ((uint8_t) (x)) + +/* Routines for unaligned data access */ + +static uint16_t +get_be16(uint8_t *buf) +{ + return ((uint16_t)buf[0] << 8) | ((uint16_t)buf[1]); +} + +static uint32_t +get_be32(uint8_t *buf) +{ + return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | + ((uint32_t)buf[2] << 8) | ((uint32_t)buf[3]); +} + +static void +put_be16(uint8_t *buf, uint16_t val) +{ + buf[0] = val >> 8; + buf[1] = val; +} + +static void +put_be32(uint8_t *buf, uint32_t val) +{ + buf[0] = val >> 24; + buf[1] = val >> 16; + buf[2] = val >> 8; + buf[3] = val & 0xff; +} + +/*------------------------------------------------------------------------* + * ustorage_fs_verify + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_verify(struct ustorage_fs_softc *sc) +{ + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint32_t lba; + uint32_t vlen; + uint64_t file_offset; + uint64_t amount_left; + + /* + * Get the starting Logical Block Address + */ + lba = get_be32(&sc->sc_cmd_data[2]); + + /* + * We allow DPO (Disable Page Out = don't save data in the cache) + * but we don't implement it. + */ + if ((sc->sc_cmd_data[1] & ~0x10) != 0) { + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + vlen = get_be16(&sc->sc_cmd_data[7]); + if (vlen == 0) { + goto done; + } + /* No default reply */ + + /* Prepare to carry out the file verify */ + amount_left = vlen; + amount_left <<= 9; + file_offset = lba; + file_offset <<= 9; + + /* Range check */ + vlen += lba; + + if ((vlen < lba) || + (vlen > currlun->num_sectors) || + (lba >= currlun->num_sectors)) { + currlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return (1); + } + /* XXX TODO: verify that data is readable */ +done: + return (ustorage_fs_min_len(sc, 0, 0 - 1)); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_inquiry + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_inquiry(struct ustorage_fs_softc *sc) +{ + uint8_t *buf = sc->sc_transfer.data_ptr; + + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + + if (!sc->sc_transfer.currlun) { + /* Unsupported LUNs are okay */ + memset(buf, 0, 36); + buf[0] = 0x7f; + /* Unsupported, no device - type */ + return (ustorage_fs_min_len(sc, 36, 0 - 1)); + } + memset(buf, 0, 8); + /* Non - removable, direct - access device */ + if (currlun->removable) + buf[1] = 0x80; + buf[2] = 2; + /* ANSI SCSI level 2 */ + buf[3] = 2; + /* SCSI - 2 INQUIRY data format */ + buf[4] = 31; + /* Additional length */ + /* No special options */ + /* Copy in ID string */ + memcpy(buf + 8, USTORAGE_FS_ID_STRING, 28); + +#if (USTORAGE_QDATA_MAX < 36) +#error "(USTORAGE_QDATA_MAX < 36)" +#endif + return (ustorage_fs_min_len(sc, 36, 0 - 1)); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_request_sense + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_request_sense(struct ustorage_fs_softc *sc) +{ + uint8_t *buf = sc->sc_transfer.data_ptr; + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint32_t sd; + uint32_t sdinfo; + uint8_t valid; + + /* + * From the SCSI-2 spec., section 7.9 (Unit attention condition): + * + * If a REQUEST SENSE command is received from an initiator + * with a pending unit attention condition (before the target + * generates the contingent allegiance condition), then the + * target shall either: + * a) report any pending sense data and preserve the unit + * attention condition on the logical unit, or, + * b) report the unit attention condition, may discard any + * pending sense data, and clear the unit attention + * condition on the logical unit for that initiator. + * + * FSG normally uses option a); enable this code to use option b). + */ +#if 0 + if (currlun && currlun->unit_attention_data != SS_NO_SENSE) { + currlun->sense_data = currlun->unit_attention_data; + currlun->unit_attention_data = SS_NO_SENSE; + } +#endif + + if (!currlun) { + /* Unsupported LUNs are okay */ + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + sdinfo = 0; + valid = 0; + } else { + sd = currlun->sense_data; + sdinfo = currlun->sense_data_info; + valid = currlun->info_valid << 7; + currlun->sense_data = SS_NO_SENSE; + currlun->sense_data_info = 0; + currlun->info_valid = 0; + } + + memset(buf, 0, 18); + buf[0] = valid | 0x70; + /* Valid, current error */ + buf[2] = SK(sd); + put_be32(&buf[3], sdinfo); + /* Sense information */ + buf[7] = 18 - 8; + /* Additional sense length */ + buf[12] = ASC(sd); + buf[13] = ASCQ(sd); + +#if (USTORAGE_QDATA_MAX < 18) +#error "(USTORAGE_QDATA_MAX < 18)" +#endif + return (ustorage_fs_min_len(sc, 18, 0 - 1)); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_read_capacity + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_read_capacity(struct ustorage_fs_softc *sc) +{ + uint8_t *buf = sc->sc_transfer.data_ptr; + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint32_t lba = get_be32(&sc->sc_cmd_data[2]); + uint8_t pmi = sc->sc_cmd_data[8]; + + /* Check the PMI and LBA fields */ + if ((pmi > 1) || ((pmi == 0) && (lba != 0))) { + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + /* Max logical block */ + put_be32(&buf[0], currlun->num_sectors - 1); + /* Block length */ + put_be32(&buf[4], 512); + +#if (USTORAGE_QDATA_MAX < 8) +#error "(USTORAGE_QDATA_MAX < 8)" +#endif + return (ustorage_fs_min_len(sc, 8, 0 - 1)); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_mode_sense + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_mode_sense(struct ustorage_fs_softc *sc) +{ + uint8_t *buf = sc->sc_transfer.data_ptr; + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint8_t *buf0; + uint16_t len; + uint16_t limit; + uint8_t mscmnd = sc->sc_cmd_data[0]; + uint8_t pc; + uint8_t page_code; + uint8_t changeable_values; + uint8_t all_pages; + + buf0 = buf; + + if ((sc->sc_cmd_data[1] & ~0x08) != 0) { + /* Mask away DBD */ + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + pc = sc->sc_cmd_data[2] >> 6; + page_code = sc->sc_cmd_data[2] & 0x3f; + if (pc == 3) { + currlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED; + return (1); + } + changeable_values = (pc == 1); + all_pages = (page_code == 0x3f); + + /* + * Write the mode parameter header. Fixed values are: default + * medium type, no cache control (DPOFUA), and no block descriptors. + * The only variable value is the WriteProtect bit. We will fill in + * the mode data length later. + */ + memset(buf, 0, 8); + if (mscmnd == SC_MODE_SENSE_6) { + buf[2] = (currlun->read_only ? 0x80 : 0x00); + /* WP, DPOFUA */ + buf += 4; + limit = 255; + } else { + /* SC_MODE_SENSE_10 */ + buf[3] = (currlun->read_only ? 0x80 : 0x00); + /* WP, DPOFUA */ + buf += 8; + limit = 65535; + /* Should really be mod_data.buflen */ + } + + /* No block descriptors */ + + /* + * The mode pages, in numerical order. + */ + if ((page_code == 0x08) || all_pages) { + buf[0] = 0x08; + /* Page code */ + buf[1] = 10; + /* Page length */ + memset(buf + 2, 0, 10); + /* None of the fields are changeable */ + + if (!changeable_values) { + buf[2] = 0x04; + /* Write cache enable, */ + /* Read cache not disabled */ + /* No cache retention priorities */ + put_be16(&buf[4], 0xffff); + /* Don 't disable prefetch */ + /* Minimum prefetch = 0 */ + put_be16(&buf[8], 0xffff); + /* Maximum prefetch */ + put_be16(&buf[10], 0xffff); + /* Maximum prefetch ceiling */ + } + buf += 12; + } + /* + * Check that a valid page was requested and the mode data length + * isn't too long. + */ + len = buf - buf0; + if (len > limit) { + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + /* Store the mode data length */ + if (mscmnd == SC_MODE_SENSE_6) + buf0[0] = len - 1; + else + put_be16(buf0, len - 2); + +#if (USTORAGE_QDATA_MAX < 24) +#error "(USTORAGE_QDATA_MAX < 24)" +#endif + return (ustorage_fs_min_len(sc, len, 0 - 1)); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_start_stop + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_start_stop(struct ustorage_fs_softc *sc) +{ + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint8_t loej; + uint8_t start; + uint8_t immed; + + if (!currlun->removable) { + currlun->sense_data = SS_INVALID_COMMAND; + return (1); + } + immed = sc->sc_cmd_data[1] & 0x01; + loej = sc->sc_cmd_data[4] & 0x02; + start = sc->sc_cmd_data[4] & 0x01; + + if (immed || loej || start) { + /* compile fix */ + } + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_prevent_allow + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_prevent_allow(struct ustorage_fs_softc *sc) +{ + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint8_t prevent; + + if (!currlun->removable) { + currlun->sense_data = SS_INVALID_COMMAND; + return (1); + } + prevent = sc->sc_cmd_data[4] & 0x01; + if ((sc->sc_cmd_data[4] & ~0x01) != 0) { + /* Mask away Prevent */ + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + if (currlun->prevent_medium_removal && !prevent) { + //fsync_sub(currlun); + } + currlun->prevent_medium_removal = prevent; + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_read_format_capacities + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_read_format_capacities(struct ustorage_fs_softc *sc) +{ + uint8_t *buf = sc->sc_transfer.data_ptr; + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + + buf[0] = buf[1] = buf[2] = 0; + buf[3] = 8; + /* Only the Current / Maximum Capacity Descriptor */ + buf += 4; + + /* Number of blocks */ + put_be32(&buf[0], currlun->num_sectors); + /* Block length */ + put_be32(&buf[4], 512); + /* Current capacity */ + buf[4] = 0x02; + +#if (USTORAGE_QDATA_MAX < 12) +#error "(USTORAGE_QDATA_MAX < 12)" +#endif + return (ustorage_fs_min_len(sc, 12, 0 - 1)); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_mode_select + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_mode_select(struct ustorage_fs_softc *sc) +{ + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + + /* We don't support MODE SELECT */ + currlun->sense_data = SS_INVALID_COMMAND; + return (1); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_synchronize_cache + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_synchronize_cache(struct ustorage_fs_softc *sc) +{ +#if 0 + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint8_t rc; + + /* + * We ignore the requested LBA and write out all dirty data buffers. + */ + rc = 0; + if (rc) { + currlun->sense_data = SS_WRITE_ERROR; + } +#endif + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_read - read data from disk + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_read(struct ustorage_fs_softc *sc) +{ + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint64_t file_offset; + uint32_t lba; + uint32_t len; + + /* + * Get the starting Logical Block Address and check that it's not + * too big + */ + if (sc->sc_cmd_data[0] == SC_READ_6) { + lba = (((uint32_t)sc->sc_cmd_data[1]) << 16) | + get_be16(&sc->sc_cmd_data[2]); + } else { + lba = get_be32(&sc->sc_cmd_data[2]); + + /* + * We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = don't read from the + * cache), but we don't implement them. + */ + if ((sc->sc_cmd_data[1] & ~0x18) != 0) { + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + } + len = sc->sc_transfer.data_rem >> 9; + len += lba; + + if ((len < lba) || + (len > currlun->num_sectors) || + (lba >= currlun->num_sectors)) { + currlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return (1); + } + file_offset = lba; + file_offset <<= 9; + + sc->sc_transfer.data_ptr = currlun->memory_image + file_offset; + + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_write - write data to disk + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_write(struct ustorage_fs_softc *sc) +{ + struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun; + uint64_t file_offset; + uint32_t lba; + uint32_t len; + + if (currlun->read_only) { + currlun->sense_data = SS_WRITE_PROTECTED; + return (1); + } + /* XXX clear SYNC */ + + /* + * Get the starting Logical Block Address and check that it's not + * too big. + */ + if (sc->sc_cmd_data[0] == SC_WRITE_6) + lba = (((uint32_t)sc->sc_cmd_data[1]) << 16) | + get_be16(&sc->sc_cmd_data[2]); + else { + lba = get_be32(&sc->sc_cmd_data[2]); + + /* + * We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = write directly to the + * medium). We don't implement DPO; we implement FUA by + * performing synchronous output. + */ + if ((sc->sc_cmd_data[1] & ~0x18) != 0) { + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return (1); + } + if (sc->sc_cmd_data[1] & 0x08) { + /* FUA */ + /* XXX set SYNC flag here */ + } + } + + len = sc->sc_transfer.data_rem >> 9; + len += lba; + + if ((len < lba) || + (len > currlun->num_sectors) || + (lba >= currlun->num_sectors)) { + currlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return (1); + } + file_offset = lba; + file_offset <<= 9; + + sc->sc_transfer.data_ptr = currlun->memory_image + file_offset; + + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_min_len + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_min_len(struct ustorage_fs_softc *sc, uint32_t len, uint32_t mask) +{ + if (len != sc->sc_transfer.data_rem) { + + if (sc->sc_transfer.cbw_dir == DIR_READ) { + /* + * there must be something wrong about this SCSI + * command + */ + sc->sc_csw.bCSWStatus = CSWSTATUS_PHASE; + return (1); + } + /* compute the minimum length */ + + if (sc->sc_transfer.data_rem > len) { + /* data ends prematurely */ + sc->sc_transfer.data_rem = len; + sc->sc_transfer.data_short = 1; + } + /* check length alignment */ + + if (sc->sc_transfer.data_rem & ~mask) { + /* data ends prematurely */ + sc->sc_transfer.data_rem &= mask; + sc->sc_transfer.data_short = 1; + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_check_cmd - check command routine + * + * Check whether the command is properly formed and whether its data + * size and direction agree with the values we already have. + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_check_cmd(struct ustorage_fs_softc *sc, uint8_t min_cmd_size, + uint16_t mask, uint8_t needs_medium) +{ + struct ustorage_fs_lun *currlun; + uint8_t lun = (sc->sc_cmd_data[1] >> 5); + uint8_t i; + + /* Verify the length of the command itself */ + if (min_cmd_size > sc->sc_transfer.cmd_len) { + DPRINTF("%u > %u\n", + min_cmd_size, sc->sc_transfer.cmd_len); + sc->sc_csw.bCSWStatus = CSWSTATUS_PHASE; + return (1); + } + /* Mask away the LUN */ + sc->sc_cmd_data[1] &= 0x1f; + + /* Check if LUN is correct */ + if (lun != sc->sc_transfer.lun) { + + } + /* Check the LUN */ + if (sc->sc_transfer.lun <= sc->sc_last_lun) { + sc->sc_transfer.currlun = currlun = + sc->sc_lun + sc->sc_transfer.lun; + if (sc->sc_cmd_data[0] != SC_REQUEST_SENSE) { + currlun->sense_data = SS_NO_SENSE; + currlun->sense_data_info = 0; + currlun->info_valid = 0; + } + /* + * If a unit attention condition exists, only INQUIRY + * and REQUEST SENSE commands are allowed. Anything + * else must fail! + */ + if ((currlun->unit_attention_data != SS_NO_SENSE) && + (sc->sc_cmd_data[0] != SC_INQUIRY) && + (sc->sc_cmd_data[0] != SC_REQUEST_SENSE)) { + currlun->sense_data = currlun->unit_attention_data; + currlun->unit_attention_data = SS_NO_SENSE; + return (1); + } + } else { + sc->sc_transfer.currlun = currlun = NULL; + + /* + * INQUIRY and REQUEST SENSE commands are explicitly allowed + * to use unsupported LUNs; all others may not. + */ + if ((sc->sc_cmd_data[0] != SC_INQUIRY) && + (sc->sc_cmd_data[0] != SC_REQUEST_SENSE)) { + return (1); + } + } + + /* + * Check that only command bytes listed in the mask are + * non-zero. + */ + for (i = 0; i != min_cmd_size; i++) { + if (sc->sc_cmd_data[i] && !(mask & (1UL << i))) { + if (currlun) { + currlun->sense_data = SS_INVALID_FIELD_IN_CDB; + } + return (1); + } + } + + /* + * If the medium isn't mounted and the command needs to access + * it, return an error. + */ + if (currlun && (!currlun->memory_image) && needs_medium) { + currlun->sense_data = SS_MEDIUM_NOT_PRESENT; + return (1); + } + return (0); +} + +/*------------------------------------------------------------------------* + * ustorage_fs_do_cmd - do command + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +ustorage_fs_do_cmd(struct ustorage_fs_softc *sc) +{ + uint8_t error = 1; + uint8_t i; + uint32_t temp; + const uint32_t mask9 = (0xFFFFFFFFUL >> 9) << 9; + + /* set default data transfer pointer */ + sc->sc_transfer.data_ptr = sc->sc_qdata; + + DPRINTF("cmd_data[0]=0x%02x, data_rem=0x%08x\n", + sc->sc_cmd_data[0], sc->sc_transfer.data_rem); + + switch (sc->sc_cmd_data[0]) { + case SC_INQUIRY: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_min_len(sc, sc->sc_cmd_data[4], 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (1UL << 4) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_inquiry(sc); + + break; + + case SC_MODE_SELECT_6: + sc->sc_transfer.cmd_dir = DIR_READ; + error = ustorage_fs_min_len(sc, sc->sc_cmd_data[4], 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (1UL << 1) | (1UL << 4) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_mode_select(sc); + + break; + + case SC_MODE_SELECT_10: + sc->sc_transfer.cmd_dir = DIR_READ; + error = ustorage_fs_min_len(sc, + get_be16(&sc->sc_cmd_data[7]), 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (1UL << 1) | (3UL << 7) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_mode_select(sc); + + break; + + case SC_MODE_SENSE_6: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_min_len(sc, sc->sc_cmd_data[4], 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (1UL << 1) | (1UL << 2) | (1UL << 4) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_mode_sense(sc); + + break; + + case SC_MODE_SENSE_10: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_min_len(sc, + get_be16(&sc->sc_cmd_data[7]), 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (1UL << 1) | (1UL << 2) | (3UL << 7) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_mode_sense(sc); + + break; + + case SC_PREVENT_ALLOW_MEDIUM_REMOVAL: + error = ustorage_fs_min_len(sc, 0, 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (1UL << 4) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_prevent_allow(sc); + + break; + + case SC_READ_6: + i = sc->sc_cmd_data[4]; + sc->sc_transfer.cmd_dir = DIR_WRITE; + temp = ((i == 0) ? 256UL : i); + error = ustorage_fs_min_len(sc, temp << 9, mask9); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (7UL << 1) | (1UL << 4) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_read(sc); + + break; + + case SC_READ_10: + sc->sc_transfer.cmd_dir = DIR_WRITE; + temp = get_be16(&sc->sc_cmd_data[7]); + error = ustorage_fs_min_len(sc, temp << 9, mask9); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (1UL << 1) | (0xfUL << 2) | (3UL << 7) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_read(sc); + + break; + + case SC_READ_12: + sc->sc_transfer.cmd_dir = DIR_WRITE; + temp = get_be32(&sc->sc_cmd_data[6]); + if (temp >= (1UL << (32 - 9))) { + /* numerical overflow */ + sc->sc_csw.bCSWStatus = CSWSTATUS_FAILED; + error = 1; + break; + } + error = ustorage_fs_min_len(sc, temp << 9, mask9); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 12, + (1UL << 1) | (0xfUL << 2) | (0xfUL << 6) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_read(sc); + + break; + + case SC_READ_CAPACITY: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_check_cmd(sc, 10, + (0xfUL << 2) | (1UL << 8) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_read_capacity(sc); + + break; + + case SC_READ_FORMAT_CAPACITIES: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_min_len(sc, + get_be16(&sc->sc_cmd_data[7]), 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (3UL << 7) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_read_format_capacities(sc); + + break; + + case SC_REQUEST_SENSE: + sc->sc_transfer.cmd_dir = DIR_WRITE; + error = ustorage_fs_min_len(sc, sc->sc_cmd_data[4], 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (1UL << 4) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_request_sense(sc); + + break; + + case SC_START_STOP_UNIT: + error = ustorage_fs_min_len(sc, 0, 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (1UL << 1) | (1UL << 4) | 1, 0); + if (error) { + break; + } + error = ustorage_fs_start_stop(sc); + + break; + + case SC_SYNCHRONIZE_CACHE: + error = ustorage_fs_min_len(sc, 0, 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (0xfUL << 2) | (3UL << 7) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_synchronize_cache(sc); + + break; + + case SC_TEST_UNIT_READY: + error = ustorage_fs_min_len(sc, 0, 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + 0 | 1, 1); + break; + + /* + * Although optional, this command is used by MS-Windows. + * We support a minimal version: BytChk must be 0. + */ + case SC_VERIFY: + error = ustorage_fs_min_len(sc, 0, 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (1UL << 1) | (0xfUL << 2) | (3UL << 7) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_verify(sc); + + break; + + case SC_WRITE_6: + i = sc->sc_cmd_data[4]; + sc->sc_transfer.cmd_dir = DIR_READ; + temp = ((i == 0) ? 256UL : i); + error = ustorage_fs_min_len(sc, temp << 9, mask9); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 6, + (7UL << 1) | (1UL << 4) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_write(sc); + + break; + + case SC_WRITE_10: + sc->sc_transfer.cmd_dir = DIR_READ; + temp = get_be16(&sc->sc_cmd_data[7]); + error = ustorage_fs_min_len(sc, temp << 9, mask9); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 10, + (1UL << 1) | (0xfUL << 2) | (3UL << 7) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_write(sc); + + break; + + case SC_WRITE_12: + sc->sc_transfer.cmd_dir = DIR_READ; + temp = get_be32(&sc->sc_cmd_data[6]); + if (temp > (mask9 >> 9)) { + /* numerical overflow */ + sc->sc_csw.bCSWStatus = CSWSTATUS_FAILED; + error = 1; + break; + } + error = ustorage_fs_min_len(sc, temp << 9, mask9); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, 12, + (1UL << 1) | (0xfUL << 2) | (0xfUL << 6) | 1, 1); + if (error) { + break; + } + error = ustorage_fs_write(sc); + + break; + + /* + * Some mandatory commands that we recognize but don't + * implement. They don't mean much in this setting. + * It's left as an exercise for anyone interested to + * implement RESERVE and RELEASE in terms of Posix + * locks. + */ + case SC_FORMAT_UNIT: + case SC_RELEASE: + case SC_RESERVE: + case SC_SEND_DIAGNOSTIC: + /* Fallthrough */ + + default: + error = ustorage_fs_min_len(sc, 0, 0 - 1); + if (error) { + break; + } + error = ustorage_fs_check_cmd(sc, sc->sc_transfer.cmd_len, + 0xff, 0); + if (error) { + break; + } + sc->sc_transfer.currlun->sense_data = + SS_INVALID_COMMAND; + error = 1; + + break; + } + return (error); +} diff --git a/sys/bus/u4b/template/usb_template.c b/sys/bus/u4b/template/usb_template.c new file mode 100644 index 0000000000..b929665ead --- /dev/null +++ b/sys/bus/u4b/template/usb_template.c @@ -0,0 +1,1374 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2007 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains sub-routines to build up USB descriptors from + * USB templates. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb_debug +#include + +#include +#include +#include + +MODULE_DEPEND(usb_template, usb, 1, 1, 1); +MODULE_VERSION(usb_template, 1); + +/* function prototypes */ + +static void usb_make_raw_desc(struct usb_temp_setup *, const uint8_t *); +static void usb_make_endpoint_desc(struct usb_temp_setup *, + const struct usb_temp_endpoint_desc *); +static void usb_make_interface_desc(struct usb_temp_setup *, + const struct usb_temp_interface_desc *); +static void usb_make_config_desc(struct usb_temp_setup *, + const struct usb_temp_config_desc *); +static void usb_make_device_desc(struct usb_temp_setup *, + const struct usb_temp_device_desc *); +static uint8_t usb_hw_ep_match(const struct usb_hw_ep_profile *, uint8_t, + uint8_t); +static uint8_t usb_hw_ep_find_match(struct usb_hw_ep_scratch *, + struct usb_hw_ep_scratch_sub *, uint8_t); +static uint8_t usb_hw_ep_get_needs(struct usb_hw_ep_scratch *, uint8_t, + uint8_t); +static usb_error_t usb_hw_ep_resolve(struct usb_device *, + struct usb_descriptor *); +static const struct usb_temp_device_desc *usb_temp_get_tdd(struct usb_device *); +static void *usb_temp_get_device_desc(struct usb_device *); +static void *usb_temp_get_qualifier_desc(struct usb_device *); +static void *usb_temp_get_config_desc(struct usb_device *, uint16_t *, + uint8_t); +static const void *usb_temp_get_string_desc(struct usb_device *, uint16_t, + uint8_t); +static const void *usb_temp_get_vendor_desc(struct usb_device *, + const struct usb_device_request *, uint16_t *plen); +static const void *usb_temp_get_hub_desc(struct usb_device *); +static usb_error_t usb_temp_get_desc(struct usb_device *, + struct usb_device_request *, const void **, uint16_t *); +static usb_error_t usb_temp_setup_by_index(struct usb_device *, + uint16_t index); +static void usb_temp_init(void *); + +/*------------------------------------------------------------------------* + * usb_make_raw_desc + * + * This function will insert a raw USB descriptor into the generated + * USB configuration. + *------------------------------------------------------------------------*/ +static void +usb_make_raw_desc(struct usb_temp_setup *temp, + const uint8_t *raw) +{ + void *dst; + uint8_t len; + + /* + * The first byte of any USB descriptor gives the length. + */ + if (raw) { + len = raw[0]; + if (temp->buf) { + dst = USB_ADD_BYTES(temp->buf, temp->size); + memcpy(dst, raw, len); + + /* check if we have got a CDC union descriptor */ + + if ((raw[0] >= sizeof(struct usb_cdc_union_descriptor)) && + (raw[1] == UDESC_CS_INTERFACE) && + (raw[2] == UDESCSUB_CDC_UNION)) { + struct usb_cdc_union_descriptor *ud = (void *)dst; + + /* update the interface numbers */ + + ud->bMasterInterface += + temp->bInterfaceNumber; + ud->bSlaveInterface[0] += + temp->bInterfaceNumber; + } + + /* check if we have got an interface association descriptor */ + + if ((raw[0] >= sizeof(struct usb_interface_assoc_descriptor)) && + (raw[1] == UDESC_IFACE_ASSOC)) { + struct usb_interface_assoc_descriptor *iad = (void *)dst; + + /* update the interface number */ + + iad->bFirstInterface += + temp->bInterfaceNumber; + } + + /* check if we have got a call management descriptor */ + + if ((raw[0] >= sizeof(struct usb_cdc_cm_descriptor)) && + (raw[1] == UDESC_CS_INTERFACE) && + (raw[2] == UDESCSUB_CDC_CM)) { + struct usb_cdc_cm_descriptor *ccd = (void *)dst; + + /* update the interface number */ + + ccd->bDataInterface += + temp->bInterfaceNumber; + } + } + temp->size += len; + } +} + +/*------------------------------------------------------------------------* + * usb_make_endpoint_desc + * + * This function will generate an USB endpoint descriptor from the + * given USB template endpoint descriptor, which will be inserted into + * the USB configuration. + *------------------------------------------------------------------------*/ +static void +usb_make_endpoint_desc(struct usb_temp_setup *temp, + const struct usb_temp_endpoint_desc *ted) +{ + struct usb_endpoint_descriptor *ed; + const void **rd; + uint16_t old_size; + uint16_t mps; + uint8_t ea; /* Endpoint Address */ + uint8_t et; /* Endpiont Type */ + + /* Reserve memory */ + old_size = temp->size; + + ea = (ted->bEndpointAddress & (UE_ADDR | UE_DIR_IN | UE_DIR_OUT)); + et = (ted->bmAttributes & UE_XFERTYPE); + + if (et == UE_ISOCHRONOUS) { + /* account for extra byte fields */ + temp->size += sizeof(*ed) + 2; + } else { + temp->size += sizeof(*ed); + } + + /* Scan all Raw Descriptors first */ + rd = ted->ppRawDesc; + if (rd) { + while (*rd) { + usb_make_raw_desc(temp, *rd); + rd++; + } + } + if (ted->pPacketSize == NULL) { + /* not initialized */ + temp->err = USB_ERR_INVAL; + return; + } + mps = ted->pPacketSize->mps[temp->usb_speed]; + if (mps == 0) { + /* not initialized */ + temp->err = USB_ERR_INVAL; + return; + } else if (mps == UE_ZERO_MPS) { + /* escape for Zero Max Packet Size */ + mps = 0; + } + + /* + * Fill out the real USB endpoint descriptor + * in case there is a buffer present: + */ + if (temp->buf) { + ed = USB_ADD_BYTES(temp->buf, old_size); + if (et == UE_ISOCHRONOUS) + ed->bLength = sizeof(*ed) + 2; + else + ed->bLength = sizeof(*ed); + ed->bDescriptorType = UDESC_ENDPOINT; + ed->bEndpointAddress = ea; + ed->bmAttributes = ted->bmAttributes; + USETW(ed->wMaxPacketSize, mps); + + /* setup bInterval parameter */ + + if (ted->pIntervals && + ted->pIntervals->bInterval[temp->usb_speed]) { + ed->bInterval = + ted->pIntervals->bInterval[temp->usb_speed]; + } else { + switch (et) { + case UE_BULK: + case UE_CONTROL: + ed->bInterval = 0; /* not used */ + break; + case UE_INTERRUPT: + switch (temp->usb_speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + ed->bInterval = 1; /* 1 ms */ + break; + default: + ed->bInterval = 4; /* 1 ms */ + break; + } + break; + default: /* UE_ISOCHRONOUS */ + switch (temp->usb_speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + ed->bInterval = 1; /* 1 ms */ + break; + default: + ed->bInterval = 1; /* 125 us */ + break; + } + break; + } + } + } + temp->bNumEndpoints++; +} + +/*------------------------------------------------------------------------* + * usb_make_interface_desc + * + * This function will generate an USB interface descriptor from the + * given USB template interface descriptor, which will be inserted + * into the USB configuration. + *------------------------------------------------------------------------*/ +static void +usb_make_interface_desc(struct usb_temp_setup *temp, + const struct usb_temp_interface_desc *tid) +{ + struct usb_interface_descriptor *id; + const struct usb_temp_endpoint_desc **ted; + const void **rd; + uint16_t old_size; + + /* Reserve memory */ + + old_size = temp->size; + temp->size += sizeof(*id); + + /* Update interface and alternate interface numbers */ + + if (tid->isAltInterface == 0) { + temp->bAlternateSetting = 0; + temp->bInterfaceNumber++; + } else { + temp->bAlternateSetting++; + } + + /* Scan all Raw Descriptors first */ + + rd = tid->ppRawDesc; + + if (rd) { + while (*rd) { + usb_make_raw_desc(temp, *rd); + rd++; + } + } + /* Reset some counters */ + + temp->bNumEndpoints = 0; + + /* Scan all Endpoint Descriptors second */ + + ted = tid->ppEndpoints; + if (ted) { + while (*ted) { + usb_make_endpoint_desc(temp, *ted); + ted++; + } + } + /* + * Fill out the real USB interface descriptor + * in case there is a buffer present: + */ + if (temp->buf) { + id = USB_ADD_BYTES(temp->buf, old_size); + id->bLength = sizeof(*id); + id->bDescriptorType = UDESC_INTERFACE; + id->bInterfaceNumber = temp->bInterfaceNumber; + id->bAlternateSetting = temp->bAlternateSetting; + id->bNumEndpoints = temp->bNumEndpoints; + id->bInterfaceClass = tid->bInterfaceClass; + id->bInterfaceSubClass = tid->bInterfaceSubClass; + id->bInterfaceProtocol = tid->bInterfaceProtocol; + id->iInterface = tid->iInterface; + } +} + +/*------------------------------------------------------------------------* + * usb_make_config_desc + * + * This function will generate an USB config descriptor from the given + * USB template config descriptor, which will be inserted into the USB + * configuration. + *------------------------------------------------------------------------*/ +static void +usb_make_config_desc(struct usb_temp_setup *temp, + const struct usb_temp_config_desc *tcd) +{ + struct usb_config_descriptor *cd; + const struct usb_temp_interface_desc **tid; + uint16_t old_size; + + /* Reserve memory */ + + old_size = temp->size; + temp->size += sizeof(*cd); + + /* Reset some counters */ + + temp->bInterfaceNumber = 0 - 1; + temp->bAlternateSetting = 0; + + /* Scan all the USB interfaces */ + + tid = tcd->ppIfaceDesc; + if (tid) { + while (*tid) { + usb_make_interface_desc(temp, *tid); + tid++; + } + } + /* + * Fill out the real USB config descriptor + * in case there is a buffer present: + */ + if (temp->buf) { + cd = USB_ADD_BYTES(temp->buf, old_size); + + /* compute total size */ + old_size = temp->size - old_size; + + cd->bLength = sizeof(*cd); + cd->bDescriptorType = UDESC_CONFIG; + USETW(cd->wTotalLength, old_size); + cd->bNumInterface = temp->bInterfaceNumber + 1; + cd->bConfigurationValue = temp->bConfigurationValue; + cd->iConfiguration = tcd->iConfiguration; + cd->bmAttributes = tcd->bmAttributes; + cd->bMaxPower = tcd->bMaxPower; + cd->bmAttributes |= (UC_REMOTE_WAKEUP | UC_BUS_POWERED); + + if (temp->self_powered) { + cd->bmAttributes |= UC_SELF_POWERED; + } else { + cd->bmAttributes &= ~UC_SELF_POWERED; + } + } +} + +/*------------------------------------------------------------------------* + * usb_make_device_desc + * + * This function will generate an USB device descriptor from the + * given USB template device descriptor. + *------------------------------------------------------------------------*/ +static void +usb_make_device_desc(struct usb_temp_setup *temp, + const struct usb_temp_device_desc *tdd) +{ + struct usb_temp_data *utd; + const struct usb_temp_config_desc **tcd; + uint16_t old_size; + + /* Reserve memory */ + + old_size = temp->size; + temp->size += sizeof(*utd); + + /* Scan all the USB configs */ + + temp->bConfigurationValue = 1; + tcd = tdd->ppConfigDesc; + if (tcd) { + while (*tcd) { + usb_make_config_desc(temp, *tcd); + temp->bConfigurationValue++; + tcd++; + } + } + /* + * Fill out the real USB device descriptor + * in case there is a buffer present: + */ + + if (temp->buf) { + utd = USB_ADD_BYTES(temp->buf, old_size); + + /* Store a pointer to our template device descriptor */ + utd->tdd = tdd; + + /* Fill out USB device descriptor */ + utd->udd.bLength = sizeof(utd->udd); + utd->udd.bDescriptorType = UDESC_DEVICE; + utd->udd.bDeviceClass = tdd->bDeviceClass; + utd->udd.bDeviceSubClass = tdd->bDeviceSubClass; + utd->udd.bDeviceProtocol = tdd->bDeviceProtocol; + USETW(utd->udd.idVendor, tdd->idVendor); + USETW(utd->udd.idProduct, tdd->idProduct); + USETW(utd->udd.bcdDevice, tdd->bcdDevice); + utd->udd.iManufacturer = tdd->iManufacturer; + utd->udd.iProduct = tdd->iProduct; + utd->udd.iSerialNumber = tdd->iSerialNumber; + utd->udd.bNumConfigurations = temp->bConfigurationValue - 1; + + /* + * Fill out the USB device qualifier. Pretend that we + * don't support any other speeds by setting + * "bNumConfigurations" equal to zero. That saves us + * generating an extra set of configuration + * descriptors. + */ + utd->udq.bLength = sizeof(utd->udq); + utd->udq.bDescriptorType = UDESC_DEVICE_QUALIFIER; + utd->udq.bDeviceClass = tdd->bDeviceClass; + utd->udq.bDeviceSubClass = tdd->bDeviceSubClass; + utd->udq.bDeviceProtocol = tdd->bDeviceProtocol; + utd->udq.bNumConfigurations = 0; + USETW(utd->udq.bcdUSB, 0x0200); + utd->udq.bMaxPacketSize0 = 0; + + switch (temp->usb_speed) { + case USB_SPEED_LOW: + USETW(utd->udd.bcdUSB, 0x0110); + utd->udd.bMaxPacketSize = 8; + break; + case USB_SPEED_FULL: + USETW(utd->udd.bcdUSB, 0x0110); + utd->udd.bMaxPacketSize = 32; + break; + case USB_SPEED_HIGH: + USETW(utd->udd.bcdUSB, 0x0200); + utd->udd.bMaxPacketSize = 64; + break; + case USB_SPEED_VARIABLE: + USETW(utd->udd.bcdUSB, 0x0250); + utd->udd.bMaxPacketSize = 255; /* 512 bytes */ + break; + case USB_SPEED_SUPER: + USETW(utd->udd.bcdUSB, 0x0300); + utd->udd.bMaxPacketSize = 9; /* 2**9 = 512 bytes */ + break; + default: + temp->err = USB_ERR_INVAL; + break; + } + } +} + +/*------------------------------------------------------------------------* + * usb_hw_ep_match + * + * Return values: + * 0: The endpoint profile does not match the criterias + * Else: The endpoint profile matches the criterias + *------------------------------------------------------------------------*/ +static uint8_t +usb_hw_ep_match(const struct usb_hw_ep_profile *pf, + uint8_t ep_type, uint8_t ep_dir_in) +{ + if (ep_type == UE_CONTROL) { + /* special */ + return (pf->support_control); + } + if ((pf->support_in && ep_dir_in) || + (pf->support_out && !ep_dir_in)) { + if ((pf->support_interrupt && (ep_type == UE_INTERRUPT)) || + (pf->support_isochronous && (ep_type == UE_ISOCHRONOUS)) || + (pf->support_bulk && (ep_type == UE_BULK))) { + return (1); + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_hw_ep_find_match + * + * This function is used to find the best matching endpoint profile + * for and endpoint belonging to an USB descriptor. + * + * Return values: + * 0: Success. Got a match. + * Else: Failure. No match. + *------------------------------------------------------------------------*/ +static uint8_t +usb_hw_ep_find_match(struct usb_hw_ep_scratch *ues, + struct usb_hw_ep_scratch_sub *ep, uint8_t is_simplex) +{ + const struct usb_hw_ep_profile *pf; + uint16_t distance; + uint16_t temp; + uint16_t max_frame_size; + uint8_t n; + uint8_t best_n; + uint8_t dir_in; + uint8_t dir_out; + + distance = 0xFFFF; + best_n = 0; + + if ((!ep->needs_in) && (!ep->needs_out)) { + return (0); /* we are done */ + } + if (ep->needs_ep_type == UE_CONTROL) { + dir_in = 1; + dir_out = 1; + } else { + if (ep->needs_in) { + dir_in = 1; + dir_out = 0; + } else { + dir_in = 0; + dir_out = 1; + } + } + + for (n = 1; n != (USB_EP_MAX / 2); n++) { + + /* get HW endpoint profile */ + (ues->methods->get_hw_ep_profile) (ues->udev, &pf, n); + if (pf == NULL) { + /* end of profiles */ + break; + } + /* check if IN-endpoint is reserved */ + if (dir_in || pf->is_simplex) { + if (ues->bmInAlloc[n / 8] & (1 << (n % 8))) { + /* mismatch */ + continue; + } + } + /* check if OUT-endpoint is reserved */ + if (dir_out || pf->is_simplex) { + if (ues->bmOutAlloc[n / 8] & (1 << (n % 8))) { + /* mismatch */ + continue; + } + } + /* check simplex */ + if (pf->is_simplex == is_simplex) { + /* mismatch */ + continue; + } + /* check if HW endpoint matches */ + if (!usb_hw_ep_match(pf, ep->needs_ep_type, dir_in)) { + /* mismatch */ + continue; + } + /* get maximum frame size */ + if (dir_in) + max_frame_size = pf->max_in_frame_size; + else + max_frame_size = pf->max_out_frame_size; + + /* check if we have a matching profile */ + if (max_frame_size >= ep->max_frame_size) { + temp = (max_frame_size - ep->max_frame_size); + if (distance > temp) { + distance = temp; + best_n = n; + ep->pf = pf; + } + } + } + + /* see if we got a match */ + if (best_n != 0) { + /* get the correct profile */ + pf = ep->pf; + + /* reserve IN-endpoint */ + if (dir_in) { + ues->bmInAlloc[best_n / 8] |= + (1 << (best_n % 8)); + ep->hw_endpoint_in = best_n | UE_DIR_IN; + ep->needs_in = 0; + } + /* reserve OUT-endpoint */ + if (dir_out) { + ues->bmOutAlloc[best_n / 8] |= + (1 << (best_n % 8)); + ep->hw_endpoint_out = best_n | UE_DIR_OUT; + ep->needs_out = 0; + } + return (0); /* got a match */ + } + return (1); /* failure */ +} + +/*------------------------------------------------------------------------* + * usb_hw_ep_get_needs + * + * This function will figure out the type and number of endpoints + * which are needed for an USB configuration. + * + * Return values: + * 0: Success. + * Else: Failure. + *------------------------------------------------------------------------*/ +static uint8_t +usb_hw_ep_get_needs(struct usb_hw_ep_scratch *ues, + uint8_t ep_type, uint8_t is_complete) +{ + const struct usb_hw_ep_profile *pf; + struct usb_hw_ep_scratch_sub *ep_iface; + struct usb_hw_ep_scratch_sub *ep_curr; + struct usb_hw_ep_scratch_sub *ep_max; + struct usb_hw_ep_scratch_sub *ep_end; + struct usb_descriptor *desc; + struct usb_interface_descriptor *id; + struct usb_endpoint_descriptor *ed; + enum usb_dev_speed speed; + uint16_t wMaxPacketSize; + uint16_t temp; + uint8_t ep_no; + + ep_iface = ues->ep_max; + ep_curr = ues->ep_max; + ep_end = ues->ep + USB_EP_MAX; + ep_max = ues->ep_max; + desc = NULL; + speed = usbd_get_speed(ues->udev); + +repeat: + + while ((desc = usb_desc_foreach(ues->cd, desc))) { + + if ((desc->bDescriptorType == UDESC_INTERFACE) && + (desc->bLength >= sizeof(*id))) { + + id = (void *)desc; + + if (id->bAlternateSetting == 0) { + /* going forward */ + ep_iface = ep_max; + } else { + /* reset */ + ep_curr = ep_iface; + } + } + if ((desc->bDescriptorType == UDESC_ENDPOINT) && + (desc->bLength >= sizeof(*ed))) { + + ed = (void *)desc; + + goto handle_endpoint_desc; + } + } + ues->ep_max = ep_max; + return (0); + +handle_endpoint_desc: + temp = (ed->bmAttributes & UE_XFERTYPE); + + if (temp == ep_type) { + + if (ep_curr == ep_end) { + /* too many endpoints */ + return (1); /* failure */ + } + wMaxPacketSize = UGETW(ed->wMaxPacketSize); + if ((wMaxPacketSize & 0xF800) && + (speed == USB_SPEED_HIGH)) { + /* handle packet multiplier */ + temp = (wMaxPacketSize >> 11) & 3; + wMaxPacketSize &= 0x7FF; + if (temp == 1) { + wMaxPacketSize *= 2; + } else { + wMaxPacketSize *= 3; + } + } + /* + * Check if we have a fixed endpoint number, else the + * endpoint number is allocated dynamically: + */ + ep_no = (ed->bEndpointAddress & UE_ADDR); + if (ep_no != 0) { + + /* get HW endpoint profile */ + (ues->methods->get_hw_ep_profile) + (ues->udev, &pf, ep_no); + if (pf == NULL) { + /* HW profile does not exist - failure */ + DPRINTFN(0, "Endpoint profile %u " + "does not exist\n", ep_no); + return (1); + } + /* reserve fixed endpoint number */ + if (ep_type == UE_CONTROL) { + ues->bmInAlloc[ep_no / 8] |= + (1 << (ep_no % 8)); + ues->bmOutAlloc[ep_no / 8] |= + (1 << (ep_no % 8)); + if ((pf->max_in_frame_size < wMaxPacketSize) || + (pf->max_out_frame_size < wMaxPacketSize)) { + DPRINTFN(0, "Endpoint profile %u " + "has too small buffer\n", ep_no); + return (1); + } + } else if (ed->bEndpointAddress & UE_DIR_IN) { + ues->bmInAlloc[ep_no / 8] |= + (1 << (ep_no % 8)); + if (pf->max_in_frame_size < wMaxPacketSize) { + DPRINTFN(0, "Endpoint profile %u " + "has too small buffer\n", ep_no); + return (1); + } + } else { + ues->bmOutAlloc[ep_no / 8] |= + (1 << (ep_no % 8)); + if (pf->max_out_frame_size < wMaxPacketSize) { + DPRINTFN(0, "Endpoint profile %u " + "has too small buffer\n", ep_no); + return (1); + } + } + } else if (is_complete) { + + /* check if we have enough buffer space */ + if (wMaxPacketSize > + ep_curr->max_frame_size) { + return (1); /* failure */ + } + if (ed->bEndpointAddress & UE_DIR_IN) { + ed->bEndpointAddress = + ep_curr->hw_endpoint_in; + } else { + ed->bEndpointAddress = + ep_curr->hw_endpoint_out; + } + + } else { + + /* compute the maximum frame size */ + if (ep_curr->max_frame_size < wMaxPacketSize) { + ep_curr->max_frame_size = wMaxPacketSize; + } + if (temp == UE_CONTROL) { + ep_curr->needs_in = 1; + ep_curr->needs_out = 1; + } else { + if (ed->bEndpointAddress & UE_DIR_IN) { + ep_curr->needs_in = 1; + } else { + ep_curr->needs_out = 1; + } + } + ep_curr->needs_ep_type = ep_type; + } + + ep_curr++; + if (ep_max < ep_curr) { + ep_max = ep_curr; + } + } + goto repeat; +} + +/*------------------------------------------------------------------------* + * usb_hw_ep_resolve + * + * This function will try to resolve endpoint requirements by the + * given endpoint profiles that the USB hardware reports. + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb_error_t +usb_hw_ep_resolve(struct usb_device *udev, + struct usb_descriptor *desc) +{ + struct usb_hw_ep_scratch *ues; + struct usb_hw_ep_scratch_sub *ep; + const struct usb_hw_ep_profile *pf; + struct usb_bus_methods *methods; + struct usb_device_descriptor *dd; + uint16_t mps; + + if (desc == NULL) { + return (USB_ERR_INVAL); + } + /* get bus methods */ + methods = udev->bus->methods; + + if (methods->get_hw_ep_profile == NULL) { + return (USB_ERR_INVAL); + } + if (desc->bDescriptorType == UDESC_DEVICE) { + + if (desc->bLength < sizeof(*dd)) { + return (USB_ERR_INVAL); + } + dd = (void *)desc; + + /* get HW control endpoint 0 profile */ + (methods->get_hw_ep_profile) (udev, &pf, 0); + if (pf == NULL) { + return (USB_ERR_INVAL); + } + if (!usb_hw_ep_match(pf, UE_CONTROL, 0)) { + DPRINTFN(0, "Endpoint 0 does not " + "support control\n"); + return (USB_ERR_INVAL); + } + mps = dd->bMaxPacketSize; + + if (udev->speed == USB_SPEED_FULL) { + /* + * We can optionally choose another packet size ! + */ + while (1) { + /* check if "mps" is ok */ + if (pf->max_in_frame_size >= mps) { + break; + } + /* reduce maximum packet size */ + mps /= 2; + + /* check if "mps" is too small */ + if (mps < 8) { + return (USB_ERR_INVAL); + } + } + + dd->bMaxPacketSize = mps; + + } else { + /* We only have one choice */ + if (mps == 255) { + mps = 512; + } + /* Check if we support the specified wMaxPacketSize */ + if (pf->max_in_frame_size < mps) { + return (USB_ERR_INVAL); + } + } + return (0); /* success */ + } + if (desc->bDescriptorType != UDESC_CONFIG) { + return (USB_ERR_INVAL); + } + if (desc->bLength < sizeof(*(ues->cd))) { + return (USB_ERR_INVAL); + } + ues = udev->bus->scratch[0].hw_ep_scratch; + + memset(ues, 0, sizeof(*ues)); + + ues->ep_max = ues->ep; + ues->cd = (void *)desc; + ues->methods = methods; + ues->udev = udev; + + /* Get all the endpoints we need */ + + if (usb_hw_ep_get_needs(ues, UE_ISOCHRONOUS, 0) || + usb_hw_ep_get_needs(ues, UE_INTERRUPT, 0) || + usb_hw_ep_get_needs(ues, UE_CONTROL, 0) || + usb_hw_ep_get_needs(ues, UE_BULK, 0)) { + DPRINTFN(0, "Could not get needs\n"); + return (USB_ERR_INVAL); + } + for (ep = ues->ep; ep != ues->ep_max; ep++) { + + while (ep->needs_in || ep->needs_out) { + + /* + * First try to use a simplex endpoint. + * Then try to use a duplex endpoint. + */ + if (usb_hw_ep_find_match(ues, ep, 1) && + usb_hw_ep_find_match(ues, ep, 0)) { + DPRINTFN(0, "Could not find match\n"); + return (USB_ERR_INVAL); + } + } + } + + ues->ep_max = ues->ep; + + /* Update all endpoint addresses */ + + if (usb_hw_ep_get_needs(ues, UE_ISOCHRONOUS, 1) || + usb_hw_ep_get_needs(ues, UE_INTERRUPT, 1) || + usb_hw_ep_get_needs(ues, UE_CONTROL, 1) || + usb_hw_ep_get_needs(ues, UE_BULK, 1)) { + DPRINTFN(0, "Could not update endpoint address\n"); + return (USB_ERR_INVAL); + } + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb_temp_get_tdd + * + * Returns: + * NULL: No USB template device descriptor found. + * Else: Pointer to the USB template device descriptor. + *------------------------------------------------------------------------*/ +static const struct usb_temp_device_desc * +usb_temp_get_tdd(struct usb_device *udev) +{ + if (udev->usb_template_ptr == NULL) { + return (NULL); + } + return (udev->usb_template_ptr->tdd); +} + +/*------------------------------------------------------------------------* + * usb_temp_get_device_desc + * + * Returns: + * NULL: No USB device descriptor found. + * Else: Pointer to USB device descriptor. + *------------------------------------------------------------------------*/ +static void * +usb_temp_get_device_desc(struct usb_device *udev) +{ + struct usb_device_descriptor *dd; + + if (udev->usb_template_ptr == NULL) { + return (NULL); + } + dd = &udev->usb_template_ptr->udd; + if (dd->bDescriptorType != UDESC_DEVICE) { + /* sanity check failed */ + return (NULL); + } + return (dd); +} + +/*------------------------------------------------------------------------* + * usb_temp_get_qualifier_desc + * + * Returns: + * NULL: No USB device_qualifier descriptor found. + * Else: Pointer to USB device_qualifier descriptor. + *------------------------------------------------------------------------*/ +static void * +usb_temp_get_qualifier_desc(struct usb_device *udev) +{ + struct usb_device_qualifier *dq; + + if (udev->usb_template_ptr == NULL) { + return (NULL); + } + dq = &udev->usb_template_ptr->udq; + if (dq->bDescriptorType != UDESC_DEVICE_QUALIFIER) { + /* sanity check failed */ + return (NULL); + } + return (dq); +} + +/*------------------------------------------------------------------------* + * usb_temp_get_config_desc + * + * Returns: + * NULL: No USB config descriptor found. + * Else: Pointer to USB config descriptor having index "index". + *------------------------------------------------------------------------*/ +static void * +usb_temp_get_config_desc(struct usb_device *udev, + uint16_t *pLength, uint8_t index) +{ + struct usb_device_descriptor *dd; + struct usb_config_descriptor *cd; + uint16_t temp; + + if (udev->usb_template_ptr == NULL) { + return (NULL); + } + dd = &udev->usb_template_ptr->udd; + cd = (void *)(udev->usb_template_ptr + 1); + + if (index >= dd->bNumConfigurations) { + /* out of range */ + return (NULL); + } + while (index--) { + if (cd->bDescriptorType != UDESC_CONFIG) { + /* sanity check failed */ + return (NULL); + } + temp = UGETW(cd->wTotalLength); + cd = USB_ADD_BYTES(cd, temp); + } + + if (pLength) { + *pLength = UGETW(cd->wTotalLength); + } + return (cd); +} + +/*------------------------------------------------------------------------* + * usb_temp_get_vendor_desc + * + * Returns: + * NULL: No vendor descriptor found. + * Else: Pointer to a vendor descriptor. + *------------------------------------------------------------------------*/ +static const void * +usb_temp_get_vendor_desc(struct usb_device *udev, + const struct usb_device_request *req, uint16_t *plen) +{ + const struct usb_temp_device_desc *tdd; + + tdd = usb_temp_get_tdd(udev); + if (tdd == NULL) { + return (NULL); + } + if (tdd->getVendorDesc == NULL) { + return (NULL); + } + return ((tdd->getVendorDesc) (req, plen)); +} + +/*------------------------------------------------------------------------* + * usb_temp_get_string_desc + * + * Returns: + * NULL: No string descriptor found. + * Else: Pointer to a string descriptor. + *------------------------------------------------------------------------*/ +static const void * +usb_temp_get_string_desc(struct usb_device *udev, + uint16_t lang_id, uint8_t string_index) +{ + const struct usb_temp_device_desc *tdd; + + tdd = usb_temp_get_tdd(udev); + if (tdd == NULL) { + return (NULL); + } + if (tdd->getStringDesc == NULL) { + return (NULL); + } + return ((tdd->getStringDesc) (lang_id, string_index)); +} + +/*------------------------------------------------------------------------* + * usb_temp_get_hub_desc + * + * Returns: + * NULL: No USB HUB descriptor found. + * Else: Pointer to a USB HUB descriptor. + *------------------------------------------------------------------------*/ +static const void * +usb_temp_get_hub_desc(struct usb_device *udev) +{ + return (NULL); /* needs to be implemented */ +} + +/*------------------------------------------------------------------------* + * usb_temp_get_desc + * + * This function is a demultiplexer for local USB device side control + * endpoint requests. + *------------------------------------------------------------------------*/ +static usb_error_t +usb_temp_get_desc(struct usb_device *udev, struct usb_device_request *req, + const void **pPtr, uint16_t *pLength) +{ + const uint8_t *buf; + uint16_t len; + + buf = NULL; + len = 0; + + switch (req->bmRequestType) { + case UT_READ_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + default: + goto tr_stalled; + } + case UT_READ_CLASS_DEVICE: + switch (req->bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_class_descriptor; + default: + goto tr_stalled; + } + default: + goto tr_stalled; + } + +tr_handle_get_descriptor: + switch (req->wValue[1]) { + case UDESC_DEVICE: + if (req->wValue[0]) { + goto tr_stalled; + } + buf = usb_temp_get_device_desc(udev); + goto tr_valid; + case UDESC_DEVICE_QUALIFIER: + if (udev->speed != USB_SPEED_HIGH) { + goto tr_stalled; + } + if (req->wValue[0]) { + goto tr_stalled; + } + buf = usb_temp_get_qualifier_desc(udev); + goto tr_valid; + case UDESC_OTHER_SPEED_CONFIGURATION: + if (udev->speed != USB_SPEED_HIGH) { + goto tr_stalled; + } + case UDESC_CONFIG: + buf = usb_temp_get_config_desc(udev, + &len, req->wValue[0]); + goto tr_valid; + case UDESC_STRING: + buf = usb_temp_get_string_desc(udev, + UGETW(req->wIndex), req->wValue[0]); + goto tr_valid; + default: + goto tr_stalled; + } + +tr_handle_get_class_descriptor: + if (req->wValue[0]) { + goto tr_stalled; + } + buf = usb_temp_get_hub_desc(udev); + goto tr_valid; + +tr_valid: + if (buf == NULL) + goto tr_stalled; + if (len == 0) + len = buf[0]; + *pPtr = buf; + *pLength = len; + return (0); /* success */ + +tr_stalled: + /* try to get a vendor specific descriptor */ + len = 0; + buf = usb_temp_get_vendor_desc(udev, req, &len); + if (buf != NULL) + goto tr_valid; + *pPtr = NULL; + *pLength = 0; + return (0); /* we ignore failures */ +} + +/*------------------------------------------------------------------------* + * usb_temp_setup + * + * This function generates USB descriptors according to the given USB + * template device descriptor. It will also try to figure out the best + * matching endpoint addresses using the hardware endpoint profiles. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usb_temp_setup(struct usb_device *udev, + const struct usb_temp_device_desc *tdd) +{ + struct usb_temp_setup *uts; + void *buf; + uint8_t n; + + if (tdd == NULL) { + /* be NULL safe */ + return (0); + } + uts = udev->bus->scratch[0].temp_setup; + + memset(uts, 0, sizeof(*uts)); + + uts->usb_speed = udev->speed; + uts->self_powered = udev->flags.self_powered; + + /* first pass */ + + usb_make_device_desc(uts, tdd); + + if (uts->err) { + /* some error happened */ + return (uts->err); + } + /* sanity check */ + if (uts->size == 0) { + return (USB_ERR_INVAL); + } + /* allocate zeroed memory */ + uts->buf = malloc(uts->size, M_USB, M_WAITOK | M_ZERO); + if (uts->buf == NULL) { + /* could not allocate memory */ + return (USB_ERR_NOMEM); + } + /* second pass */ + + uts->size = 0; + + usb_make_device_desc(uts, tdd); + + /* + * Store a pointer to our descriptors: + */ + udev->usb_template_ptr = uts->buf; + + if (uts->err) { + /* some error happened during second pass */ + goto error; + } + /* + * Resolve all endpoint addresses ! + */ + buf = usb_temp_get_device_desc(udev); + uts->err = usb_hw_ep_resolve(udev, buf); + if (uts->err) { + DPRINTFN(0, "Could not resolve endpoints for " + "Device Descriptor, error = %s\n", + usbd_errstr(uts->err)); + goto error; + } + for (n = 0;; n++) { + + buf = usb_temp_get_config_desc(udev, NULL, n); + if (buf == NULL) { + break; + } + uts->err = usb_hw_ep_resolve(udev, buf); + if (uts->err) { + DPRINTFN(0, "Could not resolve endpoints for " + "Config Descriptor %u, error = %s\n", n, + usbd_errstr(uts->err)); + goto error; + } + } + return (uts->err); + +error: + usb_temp_unsetup(udev); + return (uts->err); +} + +/*------------------------------------------------------------------------* + * usb_temp_unsetup + * + * This function frees any memory associated with the currently + * setup template, if any. + *------------------------------------------------------------------------*/ +void +usb_temp_unsetup(struct usb_device *udev) +{ + if (udev->usb_template_ptr) { + + free(udev->usb_template_ptr, M_USB); + + udev->usb_template_ptr = NULL; + } +} + +static usb_error_t +usb_temp_setup_by_index(struct usb_device *udev, uint16_t index) +{ + usb_error_t err; + + switch (index) { + case USB_TEMP_MSC: + err = usb_temp_setup(udev, &usb_template_msc); + break; + case USB_TEMP_CDCE: + err = usb_temp_setup(udev, &usb_template_cdce); + break; + case USB_TEMP_MTP: + err = usb_temp_setup(udev, &usb_template_mtp); + break; + case USB_TEMP_MODEM: + err = usb_temp_setup(udev, &usb_template_modem); + break; + case USB_TEMP_AUDIO: + err = usb_temp_setup(udev, &usb_template_audio); + break; + case USB_TEMP_KBD: + err = usb_temp_setup(udev, &usb_template_kbd); + break; + case USB_TEMP_MOUSE: + err = usb_temp_setup(udev, &usb_template_mouse); + break; + default: + return (USB_ERR_INVAL); + } + + return (err); +} + +static void +usb_temp_init(void *arg) +{ + /* register our functions */ + usb_temp_get_desc_p = &usb_temp_get_desc; + usb_temp_setup_by_index_p = &usb_temp_setup_by_index; + usb_temp_unsetup_p = &usb_temp_unsetup; +} + +SYSINIT(usb_temp_init, SI_SUB_LOCK, SI_ORDER_FIRST, usb_temp_init, NULL); +SYSUNINIT(usb_temp_unload, SI_SUB_LOCK, SI_ORDER_ANY, usb_temp_unload, NULL); diff --git a/sys/bus/u4b/template/usb_template.h b/sys/bus/u4b/template/usb_template.h new file mode 100644 index 0000000000..b05272fb2a --- /dev/null +++ b/sys/bus/u4b/template/usb_template.h @@ -0,0 +1,113 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2007 Hans Petter Selasky + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* USB templates are used to build up real USB descriptors */ + +#ifndef _USB_TEMPLATE_H_ +#define _USB_TEMPLATE_H_ + +#ifndef USB_TEMPLATE_VENDOR +#define USB_TEMPLATE_VENDOR 0x0001 +#endif + +typedef const void *(usb_temp_get_string_desc_t)(uint16_t lang_id, uint8_t string_index); +typedef const void *(usb_temp_get_vendor_desc_t)(const struct usb_device_request *req, uint16_t *plen); + +struct usb_temp_packet_size { + uint16_t mps[USB_SPEED_MAX]; +}; + +struct usb_temp_interval { + uint8_t bInterval[USB_SPEED_MAX]; +}; + +struct usb_temp_endpoint_desc { + const void **ppRawDesc; + const struct usb_temp_packet_size *pPacketSize; + const struct usb_temp_interval *pIntervals; + /* + * If (bEndpointAddress & UE_ADDR) is non-zero the endpoint number + * is pre-selected for this endpoint descriptor. Else an endpoint + * number is automatically chosen. + */ + uint8_t bEndpointAddress; /* UE_DIR_IN or UE_DIR_OUT */ + uint8_t bmAttributes; +}; + +struct usb_temp_interface_desc { + const void **ppRawDesc; + const struct usb_temp_endpoint_desc **ppEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; + uint8_t isAltInterface; +}; + +struct usb_temp_config_desc { + const struct usb_temp_interface_desc **ppIfaceDesc; + uint8_t bmAttributes; + uint8_t bMaxPower; + uint8_t iConfiguration; +}; + +struct usb_temp_device_desc { + usb_temp_get_string_desc_t *getStringDesc; + usb_temp_get_vendor_desc_t *getVendorDesc; + const struct usb_temp_config_desc **ppConfigDesc; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; +}; + +struct usb_temp_data { + const struct usb_temp_device_desc *tdd; + struct usb_device_descriptor udd; /* device descriptor */ + struct usb_device_qualifier udq; /* device qualifier */ +}; + +/* prototypes */ + +extern const struct usb_temp_device_desc usb_template_audio; +extern const struct usb_temp_device_desc usb_template_cdce; +extern const struct usb_temp_device_desc usb_template_kbd; +extern const struct usb_temp_device_desc usb_template_modem; +extern const struct usb_temp_device_desc usb_template_mouse; +extern const struct usb_temp_device_desc usb_template_msc; +extern const struct usb_temp_device_desc usb_template_mtp; + +usb_error_t usb_temp_setup(struct usb_device *, + const struct usb_temp_device_desc *); +void usb_temp_unsetup(struct usb_device *); + +#endif /* _USB_TEMPLATE_H_ */ diff --git a/sys/bus/u4b/template/usb_template_audio.c b/sys/bus/u4b/template/usb_template_audio.c new file mode 100644 index 0000000000..8e9e7f0664 --- /dev/null +++ b/sys/bus/u4b/template/usb_template_audio.c @@ -0,0 +1,405 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2010 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains the USB template for an USB Audio Device. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +enum { + INDEX_AUDIO_LANG, + INDEX_AUDIO_MIXER, + INDEX_AUDIO_RECORD, + INDEX_AUDIO_PLAYBACK, + INDEX_AUDIO_PRODUCT, + INDEX_AUDIO_MAX, +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_AUDIO_PRODUCT \ + 'A', 0, 'u', 0, 'd', 0, 'i', 0, 'o', 0, ' ', 0, \ + 'T', 0, 'e', 0, 's', 0, 't', 0, ' ', 0, \ + 'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0, ' ', 0, + +#define STRING_AUDIO_MIXER \ + 'M', 0, 'i', 0, 'x', 0, 'e', 0, 'r', 0, ' ', 0, \ + 'i', 0, 'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0, 'a', 0, 'c', 0, 'e', 0, + +#define STRING_AUDIO_RECORD \ + 'R', 0, 'e', 0, 'c', 0, 'o', 0, 'r', 0, 'd', 0, ' ', 0, \ + 'i', 0, 'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0, 'a', 0, 'c', 0, 'e', 0, + +#define STRING_AUDIO_PLAYBACK \ + 'P', 0, 'l', 0, 'a', 0, 'y', 0, 'b', 0, 'a', 0, 'c', 0, 'k', 0, ' ', 0, \ + 'i', 0, 'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0, 'a', 0, 'c', 0, 'e', 0, + + +/* make the real string descriptors */ + +USB_MAKE_STRING_DESC(STRING_LANG, string_lang); +USB_MAKE_STRING_DESC(STRING_AUDIO_MIXER, string_audio_mixer); +USB_MAKE_STRING_DESC(STRING_AUDIO_RECORD, string_audio_record); +USB_MAKE_STRING_DESC(STRING_AUDIO_PLAYBACK, string_audio_playback); +USB_MAKE_STRING_DESC(STRING_AUDIO_PRODUCT, string_audio_product); + +/* prototypes */ + +/* + * Audio Mixer description structures + * + * Some of the audio descriptors were dumped + * from a Creative Labs USB audio device. + */ + +static const uint8_t audio_raw_desc_0[] = { + 0x0a, 0x24, 0x01, 0x00, 0x01, 0xa9, 0x00, 0x02, + 0x01, 0x02 +}; + +static const uint8_t audio_raw_desc_1[] = { + 0x0c, 0x24, 0x02, 0x01, 0x01, 0x01, 0x00, 0x02, + 0x03, 0x00, 0x00, 0x00 +}; + +static const uint8_t audio_raw_desc_2[] = { + 0x0c, 0x24, 0x02, 0x02, 0x01, 0x02, 0x00, 0x02, + 0x03, 0x00, 0x00, 0x00 +}; + +static const uint8_t audio_raw_desc_3[] = { + 0x0c, 0x24, 0x02, 0x03, 0x03, 0x06, 0x00, 0x02, + 0x03, 0x00, 0x00, 0x00 +}; + +static const uint8_t audio_raw_desc_4[] = { + 0x0c, 0x24, 0x02, 0x04, 0x05, 0x06, 0x00, 0x02, + 0x03, 0x00, 0x00, 0x00 +}; + +static const uint8_t audio_raw_desc_5[] = { + 0x09, 0x24, 0x03, 0x05, 0x05, 0x06, 0x00, 0x01, + 0x00 +}; + +static const uint8_t audio_raw_desc_6[] = { + 0x09, 0x24, 0x03, 0x06, 0x01, 0x03, 0x00, 0x09, + 0x00 +}; + +static const uint8_t audio_raw_desc_7[] = { + 0x09, 0x24, 0x03, 0x07, 0x01, 0x01, 0x00, 0x08, + 0x00 +}; + +static const uint8_t audio_raw_desc_8[] = { + 0x09, 0x24, 0x05, 0x08, 0x03, 0x0a, 0x0b, 0x0c, + 0x00 +}; + +static const uint8_t audio_raw_desc_9[] = { + 0x0a, 0x24, 0x06, 0x09, 0x0f, 0x01, 0x01, 0x02, + 0x02, 0x00 +}; + +static const uint8_t audio_raw_desc_10[] = { + 0x0a, 0x24, 0x06, 0x0a, 0x02, 0x01, 0x43, 0x00, + 0x00, 0x00 +}; + +static const uint8_t audio_raw_desc_11[] = { + 0x0a, 0x24, 0x06, 0x0b, 0x03, 0x01, 0x01, 0x02, + 0x02, 0x00 +}; + +static const uint8_t audio_raw_desc_12[] = { + 0x0a, 0x24, 0x06, 0x0c, 0x04, 0x01, 0x01, 0x00, + 0x00, 0x00 +}; + +static const uint8_t audio_raw_desc_13[] = { + 0x0a, 0x24, 0x06, 0x0d, 0x02, 0x01, 0x03, 0x00, + 0x00, 0x00 +}; + +static const uint8_t audio_raw_desc_14[] = { + 0x0a, 0x24, 0x06, 0x0e, 0x03, 0x01, 0x01, 0x02, + 0x02, 0x00 +}; + +static const uint8_t audio_raw_desc_15[] = { + 0x0f, 0x24, 0x04, 0x0f, 0x03, 0x01, 0x0d, 0x0e, + 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const void *audio_raw_iface_0_desc[] = { + audio_raw_desc_0, + audio_raw_desc_1, + audio_raw_desc_2, + audio_raw_desc_3, + audio_raw_desc_4, + audio_raw_desc_5, + audio_raw_desc_6, + audio_raw_desc_7, + audio_raw_desc_8, + audio_raw_desc_9, + audio_raw_desc_10, + audio_raw_desc_11, + audio_raw_desc_12, + audio_raw_desc_13, + audio_raw_desc_14, + audio_raw_desc_15, + NULL, +}; + +static const struct usb_temp_interface_desc audio_iface_0 = { + .ppEndpoints = NULL, /* no endpoints */ + .ppRawDesc = audio_raw_iface_0_desc, + .bInterfaceClass = 1, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 0, + .iInterface = INDEX_AUDIO_MIXER, +}; + +static const uint8_t audio_raw_desc_20[] = { + 0x07, 0x24, 0x01, 0x01, 0x03, 0x01, 0x00 + +}; + +static const uint8_t audio_raw_desc_21[] = { + 0x0b, 0x24, 0x02, 0x01, 0x02, 0x02, 0x10, 0x01, + /* 48kHz */ + 0x80, 0xbb, 0x00 +}; + +static const uint8_t audio_raw_desc_22[] = { + 0x07, 0x25, 0x01, 0x00, 0x01, 0x04, 0x00 +}; + +static const void *audio_raw_iface_1_desc[] = { + audio_raw_desc_20, + audio_raw_desc_21, + NULL, +}; + +static const void *audio_raw_ep_1_desc[] = { + audio_raw_desc_22, + NULL, +}; + +static const struct usb_temp_packet_size audio_isoc_mps = { + .mps[USB_SPEED_FULL] = 0xC8, + .mps[USB_SPEED_HIGH] = 0xC8, +}; + +static const struct usb_temp_interval audio_isoc_interval = { + .bInterval[USB_SPEED_FULL] = 1, /* 1:1 */ + .bInterval[USB_SPEED_HIGH] = 4, /* 1:8 */ +}; + +static const struct usb_temp_endpoint_desc audio_isoc_out_ep = { + .ppRawDesc = audio_raw_ep_1_desc, + .pPacketSize = &audio_isoc_mps, + .pIntervals = &audio_isoc_interval, + .bEndpointAddress = UE_DIR_OUT, + .bmAttributes = UE_ISOCHRONOUS | UE_ISO_ADAPT, +}; + +static const struct usb_temp_endpoint_desc *audio_iface_1_ep[] = { + &audio_isoc_out_ep, + NULL, +}; + +static const struct usb_temp_interface_desc audio_iface_1_alt_0 = { + .ppEndpoints = NULL, /* no endpoints */ + .ppRawDesc = NULL, /* no raw descriptors */ + .bInterfaceClass = 1, + .bInterfaceSubClass = 2, + .bInterfaceProtocol = 0, + .iInterface = INDEX_AUDIO_PLAYBACK, +}; + +static const struct usb_temp_interface_desc audio_iface_1_alt_1 = { + .ppEndpoints = audio_iface_1_ep, + .ppRawDesc = audio_raw_iface_1_desc, + .bInterfaceClass = 1, + .bInterfaceSubClass = 2, + .bInterfaceProtocol = 0, + .iInterface = INDEX_AUDIO_PLAYBACK, + .isAltInterface = 1, /* this is an alternate setting */ +}; + +static const uint8_t audio_raw_desc_30[] = { + 0x07, 0x24, 0x01, 0x07, 0x01, 0x01, 0x00 + +}; + +static const uint8_t audio_raw_desc_31[] = { + 0x0b, 0x24, 0x02, 0x01, 0x02, 0x02, 0x10, 0x01, + /* 48kHz */ + 0x80, 0xbb, 0x00 +}; + +static const uint8_t audio_raw_desc_32[] = { + 0x07, 0x25, 0x01, 0x01, 0x00, 0x00, 0x00 +}; + +static const void *audio_raw_iface_2_desc[] = { + audio_raw_desc_30, + audio_raw_desc_31, + NULL, +}; + +static const void *audio_raw_ep_2_desc[] = { + audio_raw_desc_32, + NULL, +}; + +static const struct usb_temp_endpoint_desc audio_isoc_in_ep = { + .ppRawDesc = audio_raw_ep_2_desc, + .pPacketSize = &audio_isoc_mps, + .pIntervals = &audio_isoc_interval, + .bEndpointAddress = UE_DIR_IN, + .bmAttributes = UE_ISOCHRONOUS | UE_ISO_ADAPT, +}; + +static const struct usb_temp_endpoint_desc *audio_iface_2_ep[] = { + &audio_isoc_in_ep, + NULL, +}; + +static const struct usb_temp_interface_desc audio_iface_2_alt_0 = { + .ppEndpoints = NULL, /* no endpoints */ + .ppRawDesc = NULL, /* no raw descriptors */ + .bInterfaceClass = 1, + .bInterfaceSubClass = 2, + .bInterfaceProtocol = 0, + .iInterface = INDEX_AUDIO_RECORD, +}; + +static const struct usb_temp_interface_desc audio_iface_2_alt_1 = { + .ppEndpoints = audio_iface_2_ep, + .ppRawDesc = audio_raw_iface_2_desc, + .bInterfaceClass = 1, + .bInterfaceSubClass = 2, + .bInterfaceProtocol = 0, + .iInterface = INDEX_AUDIO_RECORD, + .isAltInterface = 1, /* this is an alternate setting */ +}; + +static const struct usb_temp_interface_desc *audio_interfaces[] = { + &audio_iface_0, + &audio_iface_1_alt_0, + &audio_iface_1_alt_1, + &audio_iface_2_alt_0, + &audio_iface_2_alt_1, + NULL, +}; + +static const struct usb_temp_config_desc audio_config_desc = { + .ppIfaceDesc = audio_interfaces, + .bmAttributes = UC_BUS_POWERED, + .bMaxPower = 25, /* 50 mA */ + .iConfiguration = INDEX_AUDIO_PRODUCT, +}; + +static const struct usb_temp_config_desc *audio_configs[] = { + &audio_config_desc, + NULL, +}; + +static usb_temp_get_string_desc_t audio_get_string_desc; + +const struct usb_temp_device_desc usb_template_audio = { + .getStringDesc = &audio_get_string_desc, + .ppConfigDesc = audio_configs, + .idVendor = USB_TEMPLATE_VENDOR, + .idProduct = 0x000A, + .bcdDevice = 0x0100, + .bDeviceClass = UDCLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .iManufacturer = 0, + .iProduct = INDEX_AUDIO_PRODUCT, + .iSerialNumber = 0, +}; + +/*------------------------------------------------------------------------* + * audio_get_string_desc + * + * Return values: + * NULL: Failure. No such string. + * Else: Success. Pointer to string descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +audio_get_string_desc(uint16_t lang_id, uint8_t string_index) +{ + static const void *ptr[INDEX_AUDIO_MAX] = { + [INDEX_AUDIO_LANG] = &string_lang, + [INDEX_AUDIO_MIXER] = &string_audio_mixer, + [INDEX_AUDIO_RECORD] = &string_audio_record, + [INDEX_AUDIO_PLAYBACK] = &string_audio_playback, + [INDEX_AUDIO_PRODUCT] = &string_audio_product, + }; + + if (string_index == 0) { + return (&string_lang); + } + if (lang_id != 0x0409) { + return (NULL); + } + if (string_index < INDEX_AUDIO_MAX) { + return (ptr[string_index]); + } + return (NULL); +} diff --git a/sys/bus/u4b/template/usb_template_cdce.c b/sys/bus/u4b/template/usb_template_cdce.c new file mode 100644 index 0000000000..481a69cb95 --- /dev/null +++ b/sys/bus/u4b/template/usb_template_cdce.c @@ -0,0 +1,309 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2007 Hans Petter Selasky + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains the USB templates for a CDC USB ethernet device. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +enum { + STRING_LANG_INDEX, + STRING_MAC_INDEX, + STRING_ETH_CONTROL_INDEX, + STRING_ETH_DATA_INDEX, + STRING_ETH_CONFIG_INDEX, + STRING_ETH_VENDOR_INDEX, + STRING_ETH_PRODUCT_INDEX, + STRING_ETH_SERIAL_INDEX, + STRING_ETH_MAX, +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_MAC \ + '2', 0, 'A', 0, '2', 0, '3', 0, \ + '4', 0, '5', 0, '6', 0, '7', 0, \ + '8', 0, '9', 0, 'A', 0, 'B', 0, + +#define STRING_ETH_CONTROL \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'E', 0, 't', 0, 'h', 0, 'e', 0, \ + 'r', 0, 'n', 0, 'e', 0, 't', 0, \ + ' ', 0, 'C', 0, 'o', 0, 'm', 0, \ + 'm', 0, ' ', 0, 'i', 0, 'n', 0, \ + 't', 0, 'e', 0, 'r', 0, 'f', 0, \ + 'a', 0, 'c', 0, 'e', 0, + +#define STRING_ETH_DATA \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'E', 0, 't', 0, 'h', 0, 'e', 0, \ + 'r', 0, 'n', 0, 'e', 0, 't', 0, \ + ' ', 0, 'D', 0, 'a', 0, 't', 0, \ + 'a', 0, ' ', 0, 'i', 0, 'n', 0, \ + 't', 0, 'e', 0, 'r', 0, 'f', 0, \ + 'a', 0, 'c', 0, 'e', 0, + +#define STRING_ETH_CONFIG \ + 'D', 0, 'e', 0, 'f', 0, 'a', 0, \ + 'u', 0, 'l', 0, 't', 0, ' ', 0, \ + 'c', 0, 'o', 0, 'n', 0, 'f', 0, \ + 'i', 0, 'g', 0, + +#define STRING_ETH_VENDOR \ + 'F', 0, 'r', 0, 'e', 0, 'e', 0, \ + 'B', 0, 'S', 0, 'D', 0, ' ', 0, \ + 'f', 0, 'o', 0, 'u', 0, 'n', 0, \ + 'd', 0, 'a', 0, 't', 0, 'i', 0, \ + 'o', 0, 'n', 0, + +#define STRING_ETH_PRODUCT \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'E', 0, 't', 0, 'h', 0, 'e', 0, \ + 'r', 0, 'n', 0, 'e', 0, 't', 0, \ + ' ', 0, 'A', 0, 'd', 0, 'a', 0, \ + 'p', 0, 't', 0, 'e', 0, 'r', 0, + +#define STRING_ETH_SERIAL \ + 'D', 0, 'e', 0, 'c', 0, 'e', 0, \ + 'm', 0, 'b', 0, 'e', 0, 'r', 0, \ + ' ', 0, '2', 0, '0', 0, '0', 0, \ + '7', 0, + +/* make the real string descriptors */ + +USB_MAKE_STRING_DESC(STRING_LANG, string_lang); +USB_MAKE_STRING_DESC(STRING_MAC, string_mac); +USB_MAKE_STRING_DESC(STRING_ETH_CONTROL, string_eth_control); +USB_MAKE_STRING_DESC(STRING_ETH_DATA, string_eth_data); +USB_MAKE_STRING_DESC(STRING_ETH_CONFIG, string_eth_config); +USB_MAKE_STRING_DESC(STRING_ETH_VENDOR, string_eth_vendor); +USB_MAKE_STRING_DESC(STRING_ETH_PRODUCT, string_eth_product); +USB_MAKE_STRING_DESC(STRING_ETH_SERIAL, string_eth_serial); + +/* prototypes */ + +static usb_temp_get_string_desc_t eth_get_string_desc; + +static const struct usb_cdc_union_descriptor eth_union_desc = { + .bLength = sizeof(eth_union_desc), + .bDescriptorType = UDESC_CS_INTERFACE, + .bDescriptorSubtype = UDESCSUB_CDC_UNION, + .bMasterInterface = 0, /* this is automatically updated */ + .bSlaveInterface[0] = 1, /* this is automatically updated */ +}; + +static const struct usb_cdc_header_descriptor eth_header_desc = { + .bLength = sizeof(eth_header_desc), + .bDescriptorType = UDESC_CS_INTERFACE, + .bDescriptorSubtype = UDESCSUB_CDC_HEADER, + .bcdCDC[0] = 0x10, + .bcdCDC[1] = 0x01, +}; + +static const struct usb_cdc_ethernet_descriptor eth_enf_desc = { + .bLength = sizeof(eth_enf_desc), + .bDescriptorType = UDESC_CS_INTERFACE, + .bDescriptorSubtype = UDESCSUB_CDC_ENF, + .iMacAddress = STRING_MAC_INDEX, + .bmEthernetStatistics = {0, 0, 0, 0}, + .wMaxSegmentSize = {0xEA, 0x05},/* 1514 bytes */ + .wNumberMCFilters = {0, 0}, + .bNumberPowerFilters = 0, +}; + +static const void *eth_control_if_desc[] = { + ð_union_desc, + ð_header_desc, + ð_enf_desc, + NULL, +}; + +static const struct usb_temp_packet_size bulk_mps = { + .mps[USB_SPEED_FULL] = 64, + .mps[USB_SPEED_HIGH] = 512, +}; + +static const struct usb_temp_packet_size intr_mps = { + .mps[USB_SPEED_FULL] = 8, + .mps[USB_SPEED_HIGH] = 8, +}; + +static const struct usb_temp_endpoint_desc bulk_in_ep = { + .pPacketSize = &bulk_mps, +#ifdef USB_HIP_IN_EP_0 + .bEndpointAddress = USB_HIP_IN_EP_0, +#else + .bEndpointAddress = UE_DIR_IN, +#endif + .bmAttributes = UE_BULK, +}; + +static const struct usb_temp_endpoint_desc bulk_out_ep = { + .pPacketSize = &bulk_mps, +#ifdef USB_HIP_OUT_EP_0 + .bEndpointAddress = USB_HIP_OUT_EP_0, +#else + .bEndpointAddress = UE_DIR_OUT, +#endif + .bmAttributes = UE_BULK, +}; + +static const struct usb_temp_endpoint_desc intr_in_ep = { + .pPacketSize = &intr_mps, + .bEndpointAddress = UE_DIR_IN, + .bmAttributes = UE_INTERRUPT, +}; + +static const struct usb_temp_endpoint_desc *eth_intr_endpoints[] = { + &intr_in_ep, + NULL, +}; + +static const struct usb_temp_interface_desc eth_control_interface = { + .ppEndpoints = eth_intr_endpoints, + .ppRawDesc = eth_control_if_desc, + .bInterfaceClass = UICLASS_CDC, + .bInterfaceSubClass = UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, + .bInterfaceProtocol = 0, + .iInterface = STRING_ETH_CONTROL_INDEX, +}; + +static const struct usb_temp_endpoint_desc *eth_data_endpoints[] = { + &bulk_in_ep, + &bulk_out_ep, + NULL, +}; + +static const struct usb_temp_interface_desc eth_data_null_interface = { + .ppEndpoints = NULL, /* no endpoints */ + .bInterfaceClass = UICLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = STRING_ETH_DATA_INDEX, +}; + +static const struct usb_temp_interface_desc eth_data_interface = { + .ppEndpoints = eth_data_endpoints, + .bInterfaceClass = UICLASS_CDC_DATA, + .bInterfaceSubClass = UISUBCLASS_DATA, + .bInterfaceProtocol = 0, + .iInterface = STRING_ETH_DATA_INDEX, + .isAltInterface = 1, /* this is an alternate setting */ +}; + +static const struct usb_temp_interface_desc *eth_interfaces[] = { + ð_control_interface, + ð_data_null_interface, + ð_data_interface, + NULL, +}; + +static const struct usb_temp_config_desc eth_config_desc = { + .ppIfaceDesc = eth_interfaces, + .bmAttributes = UC_BUS_POWERED, + .bMaxPower = 25, /* 50 mA */ + .iConfiguration = STRING_ETH_CONFIG_INDEX, +}; + +static const struct usb_temp_config_desc *eth_configs[] = { + ð_config_desc, + NULL, +}; + +const struct usb_temp_device_desc usb_template_cdce = { + .getStringDesc = ð_get_string_desc, + .ppConfigDesc = eth_configs, + .idVendor = USB_TEMPLATE_VENDOR, + .idProduct = 0x0001, + .bcdDevice = 0x0100, + .bDeviceClass = UDCLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .iManufacturer = STRING_ETH_VENDOR_INDEX, + .iProduct = STRING_ETH_PRODUCT_INDEX, + .iSerialNumber = STRING_ETH_SERIAL_INDEX, +}; + +/*------------------------------------------------------------------------* + * eth_get_string_desc + * + * Return values: + * NULL: Failure. No such string. + * Else: Success. Pointer to string descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +eth_get_string_desc(uint16_t lang_id, uint8_t string_index) +{ + static const void *ptr[STRING_ETH_MAX] = { + [STRING_LANG_INDEX] = &string_lang, + [STRING_MAC_INDEX] = &string_mac, + [STRING_ETH_CONTROL_INDEX] = &string_eth_control, + [STRING_ETH_DATA_INDEX] = &string_eth_data, + [STRING_ETH_CONFIG_INDEX] = &string_eth_config, + [STRING_ETH_VENDOR_INDEX] = &string_eth_vendor, + [STRING_ETH_PRODUCT_INDEX] = &string_eth_product, + [STRING_ETH_SERIAL_INDEX] = &string_eth_serial, + }; + + if (string_index == 0) { + return (&string_lang); + } + if (lang_id != 0x0409) { + return (NULL); + } + if (string_index < STRING_ETH_MAX) { + return (ptr[string_index]); + } + return (NULL); +} diff --git a/sys/bus/u4b/template/usb_template_kbd.c b/sys/bus/u4b/template/usb_template_kbd.c new file mode 100644 index 0000000000..6295825a73 --- /dev/null +++ b/sys/bus/u4b/template/usb_template_kbd.c @@ -0,0 +1,224 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2010 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains the USB template for an USB Keyboard Device. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +enum { + INDEX_LANG, + INDEX_KEYBOARD, + INDEX_PRODUCT, + INDEX_MAX, +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_PRODUCT \ + 'K', 0, 'e', 0, 'y', 0, 'b', 0, 'o', 0, 'a', 0, 'r', 0, 'd', 0, ' ', 0, \ + 'T', 0, 'e', 0, 's', 0, 't', 0, ' ', 0, \ + 'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0, ' ', 0, + +#define STRING_KEYBOARD \ + 'K', 0, 'e', 0, 'y', 0, 'b', 0, 'o', 0, 'a', 0, 'r', 0, 'd', 0, ' ', 0, \ + 'i', 0, 'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0, 'a', 0, 'c', 0, 'e', 0, + +/* make the real string descriptors */ + +USB_MAKE_STRING_DESC(STRING_LANG, string_lang); +USB_MAKE_STRING_DESC(STRING_KEYBOARD, string_keyboard); +USB_MAKE_STRING_DESC(STRING_PRODUCT, string_product); + +/* prototypes */ + +static const struct usb_temp_packet_size keyboard_intr_mps = { + .mps[USB_SPEED_LOW] = 16, + .mps[USB_SPEED_FULL] = 16, + .mps[USB_SPEED_HIGH] = 16, +}; + +static const struct usb_temp_interval keyboard_intr_interval = { + .bInterval[USB_SPEED_LOW] = 2, /* 2 ms */ + .bInterval[USB_SPEED_FULL] = 2, /* 2 ms */ + .bInterval[USB_SPEED_HIGH] = 5, /* 2 ms */ +}; + +/* The following HID descriptor was dumped from a HP keyboard. */ + +static uint8_t keyboard_hid_descriptor[] = { + 0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, + 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25, 0x01, + 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, + 0x75, 0x08, 0x81, 0x01, 0x95, 0x03, 0x75, 0x01, + 0x05, 0x08, 0x19, 0x01, 0x29, 0x03, 0x91, 0x02, + 0x95, 0x05, 0x75, 0x01, 0x91, 0x01, 0x95, 0x06, + 0x75, 0x08, 0x15, 0x00, 0x26, 0xff, 0x00, 0x05, + 0x07, 0x19, 0x00, 0x2a, 0xff, 0x00, 0x81, 0x00, + 0xc0 +}; + +static const struct usb_temp_endpoint_desc keyboard_ep_0 = { + .ppRawDesc = NULL, /* no raw descriptors */ + .pPacketSize = &keyboard_intr_mps, + .pIntervals = &keyboard_intr_interval, + .bEndpointAddress = UE_DIR_IN, + .bmAttributes = UE_INTERRUPT, +}; + +static const struct usb_temp_endpoint_desc *keyboard_endpoints[] = { + &keyboard_ep_0, + NULL, +}; + +static const uint8_t keyboard_raw_desc[] = { + 0x09, 0x21, 0x10, 0x01, 0x00, 0x01, 0x22, sizeof(keyboard_hid_descriptor), + 0x00 +}; + +static const void *keyboard_iface_0_desc[] = { + keyboard_raw_desc, + NULL, +}; + +static const struct usb_temp_interface_desc keyboard_iface_0 = { + .ppRawDesc = keyboard_iface_0_desc, + .ppEndpoints = keyboard_endpoints, + .bInterfaceClass = 3, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 1, + .iInterface = INDEX_KEYBOARD, +}; + +static const struct usb_temp_interface_desc *keyboard_interfaces[] = { + &keyboard_iface_0, + NULL, +}; + +static const struct usb_temp_config_desc keyboard_config_desc = { + .ppIfaceDesc = keyboard_interfaces, + .bmAttributes = UC_BUS_POWERED, + .bMaxPower = 25, /* 50 mA */ + .iConfiguration = INDEX_PRODUCT, +}; + +static const struct usb_temp_config_desc *keyboard_configs[] = { + &keyboard_config_desc, + NULL, +}; + +static usb_temp_get_string_desc_t keyboard_get_string_desc; +static usb_temp_get_vendor_desc_t keyboard_get_vendor_desc; + +const struct usb_temp_device_desc usb_template_kbd = { + .getStringDesc = &keyboard_get_string_desc, + .getVendorDesc = &keyboard_get_vendor_desc, + .ppConfigDesc = keyboard_configs, + .idVendor = USB_TEMPLATE_VENDOR, + .idProduct = 0x00CB, + .bcdDevice = 0x0100, + .bDeviceClass = UDCLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .iManufacturer = 0, + .iProduct = INDEX_PRODUCT, + .iSerialNumber = 0, +}; + +/*------------------------------------------------------------------------* + * keyboard_get_vendor_desc + * + * Return values: + * NULL: Failure. No such vendor descriptor. + * Else: Success. Pointer to vendor descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +keyboard_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen) +{ + if ((req->bmRequestType == 0x81) && (req->bRequest == 0x06) && + (req->wValue[0] == 0x00) && (req->wValue[1] == 0x22) && + (req->wIndex[1] == 0) && (req->wIndex[0] == 0)) { + + *plen = sizeof(keyboard_hid_descriptor); + return (keyboard_hid_descriptor); + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * keyboard_get_string_desc + * + * Return values: + * NULL: Failure. No such string. + * Else: Success. Pointer to string descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +keyboard_get_string_desc(uint16_t lang_id, uint8_t string_index) +{ + static const void *ptr[INDEX_MAX] = { + [INDEX_LANG] = &string_lang, + [INDEX_KEYBOARD] = &string_keyboard, + [INDEX_PRODUCT] = &string_product, + }; + + if (string_index == 0) { + return (&string_lang); + } + if (lang_id != 0x0409) { + return (NULL); + } + if (string_index < INDEX_MAX) { + return (ptr[string_index]); + } + return (NULL); +} diff --git a/sys/bus/u4b/template/usb_template_modem.c b/sys/bus/u4b/template/usb_template_modem.c new file mode 100644 index 0000000000..abc6f26be7 --- /dev/null +++ b/sys/bus/u4b/template/usb_template_modem.c @@ -0,0 +1,252 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2010 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains the USB template for an USB Modem Device. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +enum { + INDEX_LANG, + INDEX_MODEM, + INDEX_PRODUCT, + INDEX_MAX, +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_PRODUCT \ + 'M', 0, 'o', 0, 'd', 0, 'e', 0, 'm', 0, ' ', 0, \ + 'T', 0, 'e', 0, 's', 0, 't', 0, ' ', 0, \ + 'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0, ' ', 0, + +#define STRING_MODEM \ + 'M', 0, 'o', 0, 'd', 0, 'e', 0, 'm', 0, ' ', 0, \ + 'i', 0, 'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0, 'a', 0, 'c', 0, 'e', 0, + +/* make the real string descriptors */ + +USB_MAKE_STRING_DESC(STRING_LANG, string_lang); +USB_MAKE_STRING_DESC(STRING_MODEM, string_modem); +USB_MAKE_STRING_DESC(STRING_PRODUCT, string_product); + +#define MODEM_IFACE_0 0 +#define MODEM_IFACE_1 1 + +/* prototypes */ + +static const struct usb_temp_packet_size modem_bulk_mps = { + .mps[USB_SPEED_LOW] = 8, + .mps[USB_SPEED_FULL] = 64, + .mps[USB_SPEED_HIGH] = 512, +}; + +static const struct usb_temp_packet_size modem_intr_mps = { + .mps[USB_SPEED_LOW] = 8, + .mps[USB_SPEED_FULL] = 8, + .mps[USB_SPEED_HIGH] = 8, +}; + +static const struct usb_temp_interval modem_intr_interval = { + .bInterval[USB_SPEED_LOW] = 8, /* 8ms */ + .bInterval[USB_SPEED_FULL] = 8, /* 8ms */ + .bInterval[USB_SPEED_HIGH] = 7, /* 8ms */ +}; + +static const struct usb_temp_endpoint_desc modem_ep_0 = { + .pPacketSize = &modem_intr_mps, + .pIntervals = &modem_intr_interval, + .bEndpointAddress = UE_DIR_IN, + .bmAttributes = UE_INTERRUPT, +}; + +static const struct usb_temp_endpoint_desc modem_ep_1 = { + .pPacketSize = &modem_bulk_mps, + .bEndpointAddress = UE_DIR_OUT, + .bmAttributes = UE_BULK, +}; + +static const struct usb_temp_endpoint_desc modem_ep_2 = { + .pPacketSize = &modem_bulk_mps, + .bEndpointAddress = UE_DIR_IN, + .bmAttributes = UE_BULK, +}; + +static const struct usb_temp_endpoint_desc *modem_iface_0_ep[] = { + &modem_ep_0, + NULL, +}; + +static const struct usb_temp_endpoint_desc *modem_iface_1_ep[] = { + &modem_ep_1, + &modem_ep_2, + NULL, +}; + +static const uint8_t modem_raw_desc_0[] = { + 0x05, 0x24, 0x00, 0x10, 0x01 +}; + +static const uint8_t modem_raw_desc_1[] = { + 0x05, 0x24, 0x06, MODEM_IFACE_0, MODEM_IFACE_1 +}; + +static const uint8_t modem_raw_desc_2[] = { + 0x05, 0x24, 0x01, 0x03, MODEM_IFACE_1 +}; + +static const uint8_t modem_raw_desc_3[] = { + 0x04, 0x24, 0x02, 0x07 +}; + +static const void *modem_iface_0_desc[] = { + &modem_raw_desc_0, + &modem_raw_desc_1, + &modem_raw_desc_2, + &modem_raw_desc_3, + NULL, +}; + +static const struct usb_temp_interface_desc modem_iface_0 = { + .ppRawDesc = modem_iface_0_desc, + .ppEndpoints = modem_iface_0_ep, + .bInterfaceClass = 2, + .bInterfaceSubClass = 2, + .bInterfaceProtocol = 1, + .iInterface = INDEX_MODEM, +}; + +static const struct usb_temp_interface_desc modem_iface_1 = { + .ppEndpoints = modem_iface_1_ep, + .bInterfaceClass = 10, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = INDEX_MODEM, +}; + +static const struct usb_temp_interface_desc *modem_interfaces[] = { + &modem_iface_0, + &modem_iface_1, + NULL, +}; + +static const struct usb_temp_config_desc modem_config_desc = { + .ppIfaceDesc = modem_interfaces, + .bmAttributes = UC_BUS_POWERED, + .bMaxPower = 25, /* 50 mA */ + .iConfiguration = INDEX_PRODUCT, +}; + +static const struct usb_temp_config_desc *modem_configs[] = { + &modem_config_desc, + NULL, +}; + +static usb_temp_get_string_desc_t modem_get_string_desc; +static usb_temp_get_vendor_desc_t modem_get_vendor_desc; + +const struct usb_temp_device_desc usb_template_modem = { + .getStringDesc = &modem_get_string_desc, + .getVendorDesc = &modem_get_vendor_desc, + .ppConfigDesc = modem_configs, + .idVendor = USB_TEMPLATE_VENDOR, + .idProduct = 0x000E, + .bcdDevice = 0x0100, + .bDeviceClass = UDCLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .iManufacturer = 0, + .iProduct = INDEX_PRODUCT, + .iSerialNumber = 0, +}; + +/*------------------------------------------------------------------------* + * modem_get_vendor_desc + * + * Return values: + * NULL: Failure. No such vendor descriptor. + * Else: Success. Pointer to vendor descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +modem_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen) +{ + return (NULL); +} + +/*------------------------------------------------------------------------* + * modem_get_string_desc + * + * Return values: + * NULL: Failure. No such string. + * Else: Success. Pointer to string descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +modem_get_string_desc(uint16_t lang_id, uint8_t string_index) +{ + static const void *ptr[INDEX_MAX] = { + [INDEX_LANG] = &string_lang, + [INDEX_MODEM] = &string_modem, + [INDEX_PRODUCT] = &string_product, + }; + + if (string_index == 0) { + return (&string_lang); + } + if (lang_id != 0x0409) { + return (NULL); + } + if (string_index < INDEX_MAX) { + return (ptr[string_index]); + } + return (NULL); +} diff --git a/sys/bus/u4b/template/usb_template_mouse.c b/sys/bus/u4b/template/usb_template_mouse.c new file mode 100644 index 0000000000..628c9a5e23 --- /dev/null +++ b/sys/bus/u4b/template/usb_template_mouse.c @@ -0,0 +1,222 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2010 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains the USB template for an USB Mouse Device. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +enum { + INDEX_LANG, + INDEX_MOUSE, + INDEX_PRODUCT, + INDEX_MAX, +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_PRODUCT \ + 'M', 0, 'o', 0, 'u', 0, 's', 0, 'e', 0, ' ', 0, \ + 'T', 0, 'e', 0, 's', 0, 't', 0, ' ', 0, \ + 'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0, + +#define STRING_MOUSE \ + 'M', 0, 'o', 0, 'u', 0, 's', 0, 'e', 0, ' ', 0, \ + 'i', 0, 'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0, 'a', 0, 'c', 0, 'e', 0, + +/* make the real string descriptors */ + +USB_MAKE_STRING_DESC(STRING_LANG, string_lang); +USB_MAKE_STRING_DESC(STRING_MOUSE, string_mouse); +USB_MAKE_STRING_DESC(STRING_PRODUCT, string_product); + +/* prototypes */ + +/* The following HID descriptor was dumped from a HP mouse. */ + +static uint8_t mouse_hid_descriptor[] = { + 0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01, + 0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03, + 0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01, + 0x81, 0x02, 0x95, 0x05, 0x81, 0x03, 0x05, 0x01, + 0x09, 0x30, 0x09, 0x31, 0x09, 0x38, 0x15, 0x81, + 0x25, 0x7f, 0x75, 0x08, 0x95, 0x03, 0x81, 0x06, + 0xc0, 0xc0 +}; + +static const struct usb_temp_packet_size mouse_intr_mps = { + .mps[USB_SPEED_LOW] = 8, + .mps[USB_SPEED_FULL] = 8, + .mps[USB_SPEED_HIGH] = 8, +}; + +static const struct usb_temp_interval mouse_intr_interval = { + .bInterval[USB_SPEED_LOW] = 2, /* 2ms */ + .bInterval[USB_SPEED_FULL] = 2, /* 2ms */ + .bInterval[USB_SPEED_HIGH] = 5, /* 2ms */ +}; + +static const struct usb_temp_endpoint_desc mouse_ep_0 = { + .ppRawDesc = NULL, /* no raw descriptors */ + .pPacketSize = &mouse_intr_mps, + .pIntervals = &mouse_intr_interval, + .bEndpointAddress = UE_DIR_IN, + .bmAttributes = UE_INTERRUPT, +}; + +static const struct usb_temp_endpoint_desc *mouse_endpoints[] = { + &mouse_ep_0, + NULL, +}; + +static const uint8_t mouse_raw_desc[] = { + 0x09, 0x21, 0x10, 0x01, 0x00, 0x01, 0x22, sizeof(mouse_hid_descriptor), + 0x00 +}; + +static const void *mouse_iface_0_desc[] = { + mouse_raw_desc, + NULL, +}; + +static const struct usb_temp_interface_desc mouse_iface_0 = { + .ppRawDesc = mouse_iface_0_desc, + .ppEndpoints = mouse_endpoints, + .bInterfaceClass = 3, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 2, + .iInterface = INDEX_MOUSE, +}; + +static const struct usb_temp_interface_desc *mouse_interfaces[] = { + &mouse_iface_0, + NULL, +}; + +static const struct usb_temp_config_desc mouse_config_desc = { + .ppIfaceDesc = mouse_interfaces, + .bmAttributes = UC_BUS_POWERED, + .bMaxPower = 25, /* 50 mA */ + .iConfiguration = INDEX_PRODUCT, +}; + +static const struct usb_temp_config_desc *mouse_configs[] = { + &mouse_config_desc, + NULL, +}; + +static usb_temp_get_string_desc_t mouse_get_string_desc; +static usb_temp_get_vendor_desc_t mouse_get_vendor_desc; + +const struct usb_temp_device_desc usb_template_mouse = { + .getStringDesc = &mouse_get_string_desc, + .getVendorDesc = &mouse_get_vendor_desc, + .ppConfigDesc = mouse_configs, + .idVendor = USB_TEMPLATE_VENDOR, + .idProduct = 0x00AE, + .bcdDevice = 0x0100, + .bDeviceClass = UDCLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .iManufacturer = 0, + .iProduct = INDEX_PRODUCT, + .iSerialNumber = 0, +}; + +/*------------------------------------------------------------------------* + * mouse_get_vendor_desc + * + * Return values: + * NULL: Failure. No such vendor descriptor. + * Else: Success. Pointer to vendor descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +mouse_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen) +{ + if ((req->bmRequestType == 0x81) && (req->bRequest == 0x06) && + (req->wValue[0] == 0x00) && (req->wValue[1] == 0x22) && + (req->wIndex[1] == 0) && (req->wIndex[0] == 0)) { + + *plen = sizeof(mouse_hid_descriptor); + return (mouse_hid_descriptor); + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * mouse_get_string_desc + * + * Return values: + * NULL: Failure. No such string. + * Else: Success. Pointer to string descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +mouse_get_string_desc(uint16_t lang_id, uint8_t string_index) +{ + static const void *ptr[INDEX_MAX] = { + [INDEX_LANG] = &string_lang, + [INDEX_MOUSE] = &string_mouse, + [INDEX_PRODUCT] = &string_product, + }; + + if (string_index == 0) { + return (&string_lang); + } + if (lang_id != 0x0409) { + return (NULL); + } + if (string_index < INDEX_MAX) { + return (ptr[string_index]); + } + return (NULL); +} diff --git a/sys/bus/u4b/template/usb_template_msc.c b/sys/bus/u4b/template/usb_template_msc.c new file mode 100644 index 0000000000..5c05ffe8e2 --- /dev/null +++ b/sys/bus/u4b/template/usb_template_msc.c @@ -0,0 +1,216 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2008 Hans Petter Selasky + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains the USB templates for an USB Mass Storage Device. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +enum { + STRING_LANG_INDEX, + STRING_MSC_DATA_INDEX, + STRING_MSC_CONFIG_INDEX, + STRING_MSC_VENDOR_INDEX, + STRING_MSC_PRODUCT_INDEX, + STRING_MSC_SERIAL_INDEX, + STRING_MSC_MAX, +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_MSC_DATA \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'M', 0, 'a', 0, 's', 0, 's', 0, \ + ' ', 0, 'S', 0, 't', 0, 'o', 0, \ + 'r', 0, 'a', 0, 'g', 0, 'e', 0, \ + ' ', 0, 'I', 0, 'n', 0, 't', 0, \ + 'e', 0, 'r', 0, 'f', 0, 'a', 0, \ + 'c', 0, 'e', 0, + +#define STRING_MSC_CONFIG \ + 'D', 0, 'e', 0, 'f', 0, 'a', 0, \ + 'u', 0, 'l', 0, 't', 0, ' ', 0, \ + 'c', 0, 'o', 0, 'n', 0, 'f', 0, \ + 'i', 0, 'g', 0, + +#define STRING_MSC_VENDOR \ + 'F', 0, 'r', 0, 'e', 0, 'e', 0, \ + 'B', 0, 'S', 0, 'D', 0, ' ', 0, \ + 'f', 0, 'o', 0, 'u', 0, 'n', 0, \ + 'd', 0, 'a', 0, 't', 0, 'i', 0, \ + 'o', 0, 'n', 0, + +#define STRING_MSC_PRODUCT \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'M', 0, 'e', 0, 'm', 0, 'o', 0, \ + 'r', 0, 'y', 0, ' ', 0, 'S', 0, \ + 't', 0, 'i', 0, 'c', 0, 'k', 0 + +#define STRING_MSC_SERIAL \ + 'M', 0, 'a', 0, 'r', 0, 'c', 0, \ + 'h', 0, ' ', 0, '2', 0, '0', 0, \ + '0', 0, '8', 0, + +/* make the real string descriptors */ + +USB_MAKE_STRING_DESC(STRING_LANG, string_lang); +USB_MAKE_STRING_DESC(STRING_MSC_DATA, string_msc_data); +USB_MAKE_STRING_DESC(STRING_MSC_CONFIG, string_msc_config); +USB_MAKE_STRING_DESC(STRING_MSC_VENDOR, string_msc_vendor); +USB_MAKE_STRING_DESC(STRING_MSC_PRODUCT, string_msc_product); +USB_MAKE_STRING_DESC(STRING_MSC_SERIAL, string_msc_serial); + +/* prototypes */ + +static usb_temp_get_string_desc_t msc_get_string_desc; + +static const struct usb_temp_packet_size bulk_mps = { + .mps[USB_SPEED_FULL] = 64, + .mps[USB_SPEED_HIGH] = 512, +}; + +static const struct usb_temp_endpoint_desc bulk_in_ep = { + .pPacketSize = &bulk_mps, +#ifdef USB_HIP_IN_EP_0 + .bEndpointAddress = USB_HIP_IN_EP_0, +#else + .bEndpointAddress = UE_DIR_IN, +#endif + .bmAttributes = UE_BULK, +}; + +static const struct usb_temp_endpoint_desc bulk_out_ep = { + .pPacketSize = &bulk_mps, +#ifdef USB_HIP_OUT_EP_0 + .bEndpointAddress = USB_HIP_OUT_EP_0, +#else + .bEndpointAddress = UE_DIR_OUT, +#endif + .bmAttributes = UE_BULK, +}; + +static const struct usb_temp_endpoint_desc *msc_data_endpoints[] = { + &bulk_in_ep, + &bulk_out_ep, + NULL, +}; + +static const struct usb_temp_interface_desc msc_data_interface = { + .ppEndpoints = msc_data_endpoints, + .bInterfaceClass = UICLASS_MASS, + .bInterfaceSubClass = UISUBCLASS_SCSI, + .bInterfaceProtocol = UIPROTO_MASS_BBB, + .iInterface = STRING_MSC_DATA_INDEX, +}; + +static const struct usb_temp_interface_desc *msc_interfaces[] = { + &msc_data_interface, + NULL, +}; + +static const struct usb_temp_config_desc msc_config_desc = { + .ppIfaceDesc = msc_interfaces, + .bmAttributes = UC_BUS_POWERED, + .bMaxPower = 25, /* 50 mA */ + .iConfiguration = STRING_MSC_CONFIG_INDEX, +}; + +static const struct usb_temp_config_desc *msc_configs[] = { + &msc_config_desc, + NULL, +}; + +const struct usb_temp_device_desc usb_template_msc = { + .getStringDesc = &msc_get_string_desc, + .ppConfigDesc = msc_configs, + .idVendor = USB_TEMPLATE_VENDOR, + .idProduct = 0x0012, + .bcdDevice = 0x0100, + .bDeviceClass = UDCLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .iManufacturer = STRING_MSC_VENDOR_INDEX, + .iProduct = STRING_MSC_PRODUCT_INDEX, + .iSerialNumber = STRING_MSC_SERIAL_INDEX, +}; + +/*------------------------------------------------------------------------* + * msc_get_string_desc + * + * Return values: + * NULL: Failure. No such string. + * Else: Success. Pointer to string descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +msc_get_string_desc(uint16_t lang_id, uint8_t string_index) +{ + static const void *ptr[STRING_MSC_MAX] = { + [STRING_LANG_INDEX] = &string_lang, + [STRING_MSC_DATA_INDEX] = &string_msc_data, + [STRING_MSC_CONFIG_INDEX] = &string_msc_config, + [STRING_MSC_VENDOR_INDEX] = &string_msc_vendor, + [STRING_MSC_PRODUCT_INDEX] = &string_msc_product, + [STRING_MSC_SERIAL_INDEX] = &string_msc_serial, + }; + + if (string_index == 0) { + return (&string_lang); + } + if (lang_id != 0x0409) { + return (NULL); + } + if (string_index < STRING_MSC_MAX) { + return (ptr[string_index]); + } + return (NULL); +} diff --git a/sys/bus/u4b/template/usb_template_mtp.c b/sys/bus/u4b/template/usb_template_mtp.c new file mode 100644 index 0000000000..f48fbf422b --- /dev/null +++ b/sys/bus/u4b/template/usb_template_mtp.c @@ -0,0 +1,278 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2008 Hans Petter Selasky + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains the USB templates for an USB Message Transfer + * Protocol device. + * + * NOTE: It is common practice that MTP devices use some dummy + * descriptor cludges to be automatically detected by the host + * operating system. These descriptors are documented in the LibMTP + * library at sourceforge.net. The alternative is to supply the host + * operating system the VID and PID of your device. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define MTP_BREQUEST 0x08 + +enum { + STRING_LANG_INDEX, + STRING_MTP_DATA_INDEX, + STRING_MTP_CONFIG_INDEX, + STRING_MTP_VENDOR_INDEX, + STRING_MTP_PRODUCT_INDEX, + STRING_MTP_SERIAL_INDEX, + STRING_MTP_MAX, +}; + +#define STRING_LANG \ + 0x09, 0x04, /* American English */ + +#define STRING_MTP_DATA \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'M', 0, 'T', 0, 'P', 0, \ + ' ', 0, 'I', 0, 'n', 0, 't', 0, \ + 'e', 0, 'r', 0, 'f', 0, 'a', 0, \ + 'c', 0, 'e', 0, + +#define STRING_MTP_CONFIG \ + 'D', 0, 'e', 0, 'f', 0, 'a', 0, \ + 'u', 0, 'l', 0, 't', 0, ' ', 0, \ + 'c', 0, 'o', 0, 'n', 0, 'f', 0, \ + 'i', 0, 'g', 0, + +#define STRING_MTP_VENDOR \ + 'F', 0, 'r', 0, 'e', 0, 'e', 0, \ + 'B', 0, 'S', 0, 'D', 0, ' ', 0, \ + 'f', 0, 'o', 0, 'u', 0, 'n', 0, \ + 'd', 0, 'a', 0, 't', 0, 'i', 0, \ + 'o', 0, 'n', 0, + +#define STRING_MTP_PRODUCT \ + 'U', 0, 'S', 0, 'B', 0, ' ', 0, \ + 'M', 0, 'T', 0, 'P', 0, + +#define STRING_MTP_SERIAL \ + 'J', 0, 'u', 0, 'n', 0, 'e', 0, \ + ' ', 0, '2', 0, '0', 0, '0', 0, \ + '8', 0, + +/* make the real string descriptors */ + +USB_MAKE_STRING_DESC(STRING_LANG, string_lang); +USB_MAKE_STRING_DESC(STRING_MTP_DATA, string_mtp_data); +USB_MAKE_STRING_DESC(STRING_MTP_CONFIG, string_mtp_config); +USB_MAKE_STRING_DESC(STRING_MTP_VENDOR, string_mtp_vendor); +USB_MAKE_STRING_DESC(STRING_MTP_PRODUCT, string_mtp_product); +USB_MAKE_STRING_DESC(STRING_MTP_SERIAL, string_mtp_serial); + +/* prototypes */ + +static usb_temp_get_string_desc_t mtp_get_string_desc; +static usb_temp_get_vendor_desc_t mtp_get_vendor_desc; + +static const struct usb_temp_packet_size bulk_mps = { + .mps[USB_SPEED_FULL] = 64, + .mps[USB_SPEED_HIGH] = 512, +}; + +static const struct usb_temp_packet_size intr_mps = { + .mps[USB_SPEED_FULL] = 64, + .mps[USB_SPEED_HIGH] = 64, +}; + +static const struct usb_temp_endpoint_desc bulk_out_ep = { + .pPacketSize = &bulk_mps, +#ifdef USB_HIP_OUT_EP_0 + .bEndpointAddress = USB_HIP_OUT_EP_0, +#else + .bEndpointAddress = UE_DIR_OUT, +#endif + .bmAttributes = UE_BULK, +}; + +static const struct usb_temp_endpoint_desc intr_in_ep = { + .pPacketSize = &intr_mps, + .bEndpointAddress = UE_DIR_IN, + .bmAttributes = UE_INTERRUPT, +}; + +static const struct usb_temp_endpoint_desc bulk_in_ep = { + .pPacketSize = &bulk_mps, +#ifdef USB_HIP_IN_EP_0 + .bEndpointAddress = USB_HIP_IN_EP_0, +#else + .bEndpointAddress = UE_DIR_IN, +#endif + .bmAttributes = UE_BULK, +}; + +static const struct usb_temp_endpoint_desc *mtp_data_endpoints[] = { + &bulk_in_ep, + &bulk_out_ep, + &intr_in_ep, + NULL, +}; + +static const struct usb_temp_interface_desc mtp_data_interface = { + .ppEndpoints = mtp_data_endpoints, + .bInterfaceClass = UICLASS_IMAGE, + .bInterfaceSubClass = UISUBCLASS_SIC, /* Still Image Class */ + .bInterfaceProtocol = 1, /* PIMA 15740 */ + .iInterface = STRING_MTP_DATA_INDEX, +}; + +static const struct usb_temp_interface_desc *mtp_interfaces[] = { + &mtp_data_interface, + NULL, +}; + +static const struct usb_temp_config_desc mtp_config_desc = { + .ppIfaceDesc = mtp_interfaces, + .bmAttributes = UC_BUS_POWERED, + .bMaxPower = 25, /* 50 mA */ + .iConfiguration = STRING_MTP_CONFIG_INDEX, +}; + +static const struct usb_temp_config_desc *mtp_configs[] = { + &mtp_config_desc, + NULL, +}; + +const struct usb_temp_device_desc usb_template_mtp = { + .getStringDesc = &mtp_get_string_desc, + .getVendorDesc = &mtp_get_vendor_desc, + .ppConfigDesc = mtp_configs, + .idVendor = USB_TEMPLATE_VENDOR, + .idProduct = 0x0011, + .bcdDevice = 0x0100, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .iManufacturer = STRING_MTP_VENDOR_INDEX, + .iProduct = STRING_MTP_PRODUCT_INDEX, + .iSerialNumber = STRING_MTP_SERIAL_INDEX, +}; + +/*------------------------------------------------------------------------* + * mtp_get_vendor_desc + * + * Return values: + * NULL: Failure. No such vendor descriptor. + * Else: Success. Pointer to vendor descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +mtp_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen) +{ + static const uint8_t dummy_desc[0x28] = { + 0x28, 0, 0, 0, 0, 1, 4, 0, + 1, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0x4D, 0x54, 0x50, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + + if ((req->bmRequestType == UT_READ_VENDOR_DEVICE) && + (req->bRequest == MTP_BREQUEST) && (req->wValue[0] == 0) && + (req->wValue[1] == 0) && (req->wIndex[1] == 0) && + ((req->wIndex[0] == 4) || (req->wIndex[0] == 5))) { + /* + * By returning this descriptor LibMTP will + * automatically pickup our device. + */ + return (dummy_desc); + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * mtp_get_string_desc + * + * Return values: + * NULL: Failure. No such string. + * Else: Success. Pointer to string descriptor is returned. + *------------------------------------------------------------------------*/ +static const void * +mtp_get_string_desc(uint16_t lang_id, uint8_t string_index) +{ + static const void *ptr[STRING_MTP_MAX] = { + [STRING_LANG_INDEX] = &string_lang, + [STRING_MTP_DATA_INDEX] = &string_mtp_data, + [STRING_MTP_CONFIG_INDEX] = &string_mtp_config, + [STRING_MTP_VENDOR_INDEX] = &string_mtp_vendor, + [STRING_MTP_PRODUCT_INDEX] = &string_mtp_product, + [STRING_MTP_SERIAL_INDEX] = &string_mtp_serial, + }; + + static const uint8_t dummy_desc[0x12] = { + 0x12, 0x03, 0x4D, 0x00, 0x53, 0x00, 0x46, 0x00, + 0x54, 0x00, 0x31, 0x00, 0x30, 0x00, 0x30, 0x00, + MTP_BREQUEST, 0x00, + }; + + if (string_index == 0xEE) { + /* + * By returning this string LibMTP will automatically + * pickup our device. + */ + return (dummy_desc); + } + if (string_index == 0) { + return (&string_lang); + } + if (lang_id != 0x0409) { + return (NULL); + } + if (string_index < STRING_MTP_MAX) { + return (ptr[string_index]); + } + return (NULL); +} diff --git a/sys/bus/u4b/ufm_ioctl.h b/sys/bus/u4b/ufm_ioctl.h new file mode 100644 index 0000000000..921b3d4f37 --- /dev/null +++ b/sys/bus/u4b/ufm_ioctl.h @@ -0,0 +1,39 @@ +/*- + * Copyright (c) 2001 M. Warner Losh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This code is based on ugen.c and ulpt.c developed by Lennart Augustsson. + * This code includes software developed by the NetBSD Foundation, Inc. and + * its contributors. + */ + +/* $FreeBSD$ */ + +#include + +#define FM_SET_FREQ _IOWR('U', 200, int) +#define FM_GET_FREQ _IOWR('U', 201, int) +#define FM_START _IOWR('U', 202, int) +#define FM_STOP _IOWR('U', 203, int) +#define FM_GET_STAT _IOWR('U', 204, int) diff --git a/sys/bus/u4b/usb.h b/sys/bus/u4b/usb.h new file mode 100644 index 0000000000..eac27620ac --- /dev/null +++ b/sys/bus/u4b/usb.h @@ -0,0 +1,757 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file contains standard definitions for the following USB + * protocol versions: + * + * USB v1.0 + * USB v1.1 + * USB v2.0 + * USB v3.0 + */ + +#ifndef _USB_STANDARD_H_ +#define _USB_STANDARD_H_ + +#if defined(_KERNEL) +#include "opt_usb.h" + +/* Declare parent SYSCTL USB node. */ +#ifdef SYSCTL_DECL +SYSCTL_DECL(_hw_usb); +#endif + +#include + +MALLOC_DECLARE(M_USB); +MALLOC_DECLARE(M_USBDEV); +MALLOC_DECLARE(M_USBHC); +#endif /* _KERNEL */ + +#include +#include + +#define USB_STACK_VERSION 2000 /* 2.0 */ + +/* Definition of some hardcoded USB constants. */ + +#define USB_MAX_IPACKET 8 /* initial USB packet size */ +#define USB_EP_MAX (2*16) /* hardcoded */ +#define USB_ROOT_HUB_ADDR 1 /* index */ +#define USB_MIN_DEVICES 2 /* unused + root HUB */ +#define USB_UNCONFIG_INDEX 0xFF /* internal use only */ +#define USB_IFACE_INDEX_ANY 0xFF /* internal use only */ +#define USB_START_ADDR 0 /* default USB device BUS address + * after USB bus reset */ +#define USB_CONTROL_ENDPOINT 0 /* default control endpoint */ + +#define USB_FRAMES_PER_SECOND_FS 1000 /* full speed */ +#define USB_FRAMES_PER_SECOND_HS 8000 /* high speed */ + +#define USB_FS_BYTES_PER_HS_UFRAME 188 /* bytes */ +#define USB_HS_MICRO_FRAMES_MAX 8 /* units */ + +#define USB_ISOC_TIME_MAX 128 /* ms */ + +/* + * Minimum time a device needs to be powered down to go through a + * power cycle. These values are not in the USB specification. + */ +#define USB_POWER_DOWN_TIME 200 /* ms */ +#define USB_PORT_POWER_DOWN_TIME 100 /* ms */ + +/* Definition of software USB power modes */ +#define USB_POWER_MODE_OFF 0 /* turn off device */ +#define USB_POWER_MODE_ON 1 /* always on */ +#define USB_POWER_MODE_SAVE 2 /* automatic suspend and resume */ +#define USB_POWER_MODE_SUSPEND 3 /* force suspend */ +#define USB_POWER_MODE_RESUME 4 /* force resume */ + +#if 0 +/* These are the values from the USB specification. */ +#define USB_PORT_RESET_DELAY 10 /* ms */ +#define USB_PORT_ROOT_RESET_DELAY 50 /* ms */ +#define USB_PORT_RESET_RECOVERY 10 /* ms */ +#define USB_PORT_POWERUP_DELAY 100 /* ms */ +#define USB_PORT_RESUME_DELAY 20 /* ms */ +#define USB_SET_ADDRESS_SETTLE 2 /* ms */ +#define USB_RESUME_DELAY (20*5) /* ms */ +#define USB_RESUME_WAIT 10 /* ms */ +#define USB_RESUME_RECOVERY 10 /* ms */ +#define USB_EXTRA_POWER_UP_TIME 0 /* ms */ +#else +/* Allow for marginal and non-conforming devices. */ +#define USB_PORT_RESET_DELAY 50 /* ms */ +#define USB_PORT_ROOT_RESET_DELAY 250 /* ms */ +#define USB_PORT_RESET_RECOVERY 250 /* ms */ +#define USB_PORT_POWERUP_DELAY 300 /* ms */ +#define USB_PORT_RESUME_DELAY (20*2) /* ms */ +#define USB_SET_ADDRESS_SETTLE 10 /* ms */ +#define USB_RESUME_DELAY (50*5) /* ms */ +#define USB_RESUME_WAIT 50 /* ms */ +#define USB_RESUME_RECOVERY 50 /* ms */ +#define USB_EXTRA_POWER_UP_TIME 20 /* ms */ +#endif + +#define USB_MIN_POWER 100 /* mA */ +#define USB_MAX_POWER 500 /* mA */ + +#define USB_BUS_RESET_DELAY 100 /* ms */ + +/* + * USB record layout in memory: + * + * - USB config 0 + * - USB interfaces + * - USB alternative interfaces + * - USB endpoints + * + * - USB config 1 + * - USB interfaces + * - USB alternative interfaces + * - USB endpoints + */ + +/* Declaration of USB records */ + +struct usb_device_request { + uByte bmRequestType; + uByte bRequest; + uWord wValue; + uWord wIndex; + uWord wLength; +} __packed; +typedef struct usb_device_request usb_device_request_t; + +#define UT_WRITE 0x00 +#define UT_READ 0x80 +#define UT_STANDARD 0x00 +#define UT_CLASS 0x20 +#define UT_VENDOR 0x40 +#define UT_DEVICE 0x00 +#define UT_INTERFACE 0x01 +#define UT_ENDPOINT 0x02 +#define UT_OTHER 0x03 + +#define UT_READ_DEVICE (UT_READ | UT_STANDARD | UT_DEVICE) +#define UT_READ_INTERFACE (UT_READ | UT_STANDARD | UT_INTERFACE) +#define UT_READ_ENDPOINT (UT_READ | UT_STANDARD | UT_ENDPOINT) +#define UT_WRITE_DEVICE (UT_WRITE | UT_STANDARD | UT_DEVICE) +#define UT_WRITE_INTERFACE (UT_WRITE | UT_STANDARD | UT_INTERFACE) +#define UT_WRITE_ENDPOINT (UT_WRITE | UT_STANDARD | UT_ENDPOINT) +#define UT_READ_CLASS_DEVICE (UT_READ | UT_CLASS | UT_DEVICE) +#define UT_READ_CLASS_INTERFACE (UT_READ | UT_CLASS | UT_INTERFACE) +#define UT_READ_CLASS_OTHER (UT_READ | UT_CLASS | UT_OTHER) +#define UT_READ_CLASS_ENDPOINT (UT_READ | UT_CLASS | UT_ENDPOINT) +#define UT_WRITE_CLASS_DEVICE (UT_WRITE | UT_CLASS | UT_DEVICE) +#define UT_WRITE_CLASS_INTERFACE (UT_WRITE | UT_CLASS | UT_INTERFACE) +#define UT_WRITE_CLASS_OTHER (UT_WRITE | UT_CLASS | UT_OTHER) +#define UT_WRITE_CLASS_ENDPOINT (UT_WRITE | UT_CLASS | UT_ENDPOINT) +#define UT_READ_VENDOR_DEVICE (UT_READ | UT_VENDOR | UT_DEVICE) +#define UT_READ_VENDOR_INTERFACE (UT_READ | UT_VENDOR | UT_INTERFACE) +#define UT_READ_VENDOR_OTHER (UT_READ | UT_VENDOR | UT_OTHER) +#define UT_READ_VENDOR_ENDPOINT (UT_READ | UT_VENDOR | UT_ENDPOINT) +#define UT_WRITE_VENDOR_DEVICE (UT_WRITE | UT_VENDOR | UT_DEVICE) +#define UT_WRITE_VENDOR_INTERFACE (UT_WRITE | UT_VENDOR | UT_INTERFACE) +#define UT_WRITE_VENDOR_OTHER (UT_WRITE | UT_VENDOR | UT_OTHER) +#define UT_WRITE_VENDOR_ENDPOINT (UT_WRITE | UT_VENDOR | UT_ENDPOINT) + +/* Requests */ +#define UR_GET_STATUS 0x00 +#define UR_CLEAR_FEATURE 0x01 +#define UR_SET_FEATURE 0x03 +#define UR_SET_ADDRESS 0x05 +#define UR_GET_DESCRIPTOR 0x06 +#define UDESC_DEVICE 0x01 +#define UDESC_CONFIG 0x02 +#define UDESC_STRING 0x03 +#define USB_LANGUAGE_TABLE 0x00 /* language ID string index */ +#define UDESC_INTERFACE 0x04 +#define UDESC_ENDPOINT 0x05 +#define UDESC_DEVICE_QUALIFIER 0x06 +#define UDESC_OTHER_SPEED_CONFIGURATION 0x07 +#define UDESC_INTERFACE_POWER 0x08 +#define UDESC_OTG 0x09 +#define UDESC_DEBUG 0x0A +#define UDESC_IFACE_ASSOC 0x0B /* interface association */ +#define UDESC_BOS 0x0F /* binary object store */ +#define UDESC_DEVICE_CAPABILITY 0x10 +#define UDESC_CS_DEVICE 0x21 /* class specific */ +#define UDESC_CS_CONFIG 0x22 +#define UDESC_CS_STRING 0x23 +#define UDESC_CS_INTERFACE 0x24 +#define UDESC_CS_ENDPOINT 0x25 +#define UDESC_HUB 0x29 +#define UDESC_SS_HUB 0x2A /* super speed */ +#define UDESC_ENDPOINT_SS_COMP 0x30 /* super speed */ +#define UR_SET_DESCRIPTOR 0x07 +#define UR_GET_CONFIG 0x08 +#define UR_SET_CONFIG 0x09 +#define UR_GET_INTERFACE 0x0a +#define UR_SET_INTERFACE 0x0b +#define UR_SYNCH_FRAME 0x0c +#define UR_SET_SEL 0x30 +#define UR_ISOCH_DELAY 0x31 + +/* HUB specific request */ +#define UR_GET_BUS_STATE 0x02 +#define UR_CLEAR_TT_BUFFER 0x08 +#define UR_RESET_TT 0x09 +#define UR_GET_TT_STATE 0x0a +#define UR_STOP_TT 0x0b +#define UR_SET_HUB_DEPTH 0x0c +#define USB_SS_HUB_DEPTH_MAX 5 +#define UR_GET_PORT_ERR_COUNT 0x0d + +/* Feature numbers */ +#define UF_ENDPOINT_HALT 0 +#define UF_DEVICE_REMOTE_WAKEUP 1 +#define UF_TEST_MODE 2 +#define UF_U1_ENABLE 0x30 +#define UF_U2_ENABLE 0x31 +#define UF_LTM_ENABLE 0x32 + +/* HUB specific features */ +#define UHF_C_HUB_LOCAL_POWER 0 +#define UHF_C_HUB_OVER_CURRENT 1 +#define UHF_PORT_CONNECTION 0 +#define UHF_PORT_ENABLE 1 +#define UHF_PORT_SUSPEND 2 +#define UHF_PORT_OVER_CURRENT 3 +#define UHF_PORT_RESET 4 +#define UHF_PORT_LINK_STATE 5 +#define UHF_PORT_POWER 8 +#define UHF_PORT_LOW_SPEED 9 +#define UHF_C_PORT_CONNECTION 16 +#define UHF_C_PORT_ENABLE 17 +#define UHF_C_PORT_SUSPEND 18 +#define UHF_C_PORT_OVER_CURRENT 19 +#define UHF_C_PORT_RESET 20 +#define UHF_PORT_TEST 21 +#define UHF_PORT_INDICATOR 22 + +/* SuperSpeed HUB specific features */ +#define UHF_PORT_U1_TIMEOUT 23 +#define UHF_PORT_U2_TIMEOUT 24 +#define UHF_C_PORT_LINK_STATE 25 +#define UHF_C_PORT_CONFIG_ERROR 26 +#define UHF_PORT_REMOTE_WAKE_MASK 27 +#define UHF_BH_PORT_RESET 28 +#define UHF_C_BH_PORT_RESET 29 +#define UHF_FORCE_LINKPM_ACCEPT 30 + +struct usb_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; +} __packed; +typedef struct usb_descriptor usb_descriptor_t; + +struct usb_device_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord bcdUSB; +#define UD_USB_2_0 0x0200 +#define UD_USB_3_0 0x0300 +#define UD_IS_USB2(d) ((d)->bcdUSB[1] == 0x02) +#define UD_IS_USB3(d) ((d)->bcdUSB[1] == 0x03) + uByte bDeviceClass; + uByte bDeviceSubClass; + uByte bDeviceProtocol; + uByte bMaxPacketSize; + /* The fields below are not part of the initial descriptor. */ + uWord idVendor; + uWord idProduct; + uWord bcdDevice; + uByte iManufacturer; + uByte iProduct; + uByte iSerialNumber; + uByte bNumConfigurations; +} __packed; +typedef struct usb_device_descriptor usb_device_descriptor_t; + +/* Binary Device Object Store (BOS) */ +struct usb_bos_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord wTotalLength; + uByte bNumDeviceCaps; +} __packed; +typedef struct usb_bos_descriptor usb_bos_descriptor_t; + +/* Binary Device Object Store Capability */ +struct usb_bos_cap_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDevCapabilityType; +#define USB_DEVCAP_RESERVED 0x00 +#define USB_DEVCAP_WUSB 0x01 +#define USB_DEVCAP_USB2EXT 0x02 +#define USB_DEVCAP_SUPER_SPEED 0x03 +#define USB_DEVCAP_CONTAINER_ID 0x04 + /* data ... */ +} __packed; +typedef struct usb_bos_cap_descriptor usb_bos_cap_descriptor_t; + +struct usb_devcap_usb2ext_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDevCapabilityType; + uDWord bmAttributes; +#define USB_V2EXT_LPM 0x02 +} __packed; +typedef struct usb_devcap_usb2ext_descriptor usb_devcap_usb2ext_descriptor_t; + +struct usb_devcap_ss_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDevCapabilityType; + uByte bmAttributes; + uWord wSpeedsSupported; + uByte bFunctionalitySupport; + uByte bU1DevExitLat; + uWord wU2DevExitLat; +} __packed; +typedef struct usb_devcap_ss_descriptor usb_devcap_ss_descriptor_t; + +struct usb_devcap_container_id_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDevCapabilityType; + uByte bReserved; + uByte bContainerID; +} __packed; +typedef struct usb_devcap_container_id_descriptor + usb_devcap_container_id_descriptor_t; + +/* Device class codes */ +#define UDCLASS_IN_INTERFACE 0x00 +#define UDCLASS_COMM 0x02 +#define UDCLASS_HUB 0x09 +#define UDSUBCLASS_HUB 0x00 +#define UDPROTO_FSHUB 0x00 +#define UDPROTO_HSHUBSTT 0x01 +#define UDPROTO_HSHUBMTT 0x02 +#define UDPROTO_SSHUB 0x03 +#define UDCLASS_DIAGNOSTIC 0xdc +#define UDCLASS_WIRELESS 0xe0 +#define UDSUBCLASS_RF 0x01 +#define UDPROTO_BLUETOOTH 0x01 +#define UDCLASS_VENDOR 0xff + +struct usb_config_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord wTotalLength; + uByte bNumInterface; + uByte bConfigurationValue; +#define USB_UNCONFIG_NO 0 + uByte iConfiguration; + uByte bmAttributes; +#define UC_BUS_POWERED 0x80 +#define UC_SELF_POWERED 0x40 +#define UC_REMOTE_WAKEUP 0x20 + uByte bMaxPower; /* max current in 2 mA units */ +#define UC_POWER_FACTOR 2 +} __packed; +typedef struct usb_config_descriptor usb_config_descriptor_t; + +struct usb_interface_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bInterfaceNumber; + uByte bAlternateSetting; + uByte bNumEndpoints; + uByte bInterfaceClass; + uByte bInterfaceSubClass; + uByte bInterfaceProtocol; + uByte iInterface; +} __packed; +typedef struct usb_interface_descriptor usb_interface_descriptor_t; + +struct usb_interface_assoc_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bFirstInterface; + uByte bInterfaceCount; + uByte bFunctionClass; + uByte bFunctionSubClass; + uByte bFunctionProtocol; + uByte iFunction; +} __packed; +typedef struct usb_interface_assoc_descriptor usb_interface_assoc_descriptor_t; + +/* Interface class codes */ +#define UICLASS_UNSPEC 0x00 +#define UICLASS_AUDIO 0x01 /* audio */ +#define UISUBCLASS_AUDIOCONTROL 1 +#define UISUBCLASS_AUDIOSTREAM 2 +#define UISUBCLASS_MIDISTREAM 3 + +#define UICLASS_CDC 0x02 /* communication */ +#define UISUBCLASS_DIRECT_LINE_CONTROL_MODEL 1 +#define UISUBCLASS_ABSTRACT_CONTROL_MODEL 2 +#define UISUBCLASS_TELEPHONE_CONTROL_MODEL 3 +#define UISUBCLASS_MULTICHANNEL_CONTROL_MODEL 4 +#define UISUBCLASS_CAPI_CONTROLMODEL 5 +#define UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL 6 +#define UISUBCLASS_ATM_NETWORKING_CONTROL_MODEL 7 +#define UISUBCLASS_WIRELESS_HANDSET_CM 8 +#define UISUBCLASS_DEVICE_MGMT 9 +#define UISUBCLASS_MOBILE_DIRECT_LINE_MODEL 10 +#define UISUBCLASS_OBEX 11 +#define UISUBCLASS_ETHERNET_EMULATION_MODEL 12 +#define UISUBCLASS_NETWORK_CONTROL_MODEL 13 + +#define UIPROTO_CDC_AT 1 + +#define UICLASS_HID 0x03 +#define UISUBCLASS_BOOT 1 +#define UIPROTO_BOOT_KEYBOARD 1 +#define UIPROTO_MOUSE 2 + +#define UICLASS_PHYSICAL 0x05 +#define UICLASS_IMAGE 0x06 +#define UISUBCLASS_SIC 1 /* still image class */ +#define UICLASS_PRINTER 0x07 +#define UISUBCLASS_PRINTER 1 +#define UIPROTO_PRINTER_UNI 1 +#define UIPROTO_PRINTER_BI 2 +#define UIPROTO_PRINTER_1284 3 + +#define UICLASS_MASS 0x08 +#define UISUBCLASS_RBC 1 +#define UISUBCLASS_SFF8020I 2 +#define UISUBCLASS_QIC157 3 +#define UISUBCLASS_UFI 4 +#define UISUBCLASS_SFF8070I 5 +#define UISUBCLASS_SCSI 6 +#define UIPROTO_MASS_CBI_I 0 +#define UIPROTO_MASS_CBI 1 +#define UIPROTO_MASS_BBB_OLD 2 /* Not in the spec anymore */ +#define UIPROTO_MASS_BBB 80 /* 'P' for the Iomega Zip drive */ + +#define UICLASS_HUB 0x09 +#define UISUBCLASS_HUB 0 +#define UIPROTO_FSHUB 0 +#define UIPROTO_HSHUBSTT 0 /* Yes, same as previous */ +#define UIPROTO_HSHUBMTT 1 + +#define UICLASS_CDC_DATA 0x0a +#define UISUBCLASS_DATA 0x00 +#define UIPROTO_DATA_ISDNBRI 0x30 /* Physical iface */ +#define UIPROTO_DATA_HDLC 0x31 /* HDLC */ +#define UIPROTO_DATA_TRANSPARENT 0x32 /* Transparent */ +#define UIPROTO_DATA_Q921M 0x50 /* Management for Q921 */ +#define UIPROTO_DATA_Q921 0x51 /* Data for Q921 */ +#define UIPROTO_DATA_Q921TM 0x52 /* TEI multiplexer for Q921 */ +#define UIPROTO_DATA_V42BIS 0x90 /* Data compression */ +#define UIPROTO_DATA_Q931 0x91 /* Euro-ISDN */ +#define UIPROTO_DATA_V120 0x92 /* V.24 rate adaption */ +#define UIPROTO_DATA_CAPI 0x93 /* CAPI 2.0 commands */ +#define UIPROTO_DATA_HOST_BASED 0xfd /* Host based driver */ +#define UIPROTO_DATA_PUF 0xfe /* see Prot. Unit Func. Desc. */ +#define UIPROTO_DATA_VENDOR 0xff /* Vendor specific */ +#define UIPROTO_DATA_NCM 0x01 /* Network Control Model */ + +#define UICLASS_SMARTCARD 0x0b +#define UICLASS_FIRM_UPD 0x0c +#define UICLASS_SECURITY 0x0d +#define UICLASS_DIAGNOSTIC 0xdc +#define UICLASS_WIRELESS 0xe0 +#define UISUBCLASS_RF 0x01 +#define UIPROTO_BLUETOOTH 0x01 + +#define UICLASS_IAD 0xEF /* Interface Association Descriptor */ + +#define UICLASS_APPL_SPEC 0xfe +#define UISUBCLASS_FIRMWARE_DOWNLOAD 1 +#define UISUBCLASS_IRDA 2 +#define UIPROTO_IRDA 0 + +#define UICLASS_VENDOR 0xff +#define UISUBCLASS_XBOX360_CONTROLLER 0x5d +#define UIPROTO_XBOX360_GAMEPAD 0x01 + +struct usb_endpoint_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bEndpointAddress; +#define UE_GET_DIR(a) ((a) & 0x80) +#define UE_SET_DIR(a,d) ((a) | (((d)&1) << 7)) +#define UE_DIR_IN 0x80 /* IN-token endpoint, fixed */ +#define UE_DIR_OUT 0x00 /* OUT-token endpoint, fixed */ +#define UE_DIR_RX 0xfd /* for internal use only! */ +#define UE_DIR_TX 0xfe /* for internal use only! */ +#define UE_DIR_ANY 0xff /* for internal use only! */ +#define UE_ADDR 0x0f +#define UE_ADDR_ANY 0xff /* for internal use only! */ +#define UE_GET_ADDR(a) ((a) & UE_ADDR) + uByte bmAttributes; +#define UE_XFERTYPE 0x03 +#define UE_CONTROL 0x00 +#define UE_ISOCHRONOUS 0x01 +#define UE_BULK 0x02 +#define UE_INTERRUPT 0x03 +#define UE_BULK_INTR 0xfe /* for internal use only! */ +#define UE_TYPE_ANY 0xff /* for internal use only! */ +#define UE_GET_XFERTYPE(a) ((a) & UE_XFERTYPE) +#define UE_ISO_TYPE 0x0c +#define UE_ISO_ASYNC 0x04 +#define UE_ISO_ADAPT 0x08 +#define UE_ISO_SYNC 0x0c +#define UE_GET_ISO_TYPE(a) ((a) & UE_ISO_TYPE) + uWord wMaxPacketSize; +#define UE_ZERO_MPS 0xFFFF /* for internal use only */ + uByte bInterval; +} __packed; +typedef struct usb_endpoint_descriptor usb_endpoint_descriptor_t; + +struct usb_endpoint_ss_comp_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bMaxBurst; + uByte bmAttributes; + uWord wBytesPerInterval; +} __packed; +typedef struct usb_endpoint_ss_comp_descriptor + usb_endpoint_ss_comp_descriptor_t; + +struct usb_string_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord bString[126]; + uByte bUnused; +} __packed; +typedef struct usb_string_descriptor usb_string_descriptor_t; + +#define USB_MAKE_STRING_DESC(m,name) \ +struct name { \ + uByte bLength; \ + uByte bDescriptorType; \ + uByte bData[sizeof((uint8_t []){m})]; \ +} __packed; \ +static const struct name name = { \ + .bLength = sizeof(struct name), \ + .bDescriptorType = UDESC_STRING, \ + .bData = { m }, \ +} + +struct usb_hub_descriptor { + uByte bDescLength; + uByte bDescriptorType; + uByte bNbrPorts; + uWord wHubCharacteristics; +#define UHD_PWR 0x0003 +#define UHD_PWR_GANGED 0x0000 +#define UHD_PWR_INDIVIDUAL 0x0001 +#define UHD_PWR_NO_SWITCH 0x0002 +#define UHD_COMPOUND 0x0004 +#define UHD_OC 0x0018 +#define UHD_OC_GLOBAL 0x0000 +#define UHD_OC_INDIVIDUAL 0x0008 +#define UHD_OC_NONE 0x0010 +#define UHD_TT_THINK 0x0060 +#define UHD_TT_THINK_8 0x0000 +#define UHD_TT_THINK_16 0x0020 +#define UHD_TT_THINK_24 0x0040 +#define UHD_TT_THINK_32 0x0060 +#define UHD_PORT_IND 0x0080 + uByte bPwrOn2PwrGood; /* delay in 2 ms units */ +#define UHD_PWRON_FACTOR 2 + uByte bHubContrCurrent; + uByte DeviceRemovable[32]; /* max 255 ports */ +#define UHD_NOT_REMOV(desc, i) \ + (((desc)->DeviceRemovable[(i)/8] >> ((i) % 8)) & 1) + uByte PortPowerCtrlMask[1]; /* deprecated */ +} __packed; +typedef struct usb_hub_descriptor usb_hub_descriptor_t; + +struct usb_hub_ss_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bNbrPorts; + uWord wHubCharacteristics; + uByte bPwrOn2PwrGood; /* delay in 2 ms units */ + uByte bHubContrCurrent; + uByte bHubHdrDecLat; + uWord wHubDelay; + uByte DeviceRemovable[32]; /* max 255 ports */ +} __packed; +typedef struct usb_hub_ss_descriptor usb_hub_ss_descriptor_t; + +/* minimum HUB descriptor (8-ports maximum) */ +struct usb_hub_descriptor_min { + uByte bDescLength; + uByte bDescriptorType; + uByte bNbrPorts; + uWord wHubCharacteristics; + uByte bPwrOn2PwrGood; + uByte bHubContrCurrent; + uByte DeviceRemovable[1]; + uByte PortPowerCtrlMask[1]; +} __packed; +typedef struct usb_hub_descriptor_min usb_hub_descriptor_min_t; + +struct usb_device_qualifier { + uByte bLength; + uByte bDescriptorType; + uWord bcdUSB; + uByte bDeviceClass; + uByte bDeviceSubClass; + uByte bDeviceProtocol; + uByte bMaxPacketSize0; + uByte bNumConfigurations; + uByte bReserved; +} __packed; +typedef struct usb_device_qualifier usb_device_qualifier_t; + +struct usb_otg_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bmAttributes; +#define UOTG_SRP 0x01 +#define UOTG_HNP 0x02 +} __packed; +typedef struct usb_otg_descriptor usb_otg_descriptor_t; + +/* OTG feature selectors */ +#define UOTG_B_HNP_ENABLE 3 +#define UOTG_A_HNP_SUPPORT 4 +#define UOTG_A_ALT_HNP_SUPPORT 5 + +struct usb_status { + uWord wStatus; +/* Device status flags */ +#define UDS_SELF_POWERED 0x0001 +#define UDS_REMOTE_WAKEUP 0x0002 +/* Endpoint status flags */ +#define UES_HALT 0x0001 +} __packed; +typedef struct usb_status usb_status_t; + +struct usb_hub_status { + uWord wHubStatus; +#define UHS_LOCAL_POWER 0x0001 +#define UHS_OVER_CURRENT 0x0002 + uWord wHubChange; +} __packed; +typedef struct usb_hub_status usb_hub_status_t; + +struct usb_port_status { + uWord wPortStatus; +#define UPS_CURRENT_CONNECT_STATUS 0x0001 +#define UPS_PORT_ENABLED 0x0002 +#define UPS_SUSPEND 0x0004 +#define UPS_OVERCURRENT_INDICATOR 0x0008 +#define UPS_RESET 0x0010 +/* The link-state bits are valid for Super-Speed USB HUBs */ +#define UPS_PORT_LINK_STATE_GET(x) (((x) >> 5) & 0xF) +#define UPS_PORT_LINK_STATE_SET(x) (((x) & 0xF) << 5) +#define UPS_PORT_LS_U0 0x00 +#define UPS_PORT_LS_U1 0x01 +#define UPS_PORT_LS_U2 0x02 +#define UPS_PORT_LS_U3 0x03 +#define UPS_PORT_LS_SS_DIS 0x04 +#define UPS_PORT_LS_RX_DET 0x05 +#define UPS_PORT_LS_SS_INA 0x06 +#define UPS_PORT_LS_POLL 0x07 +#define UPS_PORT_LS_RECOVER 0x08 +#define UPS_PORT_LS_HOT_RST 0x09 +#define UPS_PORT_LS_COMP_MODE 0x0A +#define UPS_PORT_LS_LOOPBACK 0x0B +#define UPS_PORT_LS_RESUME 0x0F +#define UPS_PORT_POWER 0x0100 +#define UPS_PORT_POWER_SS 0x0200 /* super-speed only */ +#define UPS_LOW_SPEED 0x0200 +#define UPS_HIGH_SPEED 0x0400 +#define UPS_OTHER_SPEED 0x0600 /* currently FreeBSD specific */ +#define UPS_PORT_TEST 0x0800 +#define UPS_PORT_INDICATOR 0x1000 +#define UPS_PORT_MODE_DEVICE 0x8000 /* currently FreeBSD specific */ + uWord wPortChange; +#define UPS_C_CONNECT_STATUS 0x0001 +#define UPS_C_PORT_ENABLED 0x0002 +#define UPS_C_SUSPEND 0x0004 +#define UPS_C_OVERCURRENT_INDICATOR 0x0008 +#define UPS_C_PORT_RESET 0x0010 +#define UPS_C_BH_PORT_RESET 0x0020 +#define UPS_C_PORT_LINK_STATE 0x0040 +#define UPS_C_PORT_CONFIG_ERROR 0x0080 +} __packed; +typedef struct usb_port_status usb_port_status_t; + +/* + * The "USB_SPEED" macros defines all the supported USB speeds. + */ +enum usb_dev_speed { + USB_SPEED_VARIABLE, + USB_SPEED_LOW, + USB_SPEED_FULL, + USB_SPEED_HIGH, + USB_SPEED_SUPER, +}; +#define USB_SPEED_MAX (USB_SPEED_SUPER+1) + +/* + * The "USB_REV" macros defines all the supported USB revisions. + */ +enum usb_revision { + USB_REV_UNKNOWN, + USB_REV_PRE_1_0, + USB_REV_1_0, + USB_REV_1_1, + USB_REV_2_0, + USB_REV_2_5, + USB_REV_3_0 +}; +#define USB_REV_MAX (USB_REV_3_0+1) + +/* + * Supported host contoller modes. + */ +enum usb_hc_mode { + USB_MODE_HOST, /* initiates transfers */ + USB_MODE_DEVICE, /* bus transfer target */ + USB_MODE_DUAL /* can be host or device */ +}; +#define USB_MODE_MAX (USB_MODE_DUAL+1) + +/* + * The "USB_MODE" macros defines all the supported device states. + */ +enum usb_dev_state { + USB_STATE_DETACHED, + USB_STATE_ATTACHED, + USB_STATE_POWERED, + USB_STATE_ADDRESSED, + USB_STATE_CONFIGURED, +}; +#define USB_STATE_MAX (USB_STATE_CONFIGURED+1) +#endif /* _USB_STANDARD_H_ */ diff --git a/sys/bus/u4b/usb/Makefile b/sys/bus/u4b/usb/Makefile new file mode 100644 index 0000000000..c8b7c61373 --- /dev/null +++ b/sys/bus/u4b/usb/Makefile @@ -0,0 +1,17 @@ +.PATH: ${.CURDIR}/../ ${.CURDIR}/../controller + +KMOD= usb + +# XXX usb_compat_linux.c usb_pf.c + +SRCS= bus_if.h device_if.h usb_if.h usb_if.c \ + opt_usb.h opt_bus.h opt_ddb.h \ + usbdevs.h usbdevs_data.h \ + usb_busdma.c usb_controller.c usb_core.c usb_debug.c \ + usb_dev.c usb_device.c usb_dynamic.c usb_error.c usb_generic.c \ + usb_handle_request.c usb_hid.c usb_hub.c usb_lookup.c usb_mbuf.c \ + usb_msctest.c usb_parse.c usb_process.c usb_request.c \ + usb_transfer.c usb_util.c + +.include + diff --git a/sys/bus/u4b/usb_bus.h b/sys/bus/u4b/usb_bus.h new file mode 100644 index 0000000000..07207cf619 --- /dev/null +++ b/sys/bus/u4b/usb_bus.h @@ -0,0 +1,118 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_BUS_H_ +#define _USB_BUS_H_ + +/* + * The following structure defines the USB explore message sent to the USB + * explore process. + */ + +struct usb_bus_msg { + struct usb_proc_msg hdr; + struct usb_bus *bus; +}; + +/* + * The following structure defines the USB statistics structure. + */ +struct usb_bus_stat { + uint32_t uds_requests[4]; +}; + +/* + * The following structure defines an USB BUS. There is one USB BUS + * for every Host or Device controller. + */ +struct usb_bus { + struct usb_bus_stat stats_err; + struct usb_bus_stat stats_ok; + struct root_hold_token *bus_roothold; + /* + * There are two callback processes. One for Giant locked + * callbacks. One for non-Giant locked callbacks. This should + * avoid congestion and reduce response time in most cases. + */ + struct usb_process giant_callback_proc; + struct usb_process non_giant_callback_proc; + + /* Explore process */ + struct usb_process explore_proc; + + /* Control request process */ + struct usb_process control_xfer_proc; + + struct usb_bus_msg explore_msg[2]; + struct usb_bus_msg detach_msg[2]; + struct usb_bus_msg attach_msg[2]; + struct usb_bus_msg suspend_msg[2]; + struct usb_bus_msg resume_msg[2]; + struct usb_bus_msg shutdown_msg[2]; + /* + * This mutex protects the USB hardware: + */ + struct mtx bus_mtx; + struct usb_xfer_queue intr_q; + struct usb_callout power_wdog; /* power management */ + + device_t parent; + device_t bdev; /* filled by HC driver */ + +#if USB_HAVE_BUSDMA + struct usb_dma_parent_tag dma_parent_tag[1]; + struct usb_dma_tag dma_tags[USB_BUS_DMA_TAG_MAX]; +#endif + struct usb_bus_methods *methods; /* filled by HC driver */ + struct usb_device **devices; + + struct ifnet *ifp; /* only for USB Packet Filter */ + + usb_power_mask_t hw_power_state; /* see USB_HW_POWER_XXX */ + usb_size_t uframe_usage[USB_HS_MICRO_FRAMES_MAX]; + + uint16_t isoc_time_last; /* in milliseconds */ + + uint8_t alloc_failed; /* Set if memory allocation failed. */ + uint8_t driver_added_refcount; /* Current driver generation count */ + enum usb_revision usbrev; /* USB revision. See "USB_REV_XXX". */ + + uint8_t devices_max; /* maximum number of USB devices */ + uint8_t do_probe; /* set if USB should be re-probed */ + uint8_t no_explore; /* don't explore USB ports */ + + /* + * The scratch area can only be used inside the explore thread + * belonging to the give serial bus. + */ + union { + struct usb_hw_ep_scratch hw_ep_scratch[1]; + struct usb_temp_setup temp_setup[1]; + uint8_t data[255]; + } scratch[1]; +}; + +#endif /* _USB_BUS_H_ */ diff --git a/sys/bus/u4b/usb_busdma.c b/sys/bus/u4b/usb_busdma.c new file mode 100644 index 0000000000..d31aeecd97 --- /dev/null +++ b/sys/bus/u4b/usb_busdma.c @@ -0,0 +1,1068 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define USB_DEBUG_VAR usb_debug + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if USB_HAVE_BUSDMA +static void usb_dma_tag_create(struct usb_dma_tag *, usb_size_t, usb_size_t); +static void usb_dma_tag_destroy(struct usb_dma_tag *); +static void usb_dma_lock_cb(void *, bus_dma_lock_op_t); +static void usb_pc_alloc_mem_cb(void *, bus_dma_segment_t *, int, int); +static void usb_pc_load_mem_cb(void *, bus_dma_segment_t *, int, int); +static void usb_pc_common_mem_cb(void *, bus_dma_segment_t *, int, int, + uint8_t); +#endif + +/*------------------------------------------------------------------------* + * usbd_get_page - lookup DMA-able memory for the given offset + * + * NOTE: Only call this function when the "page_cache" structure has + * been properly initialized ! + *------------------------------------------------------------------------*/ +void +usbd_get_page(struct usb_page_cache *pc, usb_frlength_t offset, + struct usb_page_search *res) +{ +#if USB_HAVE_BUSDMA + struct usb_page *page; + + if (pc->page_start) { + + /* Case 1 - something has been loaded into DMA */ + + if (pc->buffer) { + + /* Case 1a - Kernel Virtual Address */ + + res->buffer = USB_ADD_BYTES(pc->buffer, offset); + } + offset += pc->page_offset_buf; + + /* compute destination page */ + + page = pc->page_start; + + if (pc->ismultiseg) { + + page += (offset / USB_PAGE_SIZE); + + offset %= USB_PAGE_SIZE; + + res->length = USB_PAGE_SIZE - offset; + res->physaddr = page->physaddr + offset; + } else { + res->length = 0 - 1; + res->physaddr = page->physaddr + offset; + } + if (!pc->buffer) { + + /* Case 1b - Non Kernel Virtual Address */ + + res->buffer = USB_ADD_BYTES(page->buffer, offset); + } + return; + } +#endif + /* Case 2 - Plain PIO */ + + res->buffer = USB_ADD_BYTES(pc->buffer, offset); + res->length = 0 - 1; +#if USB_HAVE_BUSDMA + res->physaddr = 0; +#endif +} + +/*------------------------------------------------------------------------* + * usbd_copy_in - copy directly to DMA-able memory + *------------------------------------------------------------------------*/ +void +usbd_copy_in(struct usb_page_cache *cache, usb_frlength_t offset, + const void *ptr, usb_frlength_t len) +{ + struct usb_page_search buf_res; + + while (len != 0) { + + usbd_get_page(cache, offset, &buf_res); + + if (buf_res.length > len) { + buf_res.length = len; + } + memcpy(buf_res.buffer, ptr, buf_res.length); + + offset += buf_res.length; + len -= buf_res.length; + ptr = USB_ADD_BYTES(ptr, buf_res.length); + } +} + +/*------------------------------------------------------------------------* + * usbd_copy_in_user - copy directly to DMA-able memory from userland + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +#if USB_HAVE_USER_IO +int +usbd_copy_in_user(struct usb_page_cache *cache, usb_frlength_t offset, + const void *ptr, usb_frlength_t len) +{ + struct usb_page_search buf_res; + int error; + + while (len != 0) { + + usbd_get_page(cache, offset, &buf_res); + + if (buf_res.length > len) { + buf_res.length = len; + } + error = copyin(ptr, buf_res.buffer, buf_res.length); + if (error) + return (error); + + offset += buf_res.length; + len -= buf_res.length; + ptr = USB_ADD_BYTES(ptr, buf_res.length); + } + return (0); /* success */ +} +#endif + +/*------------------------------------------------------------------------* + * usbd_m_copy_in - copy a mbuf chain directly into DMA-able memory + *------------------------------------------------------------------------*/ +#if USB_HAVE_MBUF +struct usb_m_copy_in_arg { + struct usb_page_cache *cache; + usb_frlength_t dst_offset; +}; + +static int +usbd_m_copy_in_cb(void *arg, void *src, uint32_t count) +{ + register struct usb_m_copy_in_arg *ua = arg; + + usbd_copy_in(ua->cache, ua->dst_offset, src, count); + ua->dst_offset += count; + return (0); +} + +void +usbd_m_copy_in(struct usb_page_cache *cache, usb_frlength_t dst_offset, + struct mbuf *m, usb_size_t src_offset, usb_frlength_t src_len) +{ + struct usb_m_copy_in_arg arg = {cache, dst_offset}; + int error; + + error = m_apply(m, src_offset, src_len, &usbd_m_copy_in_cb, &arg); +} +#endif + +/*------------------------------------------------------------------------* + * usb_uiomove - factored out code + *------------------------------------------------------------------------*/ +#if USB_HAVE_USER_IO +int +usb_uiomove(struct usb_page_cache *pc, struct uio *uio, + usb_frlength_t pc_offset, usb_frlength_t len) +{ + struct usb_page_search res; + int error = 0; + + while (len != 0) { + + usbd_get_page(pc, pc_offset, &res); + + if (res.length > len) { + res.length = len; + } + /* + * "uiomove()" can sleep so one needs to make a wrapper, + * exiting the mutex and checking things + */ + error = uiomove(res.buffer, res.length, uio); + + if (error) { + break; + } + pc_offset += res.length; + len -= res.length; + } + return (error); +} +#endif + +/*------------------------------------------------------------------------* + * usbd_copy_out - copy directly from DMA-able memory + *------------------------------------------------------------------------*/ +void +usbd_copy_out(struct usb_page_cache *cache, usb_frlength_t offset, + void *ptr, usb_frlength_t len) +{ + struct usb_page_search res; + + while (len != 0) { + + usbd_get_page(cache, offset, &res); + + if (res.length > len) { + res.length = len; + } + memcpy(ptr, res.buffer, res.length); + + offset += res.length; + len -= res.length; + ptr = USB_ADD_BYTES(ptr, res.length); + } +} + +/*------------------------------------------------------------------------* + * usbd_copy_out_user - copy directly from DMA-able memory to userland + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +#if USB_HAVE_USER_IO +int +usbd_copy_out_user(struct usb_page_cache *cache, usb_frlength_t offset, + void *ptr, usb_frlength_t len) +{ + struct usb_page_search res; + int error; + + while (len != 0) { + + usbd_get_page(cache, offset, &res); + + if (res.length > len) { + res.length = len; + } + error = copyout(res.buffer, ptr, res.length); + if (error) + return (error); + + offset += res.length; + len -= res.length; + ptr = USB_ADD_BYTES(ptr, res.length); + } + return (0); /* success */ +} +#endif + +/*------------------------------------------------------------------------* + * usbd_frame_zero - zero DMA-able memory + *------------------------------------------------------------------------*/ +void +usbd_frame_zero(struct usb_page_cache *cache, usb_frlength_t offset, + usb_frlength_t len) +{ + struct usb_page_search res; + + while (len != 0) { + + usbd_get_page(cache, offset, &res); + + if (res.length > len) { + res.length = len; + } + memset(res.buffer, 0, res.length); + + offset += res.length; + len -= res.length; + } +} + +#if USB_HAVE_BUSDMA + +/*------------------------------------------------------------------------* + * usb_dma_lock_cb - dummy callback + *------------------------------------------------------------------------*/ +static void +usb_dma_lock_cb(void *arg, bus_dma_lock_op_t op) +{ + /* we use "mtx_owned()" instead of this function */ +} + +/*------------------------------------------------------------------------* + * usb_dma_tag_create - allocate a DMA tag + * + * NOTE: If the "align" parameter has a value of 1 the DMA-tag will + * allow multi-segment mappings. Else all mappings are single-segment. + *------------------------------------------------------------------------*/ +static void +usb_dma_tag_create(struct usb_dma_tag *udt, + usb_size_t size, usb_size_t align) +{ + bus_dma_tag_t tag; + + if (bus_dma_tag_create + ( /* parent */ udt->tag_parent->tag, + /* alignment */ align, + /* boundary */ (align == 1) ? + USB_PAGE_SIZE : 0, + /* lowaddr */ (2ULL << (udt->tag_parent->dma_bits - 1)) - 1, + /* highaddr */ BUS_SPACE_MAXADDR, + /* filter */ NULL, + /* filterarg */ NULL, + /* maxsize */ size, + /* nsegments */ (align == 1 && size > 1) ? + (2 + (size / USB_PAGE_SIZE)) : 1, + /* maxsegsz */ (align == 1 && size > USB_PAGE_SIZE) ? + USB_PAGE_SIZE : size, + /* flags */ BUS_DMA_KEEP_PG_OFFSET, + /* lockfn */ &usb_dma_lock_cb, + /* lockarg */ NULL, + &tag)) { + tag = NULL; + } + udt->tag = tag; +} + +/*------------------------------------------------------------------------* + * usb_dma_tag_free - free a DMA tag + *------------------------------------------------------------------------*/ +static void +usb_dma_tag_destroy(struct usb_dma_tag *udt) +{ + bus_dma_tag_destroy(udt->tag); +} + +/*------------------------------------------------------------------------* + * usb_pc_alloc_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb_pc_alloc_mem_cb(void *arg, bus_dma_segment_t *segs, + int nseg, int error) +{ + usb_pc_common_mem_cb(arg, segs, nseg, error, 0); +} + +/*------------------------------------------------------------------------* + * usb_pc_load_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb_pc_load_mem_cb(void *arg, bus_dma_segment_t *segs, + int nseg, int error) +{ + usb_pc_common_mem_cb(arg, segs, nseg, error, 1); +} + +/*------------------------------------------------------------------------* + * usb_pc_common_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb_pc_common_mem_cb(void *arg, bus_dma_segment_t *segs, + int nseg, int error, uint8_t isload) +{ + struct usb_dma_parent_tag *uptag; + struct usb_page_cache *pc; + struct usb_page *pg; + usb_size_t rem; + uint8_t owned; + + pc = arg; + uptag = pc->tag_parent; + + /* + * XXX There is sometimes recursive locking here. + * XXX We should try to find a better solution. + * XXX Until further the "owned" variable does + * XXX the trick. + */ + + if (error) { + goto done; + } + pg = pc->page_start; + pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1); + rem = segs->ds_addr & (USB_PAGE_SIZE - 1); + pc->page_offset_buf = rem; + pc->page_offset_end += rem; + nseg--; +#ifdef USB_DEBUG + if (rem != (USB_P2U(pc->buffer) & (USB_PAGE_SIZE - 1))) { + /* + * This check verifies that the physical address is correct: + */ + DPRINTFN(0, "Page offset was not preserved\n"); + error = 1; + goto done; + } +#endif + while (nseg > 0) { + nseg--; + segs++; + pg++; + pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1); + } + +done: + owned = mtx_owned(uptag->mtx); + if (!owned) + mtx_lock(uptag->mtx); + + uptag->dma_error = (error ? 1 : 0); + if (isload) { + (uptag->func) (uptag); + } else { + cv_broadcast(uptag->cv); + } + if (!owned) + mtx_unlock(uptag->mtx); +} + +/*------------------------------------------------------------------------* + * usb_pc_alloc_mem - allocate DMA'able memory + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb_pc_alloc_mem(struct usb_page_cache *pc, struct usb_page *pg, + usb_size_t size, usb_size_t align) +{ + struct usb_dma_parent_tag *uptag; + struct usb_dma_tag *utag; + bus_dmamap_t map; + void *ptr; + int err; + + uptag = pc->tag_parent; + + if (align != 1) { + /* + * The alignment must be greater or equal to the + * "size" else the object can be split between two + * memory pages and we get a problem! + */ + while (align < size) { + align *= 2; + if (align == 0) { + goto error; + } + } +#if 1 + /* + * XXX BUS-DMA workaround - FIXME later: + * + * We assume that that the aligment at this point of + * the code is greater than or equal to the size and + * less than two times the size, so that if we double + * the size, the size will be greater than the + * alignment. + * + * The bus-dma system has a check for "alignment" + * being less than "size". If that check fails we end + * up using contigmalloc which is page based even for + * small allocations. Try to avoid that to save + * memory, hence we sometimes to a large number of + * small allocations! + */ + if (size <= (USB_PAGE_SIZE / 2)) { + size *= 2; + } +#endif + } + /* get the correct DMA tag */ + utag = usb_dma_tag_find(uptag, size, align); + if (utag == NULL) { + goto error; + } + /* allocate memory */ + if (bus_dmamem_alloc( + utag->tag, &ptr, (BUS_DMA_WAITOK | BUS_DMA_COHERENT), &map)) { + goto error; + } + /* setup page cache */ + pc->buffer = ptr; + pc->page_start = pg; + pc->page_offset_buf = 0; + pc->page_offset_end = size; + pc->map = map; + pc->tag = utag->tag; + pc->ismultiseg = (align == 1); + + mtx_lock(uptag->mtx); + + /* load memory into DMA */ + err = bus_dmamap_load( + utag->tag, map, ptr, size, &usb_pc_alloc_mem_cb, + pc, (BUS_DMA_WAITOK | BUS_DMA_COHERENT)); + + if (err == EINPROGRESS) { + cv_wait(uptag->cv, uptag->mtx); + err = 0; + } + mtx_unlock(uptag->mtx); + + if (err || uptag->dma_error) { + bus_dmamem_free(utag->tag, ptr, map); + goto error; + } + memset(ptr, 0, size); + + usb_pc_cpu_flush(pc); + + return (0); + +error: + /* reset most of the page cache */ + pc->buffer = NULL; + pc->page_start = NULL; + pc->page_offset_buf = 0; + pc->page_offset_end = 0; + pc->map = NULL; + pc->tag = NULL; + return (1); +} + +/*------------------------------------------------------------------------* + * usb_pc_free_mem - free DMA memory + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb_pc_free_mem(struct usb_page_cache *pc) +{ + if (pc && pc->buffer) { + + bus_dmamap_unload(pc->tag, pc->map); + + bus_dmamem_free(pc->tag, pc->buffer, pc->map); + + pc->buffer = NULL; + } +} + +/*------------------------------------------------------------------------* + * usb_pc_load_mem - load virtual memory into DMA + * + * Return values: + * 0: Success + * Else: Error + *------------------------------------------------------------------------*/ +uint8_t +usb_pc_load_mem(struct usb_page_cache *pc, usb_size_t size, uint8_t sync) +{ + /* setup page cache */ + pc->page_offset_buf = 0; + pc->page_offset_end = size; + pc->ismultiseg = 1; + + mtx_assert(pc->tag_parent->mtx, MA_OWNED); + + if (size > 0) { + if (sync) { + struct usb_dma_parent_tag *uptag; + int err; + + uptag = pc->tag_parent; + + /* + * We have to unload the previous loaded DMA + * pages before trying to load a new one! + */ + bus_dmamap_unload(pc->tag, pc->map); + + /* + * Try to load memory into DMA. + */ + err = bus_dmamap_load( + pc->tag, pc->map, pc->buffer, size, + &usb_pc_alloc_mem_cb, pc, BUS_DMA_WAITOK); + if (err == EINPROGRESS) { + cv_wait(uptag->cv, uptag->mtx); + err = 0; + } + if (err || uptag->dma_error) { + return (1); + } + } else { + + /* + * We have to unload the previous loaded DMA + * pages before trying to load a new one! + */ + bus_dmamap_unload(pc->tag, pc->map); + + /* + * Try to load memory into DMA. The callback + * will be called in all cases: + */ + if (bus_dmamap_load( + pc->tag, pc->map, pc->buffer, size, + &usb_pc_load_mem_cb, pc, BUS_DMA_WAITOK)) { + } + } + } else { + if (!sync) { + /* + * Call callback so that refcount is decremented + * properly: + */ + pc->tag_parent->dma_error = 0; + (pc->tag_parent->func) (pc->tag_parent); + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_pc_cpu_invalidate - invalidate CPU cache + *------------------------------------------------------------------------*/ +void +usb_pc_cpu_invalidate(struct usb_page_cache *pc) +{ + if (pc->page_offset_end == pc->page_offset_buf) { + /* nothing has been loaded into this page cache! */ + return; + } + + /* + * TODO: We currently do XXX_POSTREAD and XXX_PREREAD at the + * same time, but in the future we should try to isolate the + * different cases to optimise the code. --HPS + */ + bus_dmamap_sync(pc->tag, pc->map, BUS_DMASYNC_POSTREAD); + bus_dmamap_sync(pc->tag, pc->map, BUS_DMASYNC_PREREAD); +} + +/*------------------------------------------------------------------------* + * usb_pc_cpu_flush - flush CPU cache + *------------------------------------------------------------------------*/ +void +usb_pc_cpu_flush(struct usb_page_cache *pc) +{ + if (pc->page_offset_end == pc->page_offset_buf) { + /* nothing has been loaded into this page cache! */ + return; + } + bus_dmamap_sync(pc->tag, pc->map, BUS_DMASYNC_PREWRITE); +} + +/*------------------------------------------------------------------------* + * usb_pc_dmamap_create - create a DMA map + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb_pc_dmamap_create(struct usb_page_cache *pc, usb_size_t size) +{ + struct usb_xfer_root *info; + struct usb_dma_tag *utag; + + /* get info */ + info = USB_DMATAG_TO_XROOT(pc->tag_parent); + + /* sanity check */ + if (info == NULL) { + goto error; + } + utag = usb_dma_tag_find(pc->tag_parent, size, 1); + if (utag == NULL) { + goto error; + } + /* create DMA map */ + if (bus_dmamap_create(utag->tag, 0, &pc->map)) { + goto error; + } + pc->tag = utag->tag; + return 0; /* success */ + +error: + pc->map = NULL; + pc->tag = NULL; + return 1; /* failure */ +} + +/*------------------------------------------------------------------------* + * usb_pc_dmamap_destroy + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb_pc_dmamap_destroy(struct usb_page_cache *pc) +{ + if (pc && pc->tag) { + bus_dmamap_destroy(pc->tag, pc->map); + pc->tag = NULL; + pc->map = NULL; + } +} + +/*------------------------------------------------------------------------* + * usb_dma_tag_find - factored out code + *------------------------------------------------------------------------*/ +struct usb_dma_tag * +usb_dma_tag_find(struct usb_dma_parent_tag *udpt, + usb_size_t size, usb_size_t align) +{ + struct usb_dma_tag *udt; + uint8_t nudt; + + USB_ASSERT(align > 0, ("Invalid parameter align = 0\n")); + USB_ASSERT(size > 0, ("Invalid parameter size = 0\n")); + + udt = udpt->utag_first; + nudt = udpt->utag_max; + + while (nudt--) { + + if (udt->align == 0) { + usb_dma_tag_create(udt, size, align); + if (udt->tag == NULL) { + return (NULL); + } + udt->align = align; + udt->size = size; + return (udt); + } + if ((udt->align == align) && (udt->size == size)) { + return (udt); + } + udt++; + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_dma_tag_setup - initialise USB DMA tags + *------------------------------------------------------------------------*/ +void +usb_dma_tag_setup(struct usb_dma_parent_tag *udpt, + struct usb_dma_tag *udt, bus_dma_tag_t dmat, + struct mtx *mtx, usb_dma_callback_t *func, + uint8_t ndmabits, uint8_t nudt) +{ + memset(udpt, 0, sizeof(*udpt)); + + /* sanity checking */ + if ((nudt == 0) || + (ndmabits == 0) || + (mtx == NULL)) { + /* something is corrupt */ + return; + } + /* initialise condition variable */ + cv_init(udpt->cv, "USB DMA CV"); + + /* store some information */ + udpt->mtx = mtx; + udpt->func = func; + udpt->tag = dmat; + udpt->utag_first = udt; + udpt->utag_max = nudt; + udpt->dma_bits = ndmabits; + + while (nudt--) { + memset(udt, 0, sizeof(*udt)); + udt->tag_parent = udpt; + udt++; + } +} + +/*------------------------------------------------------------------------* + * usb_bus_tag_unsetup - factored out code + *------------------------------------------------------------------------*/ +void +usb_dma_tag_unsetup(struct usb_dma_parent_tag *udpt) +{ + struct usb_dma_tag *udt; + uint8_t nudt; + + udt = udpt->utag_first; + nudt = udpt->utag_max; + + while (nudt--) { + + if (udt->align) { + /* destroy the USB DMA tag */ + usb_dma_tag_destroy(udt); + udt->align = 0; + } + udt++; + } + + if (udpt->utag_max) { + /* destroy the condition variable */ + cv_destroy(udpt->cv); + } +} + +/*------------------------------------------------------------------------* + * usb_bdma_work_loop + * + * This function handles loading of virtual buffers into DMA and is + * only called when "dma_refcount" is zero. + *------------------------------------------------------------------------*/ +void +usb_bdma_work_loop(struct usb_xfer_queue *pq) +{ + struct usb_xfer_root *info; + struct usb_xfer *xfer; + usb_frcount_t nframes; + + xfer = pq->curr; + info = xfer->xroot; + + mtx_assert(info->xfer_mtx, MA_OWNED); + + if (xfer->error) { + /* some error happened */ + USB_BUS_LOCK(info->bus); + usbd_transfer_done(xfer, 0); + USB_BUS_UNLOCK(info->bus); + return; + } + if (!xfer->flags_int.bdma_setup) { + struct usb_page *pg; + usb_frlength_t frlength_0; + uint8_t isread; + + xfer->flags_int.bdma_setup = 1; + + /* reset BUS-DMA load state */ + + info->dma_error = 0; + + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + nframes = 1; + frlength_0 = xfer->sumlen; + } else { + /* can be multiple frame buffers */ + nframes = xfer->nframes; + frlength_0 = xfer->frlengths[0]; + } + + /* + * Set DMA direction first. This is needed to + * select the correct cache invalidate and cache + * flush operations. + */ + isread = USB_GET_DATA_ISREAD(xfer); + pg = xfer->dma_page_ptr; + + if (xfer->flags_int.control_xfr && + xfer->flags_int.control_hdr) { + /* special case */ + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + /* The device controller writes to memory */ + xfer->frbuffers[0].isread = 1; + } else { + /* The host controller reads from memory */ + xfer->frbuffers[0].isread = 0; + } + } else { + /* default case */ + xfer->frbuffers[0].isread = isread; + } + + /* + * Setup the "page_start" pointer which points to an array of + * USB pages where information about the physical address of a + * page will be stored. Also initialise the "isread" field of + * the USB page caches. + */ + xfer->frbuffers[0].page_start = pg; + + info->dma_nframes = nframes; + info->dma_currframe = 0; + info->dma_frlength_0 = frlength_0; + + pg += (frlength_0 / USB_PAGE_SIZE); + pg += 2; + + while (--nframes > 0) { + xfer->frbuffers[nframes].isread = isread; + xfer->frbuffers[nframes].page_start = pg; + + pg += (xfer->frlengths[nframes] / USB_PAGE_SIZE); + pg += 2; + } + + } + if (info->dma_error) { + USB_BUS_LOCK(info->bus); + usbd_transfer_done(xfer, USB_ERR_DMA_LOAD_FAILED); + USB_BUS_UNLOCK(info->bus); + return; + } + if (info->dma_currframe != info->dma_nframes) { + + if (info->dma_currframe == 0) { + /* special case */ + usb_pc_load_mem(xfer->frbuffers, + info->dma_frlength_0, 0); + } else { + /* default case */ + nframes = info->dma_currframe; + usb_pc_load_mem(xfer->frbuffers + nframes, + xfer->frlengths[nframes], 0); + } + + /* advance frame index */ + info->dma_currframe++; + + return; + } + /* go ahead */ + usb_bdma_pre_sync(xfer); + + /* start loading next USB transfer, if any */ + usb_command_wrapper(pq, NULL); + + /* finally start the hardware */ + usbd_pipe_enter(xfer); +} + +/*------------------------------------------------------------------------* + * usb_bdma_done_event + * + * This function is called when the BUS-DMA has loaded virtual memory + * into DMA, if any. + *------------------------------------------------------------------------*/ +void +usb_bdma_done_event(struct usb_dma_parent_tag *udpt) +{ + struct usb_xfer_root *info; + + info = USB_DMATAG_TO_XROOT(udpt); + + mtx_assert(info->xfer_mtx, MA_OWNED); + + /* copy error */ + info->dma_error = udpt->dma_error; + + /* enter workloop again */ + usb_command_wrapper(&info->dma_q, + info->dma_q.curr); +} + +/*------------------------------------------------------------------------* + * usb_bdma_pre_sync + * + * This function handles DMA synchronisation that must be done before + * an USB transfer is started. + *------------------------------------------------------------------------*/ +void +usb_bdma_pre_sync(struct usb_xfer *xfer) +{ + struct usb_page_cache *pc; + usb_frcount_t nframes; + + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + nframes = 1; + } else { + /* can be multiple frame buffers */ + nframes = xfer->nframes; + } + + pc = xfer->frbuffers; + + while (nframes--) { + + if (pc->isread) { + usb_pc_cpu_invalidate(pc); + } else { + usb_pc_cpu_flush(pc); + } + pc++; + } +} + +/*------------------------------------------------------------------------* + * usb_bdma_post_sync + * + * This function handles DMA synchronisation that must be done after + * an USB transfer is complete. + *------------------------------------------------------------------------*/ +void +usb_bdma_post_sync(struct usb_xfer *xfer) +{ + struct usb_page_cache *pc; + usb_frcount_t nframes; + + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + nframes = 1; + } else { + /* can be multiple frame buffers */ + nframes = xfer->nframes; + } + + pc = xfer->frbuffers; + + while (nframes--) { + if (pc->isread) { + usb_pc_cpu_invalidate(pc); + } + pc++; + } +} + +#endif diff --git a/sys/bus/u4b/usb_busdma.h b/sys/bus/u4b/usb_busdma.h new file mode 100644 index 0000000000..6b6e4039ef --- /dev/null +++ b/sys/bus/u4b/usb_busdma.h @@ -0,0 +1,161 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_BUSDMA_H_ +#define _USB_BUSDMA_H_ + +#include +#include + +#include + +/* defines */ + +#define USB_PAGE_SIZE PAGE_SIZE /* use system PAGE_SIZE */ + +#if (__FreeBSD_version >= 700020) +#define USB_GET_DMA_TAG(dev) bus_get_dma_tag(dev) +#else +#define USB_GET_DMA_TAG(dev) NULL /* XXX */ +#endif + +/* structure prototypes */ + +struct usb_xfer_root; +struct usb_dma_parent_tag; +struct usb_dma_tag; + +/* + * The following typedef defines the USB DMA load done callback. + */ + +typedef void (usb_dma_callback_t)(struct usb_dma_parent_tag *udpt); + +/* + * The following structure defines physical and non kernel virtual + * address of a memory page having size USB_PAGE_SIZE. + */ +struct usb_page { +#if USB_HAVE_BUSDMA + bus_size_t physaddr; + void *buffer; /* non Kernel Virtual Address */ +#endif +}; + +/* + * The following structure is used when needing the kernel virtual + * pointer and the physical address belonging to an offset in an USB + * page cache. + */ +struct usb_page_search { + void *buffer; +#if USB_HAVE_BUSDMA + bus_size_t physaddr; +#endif + usb_size_t length; +}; + +/* + * The following structure is used to keep information about a DMA + * memory allocation. + */ +struct usb_page_cache { + +#if USB_HAVE_BUSDMA + bus_dma_tag_t tag; + bus_dmamap_t map; + struct usb_page *page_start; +#endif + struct usb_dma_parent_tag *tag_parent; /* always set */ + void *buffer; /* virtual buffer pointer */ +#if USB_HAVE_BUSDMA + usb_size_t page_offset_buf; + usb_size_t page_offset_end; + uint8_t isread:1; /* set if we are currently reading + * from the memory. Else write. */ + uint8_t ismultiseg:1; /* set if we can have multiple + * segments */ +#endif +}; + +/* + * The following structure describes the parent USB DMA tag. + */ +#if USB_HAVE_BUSDMA +struct usb_dma_parent_tag { + struct cv cv[1]; /* internal condition variable */ + bus_dma_tag_t tag; /* always set */ + + struct mtx *mtx; /* private mutex, always set */ + usb_dma_callback_t *func; /* load complete callback function */ + struct usb_dma_tag *utag_first;/* pointer to first USB DMA tag */ + uint8_t dma_error; /* set if DMA load operation failed */ + uint8_t dma_bits; /* number of DMA address lines */ + uint8_t utag_max; /* number of USB DMA tags */ +}; +#else +struct usb_dma_parent_tag {}; /* empty struct */ +#endif + +/* + * The following structure describes an USB DMA tag. + */ +#if USB_HAVE_BUSDMA +struct usb_dma_tag { + struct usb_dma_parent_tag *tag_parent; + bus_dma_tag_t tag; + usb_size_t align; + usb_size_t size; +}; +#else +struct usb_dma_tag {}; /* empty struct */ +#endif + +/* function prototypes */ + +int usb_uiomove(struct usb_page_cache *pc, struct uio *uio, + usb_frlength_t pc_offset, usb_frlength_t len); +struct usb_dma_tag *usb_dma_tag_find(struct usb_dma_parent_tag *udpt, + usb_size_t size, usb_size_t align); +uint8_t usb_pc_alloc_mem(struct usb_page_cache *pc, struct usb_page *pg, + usb_size_t size, usb_size_t align); +uint8_t usb_pc_dmamap_create(struct usb_page_cache *pc, usb_size_t size); +uint8_t usb_pc_load_mem(struct usb_page_cache *pc, usb_size_t size, + uint8_t sync); +void usb_bdma_done_event(struct usb_dma_parent_tag *udpt); +void usb_bdma_post_sync(struct usb_xfer *xfer); +void usb_bdma_pre_sync(struct usb_xfer *xfer); +void usb_bdma_work_loop(struct usb_xfer_queue *pq); +void usb_dma_tag_setup(struct usb_dma_parent_tag *udpt, + struct usb_dma_tag *udt, bus_dma_tag_t dmat, struct mtx *mtx, + usb_dma_callback_t *func, uint8_t ndmabits, uint8_t nudt); +void usb_dma_tag_unsetup(struct usb_dma_parent_tag *udpt); +void usb_pc_cpu_flush(struct usb_page_cache *pc); +void usb_pc_cpu_invalidate(struct usb_page_cache *pc); +void usb_pc_dmamap_destroy(struct usb_page_cache *pc); +void usb_pc_free_mem(struct usb_page_cache *pc); + +#endif /* _USB_BUSDMA_H_ */ diff --git a/sys/bus/u4b/usb_cdc.h b/sys/bus/u4b/usb_cdc.h new file mode 100644 index 0000000000..b8f59faeca --- /dev/null +++ b/sys/bus/u4b/usb_cdc.h @@ -0,0 +1,288 @@ +/* $NetBSD: usbcdc.h,v 1.9 2004/10/23 13:24:24 augustss Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _USB_CDC_H_ +#define _USB_CDC_H_ + +#define UDESCSUB_CDC_HEADER 0 +#define UDESCSUB_CDC_CM 1 /* Call Management */ +#define UDESCSUB_CDC_ACM 2 /* Abstract Control Model */ +#define UDESCSUB_CDC_DLM 3 /* Direct Line Management */ +#define UDESCSUB_CDC_TRF 4 /* Telephone Ringer */ +#define UDESCSUB_CDC_TCLSR 5 /* Telephone Call */ +#define UDESCSUB_CDC_UNION 6 +#define UDESCSUB_CDC_CS 7 /* Country Selection */ +#define UDESCSUB_CDC_TOM 8 /* Telephone Operational Modes */ +#define UDESCSUB_CDC_USBT 9 /* USB Terminal */ +#define UDESCSUB_CDC_NCT 10 +#define UDESCSUB_CDC_PUF 11 +#define UDESCSUB_CDC_EUF 12 +#define UDESCSUB_CDC_MCMF 13 +#define UDESCSUB_CDC_CCMF 14 +#define UDESCSUB_CDC_ENF 15 +#define UDESCSUB_CDC_ANF 16 + +struct usb_cdc_header_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uWord bcdCDC; +} __packed; + +struct usb_cdc_cm_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bmCapabilities; +#define USB_CDC_CM_DOES_CM 0x01 +#define USB_CDC_CM_OVER_DATA 0x02 + uByte bDataInterface; +} __packed; + +struct usb_cdc_acm_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bmCapabilities; +#define USB_CDC_ACM_HAS_FEATURE 0x01 +#define USB_CDC_ACM_HAS_LINE 0x02 +#define USB_CDC_ACM_HAS_BREAK 0x04 +#define USB_CDC_ACM_HAS_NETWORK_CONN 0x08 +} __packed; + +struct usb_cdc_union_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bMasterInterface; + uByte bSlaveInterface[1]; +} __packed; + +struct usb_cdc_ethernet_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte iMacAddress; + uDWord bmEthernetStatistics; + uWord wMaxSegmentSize; + uWord wNumberMCFilters; + uByte bNumberPowerFilters; +} __packed; + +#define UCDC_SEND_ENCAPSULATED_COMMAND 0x00 +#define UCDC_GET_ENCAPSULATED_RESPONSE 0x01 +#define UCDC_SET_COMM_FEATURE 0x02 +#define UCDC_GET_COMM_FEATURE 0x03 +#define UCDC_ABSTRACT_STATE 0x01 +#define UCDC_COUNTRY_SETTING 0x02 +#define UCDC_CLEAR_COMM_FEATURE 0x04 +#define UCDC_SET_LINE_CODING 0x20 +#define UCDC_GET_LINE_CODING 0x21 +#define UCDC_SET_CONTROL_LINE_STATE 0x22 +#define UCDC_LINE_DTR 0x0001 +#define UCDC_LINE_RTS 0x0002 +#define UCDC_SEND_BREAK 0x23 +#define UCDC_BREAK_ON 0xffff +#define UCDC_BREAK_OFF 0x0000 + +struct usb_cdc_abstract_state { + uWord wState; +#define UCDC_IDLE_SETTING 0x0001 +#define UCDC_DATA_MULTIPLEXED 0x0002 +} __packed; + +#define UCDC_ABSTRACT_STATE_LENGTH 2 + +struct usb_cdc_line_state { + uDWord dwDTERate; + uByte bCharFormat; +#define UCDC_STOP_BIT_1 0 +#define UCDC_STOP_BIT_1_5 1 +#define UCDC_STOP_BIT_2 2 + uByte bParityType; +#define UCDC_PARITY_NONE 0 +#define UCDC_PARITY_ODD 1 +#define UCDC_PARITY_EVEN 2 +#define UCDC_PARITY_MARK 3 +#define UCDC_PARITY_SPACE 4 + uByte bDataBits; +} __packed; + +#define UCDC_LINE_STATE_LENGTH 7 + +struct usb_cdc_notification { + uByte bmRequestType; +#define UCDC_NOTIFICATION 0xa1 + uByte bNotification; +#define UCDC_N_NETWORK_CONNECTION 0x00 +#define UCDC_N_RESPONSE_AVAILABLE 0x01 +#define UCDC_N_AUX_JACK_HOOK_STATE 0x08 +#define UCDC_N_RING_DETECT 0x09 +#define UCDC_N_SERIAL_STATE 0x20 +#define UCDC_N_CALL_STATE_CHANGED 0x28 +#define UCDC_N_LINE_STATE_CHANGED 0x29 +#define UCDC_N_CONNECTION_SPEED_CHANGE 0x2a + uWord wValue; + uWord wIndex; + uWord wLength; + uByte data[16]; +} __packed; + +#define UCDC_NOTIFICATION_LENGTH 8 + +/* + * Bits set in the SERIAL STATE notifcation (first byte of data) + */ + +#define UCDC_N_SERIAL_OVERRUN 0x40 +#define UCDC_N_SERIAL_PARITY 0x20 +#define UCDC_N_SERIAL_FRAMING 0x10 +#define UCDC_N_SERIAL_RI 0x08 +#define UCDC_N_SERIAL_BREAK 0x04 +#define UCDC_N_SERIAL_DSR 0x02 +#define UCDC_N_SERIAL_DCD 0x01 + +/* Serial state bit masks */ +#define UCDC_MDM_RXCARRIER 0x01 +#define UCDC_MDM_TXCARRIER 0x02 +#define UCDC_MDM_BREAK 0x04 +#define UCDC_MDM_RING 0x08 +#define UCDC_MDM_FRAMING_ERR 0x10 +#define UCDC_MDM_PARITY_ERR 0x20 +#define UCDC_MDM_OVERRUN_ERR 0x40 + +/* + * Network Control Model, NCM16 + NCM32, protocol definitions + */ +struct usb_ncm16_hdr { + uDWord dwSignature; + uWord wHeaderLength; + uWord wSequence; + uWord wBlockLength; + uWord wDptIndex; +} __packed; + +struct usb_ncm16_dp { + uWord wFrameIndex; + uWord wFrameLength; +} __packed; + +struct usb_ncm16_dpt { + uDWord dwSignature; + uWord wLength; + uWord wNextNdpIndex; + struct usb_ncm16_dp dp[0]; +} __packed; + +struct usb_ncm32_hdr { + uDWord dwSignature; + uWord wHeaderLength; + uWord wSequence; + uDWord dwBlockLength; + uDWord dwDptIndex; +} __packed; + +struct usb_ncm32_dp { + uDWord dwFrameIndex; + uDWord dwFrameLength; +} __packed; + +struct usb_ncm32_dpt { + uDWord dwSignature; + uWord wLength; + uWord wReserved6; + uDWord dwNextNdpIndex; + uDWord dwReserved12; + struct usb_ncm32_dp dp[0]; +} __packed; + +/* Communications interface class specific descriptors */ + +#define UCDC_NCM_FUNC_DESC_SUBTYPE 0x1A + +struct usb_ncm_func_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bcdNcmVersion[2]; + uByte bmNetworkCapabilities; +#define UCDC_NCM_CAP_FILTER 0x01 +#define UCDC_NCM_CAP_MAC_ADDR 0x02 +#define UCDC_NCM_CAP_ENCAP 0x04 +#define UCDC_NCM_CAP_MAX_DATA 0x08 +#define UCDC_NCM_CAP_CRCMODE 0x10 +#define UCDC_NCM_CAP_MAX_DGRAM 0x20 +} __packed; + +/* Communications interface specific class request codes */ + +#define UCDC_NCM_SET_ETHERNET_MULTICAST_FILTERS 0x40 +#define UCDC_NCM_SET_ETHERNET_POWER_MGMT_PATTERN_FILTER 0x41 +#define UCDC_NCM_GET_ETHERNET_POWER_MGMT_PATTERN_FILTER 0x42 +#define UCDC_NCM_SET_ETHERNET_PACKET_FILTER 0x43 +#define UCDC_NCM_GET_ETHERNET_STATISTIC 0x44 +#define UCDC_NCM_GET_NTB_PARAMETERS 0x80 +#define UCDC_NCM_GET_NET_ADDRESS 0x81 +#define UCDC_NCM_SET_NET_ADDRESS 0x82 +#define UCDC_NCM_GET_NTB_FORMAT 0x83 +#define UCDC_NCM_SET_NTB_FORMAT 0x84 +#define UCDC_NCM_GET_NTB_INPUT_SIZE 0x85 +#define UCDC_NCM_SET_NTB_INPUT_SIZE 0x86 +#define UCDC_NCM_GET_MAX_DATAGRAM_SIZE 0x87 +#define UCDC_NCM_SET_MAX_DATAGRAM_SIZE 0x88 +#define UCDC_NCM_GET_CRC_MODE 0x89 +#define UCDC_NCM_SET_CRC_MODE 0x8A + +struct usb_ncm_parameters { + uWord wLength; + uWord bmNtbFormatsSupported; +#define UCDC_NCM_FORMAT_NTB16 0x0001 +#define UCDC_NCM_FORMAT_NTB32 0x0002 + uDWord dwNtbInMaxSize; + uWord wNdpInDivisor; + uWord wNdpInPayloadRemainder; + uWord wNdpInAlignment; + uWord wReserved14; + uDWord dwNtbOutMaxSize; + uWord wNdpOutDivisor; + uWord wNdpOutPayloadRemainder; + uWord wNdpOutAlignment; + uWord wNtbOutMaxDatagrams; +} __packed; + +/* Communications interface specific class notification codes */ +#define UCDC_NCM_NOTIF_NETWORK_CONNECTION 0x00 +#define UCDC_NCM_NOTIF_RESPONSE_AVAILABLE 0x01 +#define UCDC_NCM_NOTIF_CONNECTION_SPEED_CHANGE 0x2A + +#endif /* _USB_CDC_H_ */ diff --git a/sys/bus/u4b/usb_compat_linux.c b/sys/bus/u4b/usb_compat_linux.c new file mode 100644 index 0000000000..2f78d9f6ee --- /dev/null +++ b/sys/bus/u4b/usb_compat_linux.c @@ -0,0 +1,1732 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2007 Luigi Rizzo - Universita` di Pisa. All rights reserved. + * Copyright (c) 2007 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct usb_linux_softc { + LIST_ENTRY(usb_linux_softc) sc_attached_list; + + device_t sc_fbsd_dev; + struct usb_device *sc_fbsd_udev; + struct usb_interface *sc_ui; + struct usb_driver *sc_udrv; +}; + +/* prototypes */ +static device_probe_t usb_linux_probe; +static device_attach_t usb_linux_attach; +static device_detach_t usb_linux_detach; +static device_suspend_t usb_linux_suspend; +static device_resume_t usb_linux_resume; + +static usb_callback_t usb_linux_isoc_callback; +static usb_callback_t usb_linux_non_isoc_callback; + +static usb_complete_t usb_linux_wait_complete; + +static uint16_t usb_max_isoc_frames(struct usb_device *); +static int usb_start_wait_urb(struct urb *, usb_timeout_t, uint16_t *); +static const struct usb_device_id *usb_linux_lookup_id( + const struct usb_device_id *, struct usb_attach_arg *); +static struct usb_driver *usb_linux_get_usb_driver(struct usb_linux_softc *); +static int usb_linux_create_usb_device(struct usb_device *, device_t); +static void usb_linux_cleanup_interface(struct usb_device *, + struct usb_interface *); +static void usb_linux_complete(struct usb_xfer *); +static int usb_unlink_urb_sub(struct urb *, uint8_t); + +/*------------------------------------------------------------------------* + * FreeBSD USB interface + *------------------------------------------------------------------------*/ + +static LIST_HEAD(, usb_linux_softc) usb_linux_attached_list; +static LIST_HEAD(, usb_driver) usb_linux_driver_list; + +static device_method_t usb_linux_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, usb_linux_probe), + DEVMETHOD(device_attach, usb_linux_attach), + DEVMETHOD(device_detach, usb_linux_detach), + DEVMETHOD(device_suspend, usb_linux_suspend), + DEVMETHOD(device_resume, usb_linux_resume), + + {0, 0} +}; + +static driver_t usb_linux_driver = { + .name = "usb_linux", + .methods = usb_linux_methods, + .size = sizeof(struct usb_linux_softc), +}; + +static devclass_t usb_linux_devclass; + +DRIVER_MODULE(usb_linux, uhub, usb_linux_driver, usb_linux_devclass, NULL, 0); +MODULE_VERSION(usb_linux, 1); + +/*------------------------------------------------------------------------* + * usb_linux_lookup_id + * + * This functions takes an array of "struct usb_device_id" and tries + * to match the entries with the information in "struct usb_attach_arg". + * If it finds a match the matching entry will be returned. + * Else "NULL" will be returned. + *------------------------------------------------------------------------*/ +static const struct usb_device_id * +usb_linux_lookup_id(const struct usb_device_id *id, struct usb_attach_arg *uaa) +{ + if (id == NULL) { + goto done; + } + /* + * Keep on matching array entries until we find one with + * "match_flags" equal to zero, which indicates the end of the + * array: + */ + for (; id->match_flags; id++) { + + if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->idVendor != uaa->info.idVendor)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + (id->idProduct != uaa->info.idProduct)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) && + (id->bcdDevice_lo > uaa->info.bcdDevice)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) && + (id->bcdDevice_hi < uaa->info.bcdDevice)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) && + (id->bDeviceClass != uaa->info.bDeviceClass)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) && + (id->bDeviceSubClass != uaa->info.bDeviceSubClass)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) && + (id->bDeviceProtocol != uaa->info.bDeviceProtocol)) { + continue; + } + if ((uaa->info.bDeviceClass == 0xFF) && + !(id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->match_flags & (USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS | + USB_DEVICE_ID_MATCH_INT_PROTOCOL))) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) && + (id->bInterfaceClass != uaa->info.bInterfaceClass)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_SUBCLASS) && + (id->bInterfaceSubClass != uaa->info.bInterfaceSubClass)) { + continue; + } + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_PROTOCOL) && + (id->bInterfaceProtocol != uaa->info.bInterfaceProtocol)) { + continue; + } + /* we found a match! */ + return (id); + } + +done: + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_linux_probe + * + * This function is the FreeBSD probe callback. It is called from the + * FreeBSD USB stack through the "device_probe_and_attach()" function. + *------------------------------------------------------------------------*/ +static int +usb_linux_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct usb_driver *udrv; + int err = ENXIO; + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + mtx_lock(&Giant); + LIST_FOREACH(udrv, &usb_linux_driver_list, linux_driver_list) { + if (usb_linux_lookup_id(udrv->id_table, uaa)) { + err = 0; + break; + } + } + mtx_unlock(&Giant); + + return (err); +} + +/*------------------------------------------------------------------------* + * usb_linux_get_usb_driver + * + * This function returns the pointer to the "struct usb_driver" where + * the Linux USB device driver "struct usb_device_id" match was found. + * We apply a lock before reading out the pointer to avoid races. + *------------------------------------------------------------------------*/ +static struct usb_driver * +usb_linux_get_usb_driver(struct usb_linux_softc *sc) +{ + struct usb_driver *udrv; + + mtx_lock(&Giant); + udrv = sc->sc_udrv; + mtx_unlock(&Giant); + return (udrv); +} + +/*------------------------------------------------------------------------* + * usb_linux_attach + * + * This function is the FreeBSD attach callback. It is called from the + * FreeBSD USB stack through the "device_probe_and_attach()" function. + * This function is called when "usb_linux_probe()" returns zero. + *------------------------------------------------------------------------*/ +static int +usb_linux_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv; + const struct usb_device_id *id = NULL; + + mtx_lock(&Giant); + LIST_FOREACH(udrv, &usb_linux_driver_list, linux_driver_list) { + id = usb_linux_lookup_id(udrv->id_table, uaa); + if (id) + break; + } + mtx_unlock(&Giant); + + if (id == NULL) { + return (ENXIO); + } + if (usb_linux_create_usb_device(uaa->device, dev) != 0) + return (ENOMEM); + device_set_usb_desc(dev); + + sc->sc_fbsd_udev = uaa->device; + sc->sc_fbsd_dev = dev; + sc->sc_udrv = udrv; + sc->sc_ui = usb_ifnum_to_if(uaa->device, uaa->info.bIfaceNum); + if (sc->sc_ui == NULL) { + return (EINVAL); + } + if (udrv->probe) { + if ((udrv->probe) (sc->sc_ui, id)) { + return (ENXIO); + } + } + mtx_lock(&Giant); + LIST_INSERT_HEAD(&usb_linux_attached_list, sc, sc_attached_list); + mtx_unlock(&Giant); + + /* success */ + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_detach + * + * This function is the FreeBSD detach callback. It is called from the + * FreeBSD USB stack through the "device_detach()" function. + *------------------------------------------------------------------------*/ +static int +usb_linux_detach(device_t dev) +{ + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv = NULL; + + mtx_lock(&Giant); + if (sc->sc_attached_list.le_prev) { + LIST_REMOVE(sc, sc_attached_list); + sc->sc_attached_list.le_prev = NULL; + udrv = sc->sc_udrv; + sc->sc_udrv = NULL; + } + mtx_unlock(&Giant); + + if (udrv && udrv->disconnect) { + (udrv->disconnect) (sc->sc_ui); + } + /* + * Make sure that we free all FreeBSD USB transfers belonging to + * this Linux "usb_interface", hence they will most likely not be + * needed any more. + */ + usb_linux_cleanup_interface(sc->sc_fbsd_udev, sc->sc_ui); + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_suspend + * + * This function is the FreeBSD suspend callback. Usually it does nothing. + *------------------------------------------------------------------------*/ +static int +usb_linux_suspend(device_t dev) +{ + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv = usb_linux_get_usb_driver(sc); + int err; + + if (udrv && udrv->suspend) { + err = (udrv->suspend) (sc->sc_ui, 0); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_resume + * + * This function is the FreeBSD resume callback. Usually it does nothing. + *------------------------------------------------------------------------*/ +static int +usb_linux_resume(device_t dev) +{ + struct usb_linux_softc *sc = device_get_softc(dev); + struct usb_driver *udrv = usb_linux_get_usb_driver(sc); + int err; + + if (udrv && udrv->resume) { + err = (udrv->resume) (sc->sc_ui); + } + return (0); +} + +/*------------------------------------------------------------------------* + * Linux emulation layer + *------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------* + * usb_max_isoc_frames + * + * The following function returns the maximum number of isochronous + * frames that we support per URB. It is not part of the Linux USB API. + *------------------------------------------------------------------------*/ +static uint16_t +usb_max_isoc_frames(struct usb_device *dev) +{ + ; /* indent fix */ + switch (usbd_get_speed(dev)) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + return (USB_MAX_FULL_SPEED_ISOC_FRAMES); + default: + return (USB_MAX_HIGH_SPEED_ISOC_FRAMES); + } +} + +/*------------------------------------------------------------------------* + * usb_submit_urb + * + * This function is used to queue an URB after that it has been + * initialized. If it returns non-zero, it means that the URB was not + * queued. + *------------------------------------------------------------------------*/ +int +usb_submit_urb(struct urb *urb, uint16_t mem_flags) +{ + struct usb_host_endpoint *uhe; + uint8_t do_unlock; + int err; + + if (urb == NULL) + return (-EINVAL); + + do_unlock = mtx_owned(&Giant) ? 0 : 1; + if (do_unlock) + mtx_lock(&Giant); + + if (urb->endpoint == NULL) { + err = -EINVAL; + goto done; + } + + /* + * Check to see if the urb is in the process of being killed + * and stop a urb that is in the process of being killed from + * being re-submitted (e.g. from its completion callback + * function). + */ + if (urb->kill_count != 0) { + err = -EPERM; + goto done; + } + + uhe = urb->endpoint; + + /* + * Check that we have got a FreeBSD USB transfer that will dequeue + * the URB structure and do the real transfer. If there are no USB + * transfers, then we return an error. + */ + if (uhe->bsd_xfer[0] || + uhe->bsd_xfer[1]) { + /* we are ready! */ + + TAILQ_INSERT_TAIL(&uhe->bsd_urb_list, urb, bsd_urb_list); + + urb->status = -EINPROGRESS; + + usbd_transfer_start(uhe->bsd_xfer[0]); + usbd_transfer_start(uhe->bsd_xfer[1]); + err = 0; + } else { + /* no pipes have been setup yet! */ + urb->status = -EINVAL; + err = -EINVAL; + } +done: + if (do_unlock) + mtx_unlock(&Giant); + return (err); +} + +/*------------------------------------------------------------------------* + * usb_unlink_urb + * + * This function is used to stop an URB after that it is been + * submitted, but before the "complete" callback has been called. On + *------------------------------------------------------------------------*/ +int +usb_unlink_urb(struct urb *urb) +{ + return (usb_unlink_urb_sub(urb, 0)); +} + +static void +usb_unlink_bsd(struct usb_xfer *xfer, + struct urb *urb, uint8_t drain) +{ + if (xfer == NULL) + return; + if (!usbd_transfer_pending(xfer)) + return; + if (xfer->priv_fifo == (void *)urb) { + if (drain) { + mtx_unlock(&Giant); + usbd_transfer_drain(xfer); + mtx_lock(&Giant); + } else { + usbd_transfer_stop(xfer); + } + usbd_transfer_start(xfer); + } +} + +static int +usb_unlink_urb_sub(struct urb *urb, uint8_t drain) +{ + struct usb_host_endpoint *uhe; + uint16_t x; + uint8_t do_unlock; + int err; + + if (urb == NULL) + return (-EINVAL); + + do_unlock = mtx_owned(&Giant) ? 0 : 1; + if (do_unlock) + mtx_lock(&Giant); + if (drain) + urb->kill_count++; + + if (urb->endpoint == NULL) { + err = -EINVAL; + goto done; + } + uhe = urb->endpoint; + + if (urb->bsd_urb_list.tqe_prev) { + + /* not started yet, just remove it from the queue */ + TAILQ_REMOVE(&uhe->bsd_urb_list, urb, bsd_urb_list); + urb->bsd_urb_list.tqe_prev = NULL; + urb->status = -ECONNRESET; + urb->actual_length = 0; + + for (x = 0; x < urb->number_of_packets; x++) { + urb->iso_frame_desc[x].actual_length = 0; + } + + if (urb->complete) { + (urb->complete) (urb); + } + } else { + + /* + * If the URB is not on the URB list, then check if one of + * the FreeBSD USB transfer are processing the current URB. + * If so, re-start that transfer, which will lead to the + * termination of that URB: + */ + usb_unlink_bsd(uhe->bsd_xfer[0], urb, drain); + usb_unlink_bsd(uhe->bsd_xfer[1], urb, drain); + } + err = 0; +done: + if (drain) + urb->kill_count--; + if (do_unlock) + mtx_unlock(&Giant); + return (err); +} + +/*------------------------------------------------------------------------* + * usb_clear_halt + * + * This function must always be used to clear the stall. Stall is when + * an USB endpoint returns a stall message to the USB host controller. + * Until the stall is cleared, no data can be transferred. + *------------------------------------------------------------------------*/ +int +usb_clear_halt(struct usb_device *dev, struct usb_host_endpoint *uhe) +{ + struct usb_config cfg[1]; + struct usb_endpoint *ep; + uint8_t type; + uint8_t addr; + + if (uhe == NULL) + return (-EINVAL); + + type = uhe->desc.bmAttributes & UE_XFERTYPE; + addr = uhe->desc.bEndpointAddress; + + memset(cfg, 0, sizeof(cfg)); + + cfg[0].type = type; + cfg[0].endpoint = addr & UE_ADDR; + cfg[0].direction = addr & (UE_DIR_OUT | UE_DIR_IN); + + ep = usbd_get_endpoint(dev, uhe->bsd_iface_index, cfg); + if (ep == NULL) + return (-EINVAL); + + usbd_clear_data_toggle(dev, ep); + + return (usb_control_msg(dev, &dev->ep0, + UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT, + UF_ENDPOINT_HALT, addr, NULL, 0, 1000)); +} + +/*------------------------------------------------------------------------* + * usb_start_wait_urb + * + * This is an internal function that is used to perform synchronous + * Linux USB transfers. + *------------------------------------------------------------------------*/ +static int +usb_start_wait_urb(struct urb *urb, usb_timeout_t timeout, uint16_t *p_actlen) +{ + int err; + uint8_t do_unlock; + + /* you must have a timeout! */ + if (timeout == 0) { + timeout = 1; + } + urb->complete = &usb_linux_wait_complete; + urb->timeout = timeout; + urb->transfer_flags |= URB_WAIT_WAKEUP; + urb->transfer_flags &= ~URB_IS_SLEEPING; + + do_unlock = mtx_owned(&Giant) ? 0 : 1; + if (do_unlock) + mtx_lock(&Giant); + err = usb_submit_urb(urb, 0); + if (err) + goto done; + + /* + * the URB might have completed before we get here, so check that by + * using some flags! + */ + while (urb->transfer_flags & URB_WAIT_WAKEUP) { + urb->transfer_flags |= URB_IS_SLEEPING; + cv_wait(&urb->cv_wait, &Giant); + urb->transfer_flags &= ~URB_IS_SLEEPING; + } + + err = urb->status; + +done: + if (do_unlock) + mtx_unlock(&Giant); + if (p_actlen != NULL) { + if (err) + *p_actlen = 0; + else + *p_actlen = urb->actual_length; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb_control_msg + * + * The following function performs a control transfer sequence one any + * control, bulk or interrupt endpoint, specified by "uhe". A control + * transfer means that you transfer an 8-byte header first followed by + * a data-phase as indicated by the 8-byte header. The "timeout" is + * given in milliseconds. + * + * Return values: + * 0: Success + * < 0: Failure + * > 0: Acutal length + *------------------------------------------------------------------------*/ +int +usb_control_msg(struct usb_device *dev, struct usb_host_endpoint *uhe, + uint8_t request, uint8_t requesttype, + uint16_t value, uint16_t index, void *data, + uint16_t size, usb_timeout_t timeout) +{ + struct usb_device_request req; + struct urb *urb; + int err; + uint16_t actlen; + uint8_t type; + uint8_t addr; + + req.bmRequestType = requesttype; + req.bRequest = request; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, size); + + if (uhe == NULL) { + return (-EINVAL); + } + type = (uhe->desc.bmAttributes & UE_XFERTYPE); + addr = (uhe->desc.bEndpointAddress & UE_ADDR); + + if (type != UE_CONTROL) { + return (-EINVAL); + } + if (addr == 0) { + /* + * The FreeBSD USB stack supports standard control + * transfers on control endpoint zero: + */ + err = usbd_do_request_flags(dev, + NULL, &req, data, USB_SHORT_XFER_OK, + &actlen, timeout); + if (err) { + err = -EPIPE; + } else { + err = actlen; + } + return (err); + } + if (dev->flags.usb_mode != USB_MODE_HOST) { + /* not supported */ + return (-EINVAL); + } + err = usb_setup_endpoint(dev, uhe, 1 /* dummy */ ); + + /* + * NOTE: we need to allocate real memory here so that we don't + * transfer data to/from the stack! + * + * 0xFFFF is a FreeBSD specific magic value. + */ + urb = usb_alloc_urb(0xFFFF, size); + if (urb == NULL) + return (-ENOMEM); + + urb->dev = dev; + urb->endpoint = uhe; + + memcpy(urb->setup_packet, &req, sizeof(req)); + + if (size && (!(req.bmRequestType & UT_READ))) { + /* move the data to a real buffer */ + memcpy(USB_ADD_BYTES(urb->setup_packet, sizeof(req)), + data, size); + } + err = usb_start_wait_urb(urb, timeout, &actlen); + + if (req.bmRequestType & UT_READ) { + if (actlen) { + bcopy(USB_ADD_BYTES(urb->setup_packet, + sizeof(req)), data, actlen); + } + } + usb_free_urb(urb); + + if (err == 0) { + err = actlen; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb_set_interface + * + * The following function will select which alternate setting of an + * USB interface you plan to use. By default alternate setting with + * index zero is selected. Note that "iface_no" is not the interface + * index, but rather the value of "bInterfaceNumber". + *------------------------------------------------------------------------*/ +int +usb_set_interface(struct usb_device *dev, uint8_t iface_no, uint8_t alt_index) +{ + struct usb_interface *p_ui = usb_ifnum_to_if(dev, iface_no); + int err; + + if (p_ui == NULL) + return (-EINVAL); + if (alt_index >= p_ui->num_altsetting) + return (-EINVAL); + usb_linux_cleanup_interface(dev, p_ui); + err = -usbd_set_alt_interface_index(dev, + p_ui->bsd_iface_index, alt_index); + if (err == 0) { + p_ui->cur_altsetting = p_ui->altsetting + alt_index; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb_setup_endpoint + * + * The following function is an extension to the Linux USB API that + * allows you to set a maximum buffer size for a given USB endpoint. + * The maximum buffer size is per URB. If you don't call this function + * to set a maximum buffer size, the endpoint will not be functional. + * Note that for isochronous endpoints the maximum buffer size must be + * a non-zero dummy, hence this function will base the maximum buffer + * size on "wMaxPacketSize". + *------------------------------------------------------------------------*/ +int +usb_setup_endpoint(struct usb_device *dev, + struct usb_host_endpoint *uhe, usb_size_t bufsize) +{ + struct usb_config cfg[2]; + uint8_t type = uhe->desc.bmAttributes & UE_XFERTYPE; + uint8_t addr = uhe->desc.bEndpointAddress; + + if (uhe->fbsd_buf_size == bufsize) { + /* optimize */ + return (0); + } + usbd_transfer_unsetup(uhe->bsd_xfer, 2); + + uhe->fbsd_buf_size = bufsize; + + if (bufsize == 0) { + return (0); + } + memset(cfg, 0, sizeof(cfg)); + + if (type == UE_ISOCHRONOUS) { + + /* + * Isochronous transfers are special in that they don't fit + * into the BULK/INTR/CONTROL transfer model. + */ + + cfg[0].type = type; + cfg[0].endpoint = addr & UE_ADDR; + cfg[0].direction = addr & (UE_DIR_OUT | UE_DIR_IN); + cfg[0].callback = &usb_linux_isoc_callback; + cfg[0].bufsize = 0; /* use wMaxPacketSize */ + cfg[0].frames = usb_max_isoc_frames(dev); + cfg[0].flags.proxy_buffer = 1; +#if 0 + /* + * The Linux USB API allows non back-to-back + * isochronous frames which we do not support. If the + * isochronous frames are not back-to-back we need to + * do a copy, and then we need a buffer for + * that. Enable this at your own risk. + */ + cfg[0].flags.ext_buffer = 1; +#endif + cfg[0].flags.short_xfer_ok = 1; + + bcopy(cfg, cfg + 1, sizeof(*cfg)); + + /* Allocate and setup two generic FreeBSD USB transfers */ + + if (usbd_transfer_setup(dev, &uhe->bsd_iface_index, + uhe->bsd_xfer, cfg, 2, uhe, &Giant)) { + return (-EINVAL); + } + } else { + if (bufsize > (1 << 22)) { + /* limit buffer size */ + bufsize = (1 << 22); + } + /* Allocate and setup one generic FreeBSD USB transfer */ + + cfg[0].type = type; + cfg[0].endpoint = addr & UE_ADDR; + cfg[0].direction = addr & (UE_DIR_OUT | UE_DIR_IN); + cfg[0].callback = &usb_linux_non_isoc_callback; + cfg[0].bufsize = bufsize; + cfg[0].flags.ext_buffer = 1; /* enable zero-copy */ + cfg[0].flags.proxy_buffer = 1; + cfg[0].flags.short_xfer_ok = 1; + + if (usbd_transfer_setup(dev, &uhe->bsd_iface_index, + uhe->bsd_xfer, cfg, 1, uhe, &Giant)) { + return (-EINVAL); + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_linux_create_usb_device + * + * The following function is used to build up a per USB device + * structure tree, that mimics the Linux one. The root structure + * is returned by this function. + *------------------------------------------------------------------------*/ +static int +usb_linux_create_usb_device(struct usb_device *udev, device_t dev) +{ + struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev); + struct usb_descriptor *desc; + struct usb_interface_descriptor *id; + struct usb_endpoint_descriptor *ed; + struct usb_interface *p_ui = NULL; + struct usb_host_interface *p_uhi = NULL; + struct usb_host_endpoint *p_uhe = NULL; + usb_size_t size; + uint16_t niface_total; + uint16_t nedesc; + uint16_t iface_no_curr; + uint16_t iface_index; + uint8_t pass; + uint8_t iface_no; + + /* + * We do two passes. One pass for computing necessary memory size + * and one pass to initialize all the allocated memory structures. + */ + for (pass = 0; pass < 2; pass++) { + + iface_no_curr = 0 - 1; + niface_total = 0; + iface_index = 0; + nedesc = 0; + desc = NULL; + + /* + * Iterate over all the USB descriptors. Use the USB config + * descriptor pointer provided by the FreeBSD USB stack. + */ + while ((desc = usb_desc_foreach(cd, desc))) { + + /* + * Build up a tree according to the descriptors we + * find: + */ + switch (desc->bDescriptorType) { + case UDESC_DEVICE: + break; + + case UDESC_ENDPOINT: + ed = (void *)desc; + if ((ed->bLength < sizeof(*ed)) || + (iface_index == 0)) + break; + if (p_uhe) { + bcopy(ed, &p_uhe->desc, sizeof(p_uhe->desc)); + p_uhe->bsd_iface_index = iface_index - 1; + TAILQ_INIT(&p_uhe->bsd_urb_list); + p_uhe++; + } + if (p_uhi) { + (p_uhi - 1)->desc.bNumEndpoints++; + } + nedesc++; + break; + + case UDESC_INTERFACE: + id = (void *)desc; + if (id->bLength < sizeof(*id)) + break; + if (p_uhi) { + bcopy(id, &p_uhi->desc, sizeof(p_uhi->desc)); + p_uhi->desc.bNumEndpoints = 0; + p_uhi->endpoint = p_uhe; + p_uhi->string = ""; + p_uhi->bsd_iface_index = iface_index; + p_uhi++; + } + iface_no = id->bInterfaceNumber; + niface_total++; + if (iface_no_curr != iface_no) { + if (p_ui) { + p_ui->altsetting = p_uhi - 1; + p_ui->cur_altsetting = p_uhi - 1; + p_ui->num_altsetting = 1; + p_ui->bsd_iface_index = iface_index; + p_ui->linux_udev = udev; + p_ui++; + } + iface_no_curr = iface_no; + iface_index++; + } else { + if (p_ui) { + (p_ui - 1)->num_altsetting++; + } + } + break; + + default: + break; + } + } + + if (pass == 0) { + + size = (sizeof(*p_uhe) * nedesc) + + (sizeof(*p_ui) * iface_index) + + (sizeof(*p_uhi) * niface_total); + + p_uhe = malloc(size, M_USBDEV, M_WAITOK | M_ZERO); + p_ui = (void *)(p_uhe + nedesc); + p_uhi = (void *)(p_ui + iface_index); + + udev->linux_iface_start = p_ui; + udev->linux_iface_end = p_ui + iface_index; + udev->linux_endpoint_start = p_uhe; + udev->linux_endpoint_end = p_uhe + nedesc; + udev->devnum = device_get_unit(dev); + bcopy(&udev->ddesc, &udev->descriptor, + sizeof(udev->descriptor)); + bcopy(udev->ctrl_ep.edesc, &udev->ep0.desc, + sizeof(udev->ep0.desc)); + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_alloc_urb + * + * This function should always be used when you allocate an URB for + * use with the USB Linux stack. In case of an isochronous transfer + * you must specifiy the maximum number of "iso_packets" which you + * plan to transfer per URB. This function is always blocking, and + * "mem_flags" are not regarded like on Linux. + *------------------------------------------------------------------------*/ +struct urb * +usb_alloc_urb(uint16_t iso_packets, uint16_t mem_flags) +{ + struct urb *urb; + usb_size_t size; + + if (iso_packets == 0xFFFF) { + /* + * FreeBSD specific magic value to ask for control transfer + * memory allocation: + */ + size = sizeof(*urb) + sizeof(struct usb_device_request) + mem_flags; + } else { + size = sizeof(*urb) + (iso_packets * sizeof(urb->iso_frame_desc[0])); + } + + urb = malloc(size, M_USBDEV, M_WAITOK | M_ZERO); + if (urb) { + + cv_init(&urb->cv_wait, "URBWAIT"); + if (iso_packets == 0xFFFF) { + urb->setup_packet = (void *)(urb + 1); + urb->transfer_buffer = (void *)(urb->setup_packet + + sizeof(struct usb_device_request)); + } else { + urb->number_of_packets = iso_packets; + } + } + return (urb); +} + +/*------------------------------------------------------------------------* + * usb_find_host_endpoint + * + * The following function will return the Linux USB host endpoint + * structure that matches the given endpoint type and endpoint + * value. If no match is found, NULL is returned. This function is not + * part of the Linux USB API and is only used internally. + *------------------------------------------------------------------------*/ +struct usb_host_endpoint * +usb_find_host_endpoint(struct usb_device *dev, uint8_t type, uint8_t ep) +{ + struct usb_host_endpoint *uhe; + struct usb_host_endpoint *uhe_end; + struct usb_host_interface *uhi; + struct usb_interface *ui; + uint8_t ea; + uint8_t at; + uint8_t mask; + + if (dev == NULL) { + return (NULL); + } + if (type == UE_CONTROL) { + mask = UE_ADDR; + } else { + mask = (UE_DIR_IN | UE_DIR_OUT | UE_ADDR); + } + + ep &= mask; + + /* + * Iterate over all the interfaces searching the selected alternate + * setting only, and all belonging endpoints. + */ + for (ui = dev->linux_iface_start; + ui != dev->linux_iface_end; + ui++) { + uhi = ui->cur_altsetting; + if (uhi) { + uhe_end = uhi->endpoint + uhi->desc.bNumEndpoints; + for (uhe = uhi->endpoint; + uhe != uhe_end; + uhe++) { + ea = uhe->desc.bEndpointAddress; + at = uhe->desc.bmAttributes; + + if (((ea & mask) == ep) && + ((at & UE_XFERTYPE) == type)) { + return (uhe); + } + } + } + } + + if ((type == UE_CONTROL) && ((ep & UE_ADDR) == 0)) { + return (&dev->ep0); + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_altnum_to_altsetting + * + * The following function returns a pointer to an alternate setting by + * index given a "usb_interface" pointer. If the alternate setting by + * index does not exist, NULL is returned. And alternate setting is a + * variant of an interface, but usually with slightly different + * characteristics. + *------------------------------------------------------------------------*/ +struct usb_host_interface * +usb_altnum_to_altsetting(const struct usb_interface *intf, uint8_t alt_index) +{ + if (alt_index >= intf->num_altsetting) { + return (NULL); + } + return (intf->altsetting + alt_index); +} + +/*------------------------------------------------------------------------* + * usb_ifnum_to_if + * + * The following function searches up an USB interface by + * "bInterfaceNumber". If no match is found, NULL is returned. + *------------------------------------------------------------------------*/ +struct usb_interface * +usb_ifnum_to_if(struct usb_device *dev, uint8_t iface_no) +{ + struct usb_interface *p_ui; + + for (p_ui = dev->linux_iface_start; + p_ui != dev->linux_iface_end; + p_ui++) { + if ((p_ui->num_altsetting > 0) && + (p_ui->altsetting->desc.bInterfaceNumber == iface_no)) { + return (p_ui); + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_buffer_alloc + *------------------------------------------------------------------------*/ +void * +usb_buffer_alloc(struct usb_device *dev, usb_size_t size, uint16_t mem_flags, uint8_t *dma_addr) +{ + return (malloc(size, M_USBDEV, M_WAITOK | M_ZERO)); +} + +/*------------------------------------------------------------------------* + * usbd_get_intfdata + *------------------------------------------------------------------------*/ +void * +usbd_get_intfdata(struct usb_interface *intf) +{ + return (intf->bsd_priv_sc); +} + +/*------------------------------------------------------------------------* + * usb_linux_register + * + * The following function is used by the "USB_DRIVER_EXPORT()" macro, + * and is used to register a Linux USB driver, so that its + * "usb_device_id" structures gets searched a probe time. This + * function is not part of the Linux USB API, and is for internal use + * only. + *------------------------------------------------------------------------*/ +void +usb_linux_register(void *arg) +{ + struct usb_driver *drv = arg; + + mtx_lock(&Giant); + LIST_INSERT_HEAD(&usb_linux_driver_list, drv, linux_driver_list); + mtx_unlock(&Giant); + + usb_needs_explore_all(); +} + +/*------------------------------------------------------------------------* + * usb_linux_deregister + * + * The following function is used by the "USB_DRIVER_EXPORT()" macro, + * and is used to deregister a Linux USB driver. This function will + * ensure that all driver instances belonging to the Linux USB device + * driver in question, gets detached before the driver is + * unloaded. This function is not part of the Linux USB API, and is + * for internal use only. + *------------------------------------------------------------------------*/ +void +usb_linux_deregister(void *arg) +{ + struct usb_driver *drv = arg; + struct usb_linux_softc *sc; + +repeat: + mtx_lock(&Giant); + LIST_FOREACH(sc, &usb_linux_attached_list, sc_attached_list) { + if (sc->sc_udrv == drv) { + mtx_unlock(&Giant); + device_detach(sc->sc_fbsd_dev); + goto repeat; + } + } + LIST_REMOVE(drv, linux_driver_list); + mtx_unlock(&Giant); +} + +/*------------------------------------------------------------------------* + * usb_linux_free_device + * + * The following function is only used by the FreeBSD USB stack, to + * cleanup and free memory after that a Linux USB device was attached. + *------------------------------------------------------------------------*/ +void +usb_linux_free_device(struct usb_device *dev) +{ + struct usb_host_endpoint *uhe; + struct usb_host_endpoint *uhe_end; + int err; + + uhe = dev->linux_endpoint_start; + uhe_end = dev->linux_endpoint_end; + while (uhe != uhe_end) { + err = usb_setup_endpoint(dev, uhe, 0); + uhe++; + } + err = usb_setup_endpoint(dev, &dev->ep0, 0); + free(dev->linux_endpoint_start, M_USBDEV); +} + +/*------------------------------------------------------------------------* + * usb_buffer_free + *------------------------------------------------------------------------*/ +void +usb_buffer_free(struct usb_device *dev, usb_size_t size, + void *addr, uint8_t dma_addr) +{ + free(addr, M_USBDEV); +} + +/*------------------------------------------------------------------------* + * usb_free_urb + *------------------------------------------------------------------------*/ +void +usb_free_urb(struct urb *urb) +{ + if (urb == NULL) { + return; + } + /* make sure that the current URB is not active */ + usb_kill_urb(urb); + + /* destroy condition variable */ + cv_destroy(&urb->cv_wait); + + /* just free it */ + free(urb, M_USBDEV); +} + +/*------------------------------------------------------------------------* + * usb_init_urb + * + * The following function can be used to initialize a custom URB. It + * is not recommended to use this function. Use "usb_alloc_urb()" + * instead. + *------------------------------------------------------------------------*/ +void +usb_init_urb(struct urb *urb) +{ + if (urb == NULL) { + return; + } + memset(urb, 0, sizeof(*urb)); +} + +/*------------------------------------------------------------------------* + * usb_kill_urb + *------------------------------------------------------------------------*/ +void +usb_kill_urb(struct urb *urb) +{ + usb_unlink_urb_sub(urb, 1); +} + +/*------------------------------------------------------------------------* + * usb_set_intfdata + * + * The following function sets the per Linux USB interface private + * data pointer. It is used by most Linux USB device drivers. + *------------------------------------------------------------------------*/ +void +usb_set_intfdata(struct usb_interface *intf, void *data) +{ + intf->bsd_priv_sc = data; +} + +/*------------------------------------------------------------------------* + * usb_linux_cleanup_interface + * + * The following function will release all FreeBSD USB transfers + * associated with a Linux USB interface. It is for internal use only. + *------------------------------------------------------------------------*/ +static void +usb_linux_cleanup_interface(struct usb_device *dev, struct usb_interface *iface) +{ + struct usb_host_interface *uhi; + struct usb_host_interface *uhi_end; + struct usb_host_endpoint *uhe; + struct usb_host_endpoint *uhe_end; + int err; + + uhi = iface->altsetting; + uhi_end = iface->altsetting + iface->num_altsetting; + while (uhi != uhi_end) { + uhe = uhi->endpoint; + uhe_end = uhi->endpoint + uhi->desc.bNumEndpoints; + while (uhe != uhe_end) { + err = usb_setup_endpoint(dev, uhe, 0); + uhe++; + } + uhi++; + } +} + +/*------------------------------------------------------------------------* + * usb_linux_wait_complete + * + * The following function is used by "usb_start_wait_urb()" to wake it + * up, when an USB transfer has finished. + *------------------------------------------------------------------------*/ +static void +usb_linux_wait_complete(struct urb *urb) +{ + if (urb->transfer_flags & URB_IS_SLEEPING) { + cv_signal(&urb->cv_wait); + } + urb->transfer_flags &= ~URB_WAIT_WAKEUP; +} + +/*------------------------------------------------------------------------* + * usb_linux_complete + *------------------------------------------------------------------------*/ +static void +usb_linux_complete(struct usb_xfer *xfer) +{ + struct urb *urb; + + urb = usbd_xfer_get_priv(xfer); + usbd_xfer_set_priv(xfer, NULL); + if (urb->complete) { + (urb->complete) (urb); + } +} + +/*------------------------------------------------------------------------* + * usb_linux_isoc_callback + * + * The following is the FreeBSD isochronous USB callback. Isochronous + * frames are USB packets transferred 1000 or 8000 times per second, + * depending on whether a full- or high- speed USB transfer is + * used. + *------------------------------------------------------------------------*/ +static void +usb_linux_isoc_callback(struct usb_xfer *xfer, usb_error_t error) +{ + usb_frlength_t max_frame = xfer->max_frame_size; + usb_frlength_t offset; + usb_frcount_t x; + struct urb *urb = usbd_xfer_get_priv(xfer); + struct usb_host_endpoint *uhe = usbd_xfer_softc(xfer); + struct usb_iso_packet_descriptor *uipd; + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (urb->bsd_isread) { + + /* copy in data with regard to the URB */ + + offset = 0; + + for (x = 0; x < urb->number_of_packets; x++) { + uipd = urb->iso_frame_desc + x; + if (uipd->length > xfer->frlengths[x]) { + if (urb->transfer_flags & URB_SHORT_NOT_OK) { + /* XXX should be EREMOTEIO */ + uipd->status = -EPIPE; + } else { + uipd->status = 0; + } + } else { + uipd->status = 0; + } + uipd->actual_length = xfer->frlengths[x]; + if (!xfer->flags.ext_buffer) { + usbd_copy_out(xfer->frbuffers, offset, + USB_ADD_BYTES(urb->transfer_buffer, + uipd->offset), uipd->actual_length); + } + offset += max_frame; + } + } else { + for (x = 0; x < urb->number_of_packets; x++) { + uipd = urb->iso_frame_desc + x; + uipd->actual_length = xfer->frlengths[x]; + uipd->status = 0; + } + } + + urb->actual_length = xfer->actlen; + + /* check for short transfer */ + if (xfer->actlen < xfer->sumlen) { + /* short transfer */ + if (urb->transfer_flags & URB_SHORT_NOT_OK) { + /* XXX should be EREMOTEIO */ + urb->status = -EPIPE; + } else { + urb->status = 0; + } + } else { + /* success */ + urb->status = 0; + } + + /* call callback */ + usb_linux_complete(xfer); + + case USB_ST_SETUP: +tr_setup: + + if (xfer->priv_fifo == NULL) { + + /* get next transfer */ + urb = TAILQ_FIRST(&uhe->bsd_urb_list); + if (urb == NULL) { + /* nothing to do */ + return; + } + TAILQ_REMOVE(&uhe->bsd_urb_list, urb, bsd_urb_list); + urb->bsd_urb_list.tqe_prev = NULL; + + x = xfer->max_frame_count; + if (urb->number_of_packets > x) { + /* XXX simply truncate the transfer */ + urb->number_of_packets = x; + } + } else { + DPRINTF("Already got a transfer\n"); + + /* already got a transfer (should not happen) */ + urb = usbd_xfer_get_priv(xfer); + } + + urb->bsd_isread = (uhe->desc.bEndpointAddress & UE_DIR_IN) ? 1 : 0; + + if (xfer->flags.ext_buffer) { + /* set virtual address to load */ + usbd_xfer_set_frame_data(xfer, 0, urb->transfer_buffer, 0); + } + if (!(urb->bsd_isread)) { + + /* copy out data with regard to the URB */ + + offset = 0; + + for (x = 0; x < urb->number_of_packets; x++) { + uipd = urb->iso_frame_desc + x; + usbd_xfer_set_frame_len(xfer, x, uipd->length); + if (!xfer->flags.ext_buffer) { + usbd_copy_in(xfer->frbuffers, offset, + USB_ADD_BYTES(urb->transfer_buffer, + uipd->offset), uipd->length); + } + offset += uipd->length; + } + } else { + + /* + * compute the transfer length into the "offset" + * variable + */ + + offset = urb->number_of_packets * max_frame; + + /* setup "frlengths" array */ + + for (x = 0; x < urb->number_of_packets; x++) { + uipd = urb->iso_frame_desc + x; + usbd_xfer_set_frame_len(xfer, x, max_frame); + } + } + usbd_xfer_set_priv(xfer, urb); + xfer->flags.force_short_xfer = 0; + xfer->timeout = urb->timeout; + xfer->nframes = urb->number_of_packets; + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + urb->status = -ECONNRESET; + } else { + urb->status = -EPIPE; /* stalled */ + } + + /* Set zero for "actual_length" */ + urb->actual_length = 0; + + /* Set zero for "actual_length" */ + for (x = 0; x < urb->number_of_packets; x++) { + urb->iso_frame_desc[x].actual_length = 0; + urb->iso_frame_desc[x].status = urb->status; + } + + /* call callback */ + usb_linux_complete(xfer); + + if (xfer->error == USB_ERR_CANCELLED) { + /* we need to return in this case */ + return; + } + goto tr_setup; + + } +} + +/*------------------------------------------------------------------------* + * usb_linux_non_isoc_callback + * + * The following is the FreeBSD BULK/INTERRUPT and CONTROL USB + * callback. It dequeues Linux USB stack compatible URB's, transforms + * the URB fields into a FreeBSD USB transfer, and defragments the USB + * transfer as required. When the transfer is complete the "complete" + * callback is called. + *------------------------------------------------------------------------*/ +static void +usb_linux_non_isoc_callback(struct usb_xfer *xfer, usb_error_t error) +{ + enum { + REQ_SIZE = sizeof(struct usb_device_request) + }; + struct urb *urb = usbd_xfer_get_priv(xfer); + struct usb_host_endpoint *uhe = usbd_xfer_softc(xfer); + uint8_t *ptr; + usb_frlength_t max_bulk = usbd_xfer_max_len(xfer); + uint8_t data_frame = xfer->flags_int.control_xfr ? 1 : 0; + + DPRINTF("\n"); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->flags_int.control_xfr) { + + /* don't transfer the setup packet again: */ + + usbd_xfer_set_frame_len(xfer, 0, 0); + } + if (urb->bsd_isread && (!xfer->flags.ext_buffer)) { + /* copy in data with regard to the URB */ + usbd_copy_out(xfer->frbuffers + data_frame, 0, + urb->bsd_data_ptr, xfer->frlengths[data_frame]); + } + urb->bsd_length_rem -= xfer->frlengths[data_frame]; + urb->bsd_data_ptr += xfer->frlengths[data_frame]; + urb->actual_length += xfer->frlengths[data_frame]; + + /* check for short transfer */ + if (xfer->actlen < xfer->sumlen) { + urb->bsd_length_rem = 0; + + /* short transfer */ + if (urb->transfer_flags & URB_SHORT_NOT_OK) { + urb->status = -EPIPE; + } else { + urb->status = 0; + } + } else { + /* check remainder */ + if (urb->bsd_length_rem > 0) { + goto setup_bulk; + } + /* success */ + urb->status = 0; + } + + /* call callback */ + usb_linux_complete(xfer); + + case USB_ST_SETUP: +tr_setup: + /* get next transfer */ + urb = TAILQ_FIRST(&uhe->bsd_urb_list); + if (urb == NULL) { + /* nothing to do */ + return; + } + TAILQ_REMOVE(&uhe->bsd_urb_list, urb, bsd_urb_list); + urb->bsd_urb_list.tqe_prev = NULL; + + usbd_xfer_set_priv(xfer, urb); + xfer->flags.force_short_xfer = 0; + xfer->timeout = urb->timeout; + + if (xfer->flags_int.control_xfr) { + + /* + * USB control transfers need special handling. + * First copy in the header, then copy in data! + */ + if (!xfer->flags.ext_buffer) { + usbd_copy_in(xfer->frbuffers, 0, + urb->setup_packet, REQ_SIZE); + usbd_xfer_set_frame_len(xfer, 0, REQ_SIZE); + } else { + /* set virtual address to load */ + usbd_xfer_set_frame_data(xfer, 0, + urb->setup_packet, REQ_SIZE); + } + + ptr = urb->setup_packet; + + /* setup data transfer direction and length */ + urb->bsd_isread = (ptr[0] & UT_READ) ? 1 : 0; + urb->bsd_length_rem = ptr[6] | (ptr[7] << 8); + + } else { + + /* setup data transfer direction */ + + urb->bsd_length_rem = urb->transfer_buffer_length; + urb->bsd_isread = (uhe->desc.bEndpointAddress & + UE_DIR_IN) ? 1 : 0; + } + + urb->bsd_data_ptr = urb->transfer_buffer; + urb->actual_length = 0; + +setup_bulk: + if (max_bulk > urb->bsd_length_rem) { + max_bulk = urb->bsd_length_rem; + } + /* check if we need to force a short transfer */ + + if ((max_bulk == urb->bsd_length_rem) && + (urb->transfer_flags & URB_ZERO_PACKET) && + (!xfer->flags_int.control_xfr)) { + xfer->flags.force_short_xfer = 1; + } + /* check if we need to copy in data */ + + if (xfer->flags.ext_buffer) { + /* set virtual address to load */ + usbd_xfer_set_frame_data(xfer, data_frame, + urb->bsd_data_ptr, max_bulk); + } else if (!urb->bsd_isread) { + /* copy out data with regard to the URB */ + usbd_copy_in(xfer->frbuffers + data_frame, 0, + urb->bsd_data_ptr, max_bulk); + usbd_xfer_set_frame_len(xfer, data_frame, max_bulk); + } + if (xfer->flags_int.control_xfr) { + if (max_bulk > 0) { + xfer->nframes = 2; + } else { + xfer->nframes = 1; + } + } else { + xfer->nframes = 1; + } + usbd_transfer_submit(xfer); + return; + + default: + if (xfer->error == USB_ERR_CANCELLED) { + urb->status = -ECONNRESET; + } else { + urb->status = -EPIPE; + } + + /* Set zero for "actual_length" */ + urb->actual_length = 0; + + /* call callback */ + usb_linux_complete(xfer); + + if (xfer->error == USB_ERR_CANCELLED) { + /* we need to return in this case */ + return; + } + goto tr_setup; + } +} + +/*------------------------------------------------------------------------* + * usb_fill_bulk_urb + *------------------------------------------------------------------------*/ +void +usb_fill_bulk_urb(struct urb *urb, struct usb_device *udev, + struct usb_host_endpoint *uhe, void *buf, + int length, usb_complete_t callback, void *arg) +{ + urb->dev = udev; + urb->endpoint = uhe; + urb->transfer_buffer = buf; + urb->transfer_buffer_length = length; + urb->complete = callback; + urb->context = arg; +} + +/*------------------------------------------------------------------------* + * usb_bulk_msg + * + * NOTE: This function can also be used for interrupt endpoints! + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +int +usb_bulk_msg(struct usb_device *udev, struct usb_host_endpoint *uhe, + void *data, int len, uint16_t *pactlen, usb_timeout_t timeout) +{ + struct urb *urb; + int err; + + if (uhe == NULL) + return (-EINVAL); + if (len < 0) + return (-EINVAL); + + err = usb_setup_endpoint(udev, uhe, 4096 /* bytes */); + if (err) + return (err); + + urb = usb_alloc_urb(0, 0); + if (urb == NULL) + return (-ENOMEM); + + usb_fill_bulk_urb(urb, udev, uhe, data, len, + usb_linux_wait_complete, NULL); + + err = usb_start_wait_urb(urb, timeout, pactlen); + + usb_free_urb(urb); + + return (err); +} diff --git a/sys/bus/u4b/usb_compat_linux.h b/sys/bus/u4b/usb_compat_linux.h new file mode 100644 index 0000000000..1f00d4b4ad --- /dev/null +++ b/sys/bus/u4b/usb_compat_linux.h @@ -0,0 +1,310 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2007 Luigi Rizzo - Universita` di Pisa. All rights reserved. + * Copyright (c) 2007 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_COMPAT_LINUX_H +#define _USB_COMPAT_LINUX_H + +struct usb_device; +struct usb_interface; +struct usb_driver; +struct urb; + +typedef void *pm_message_t; +typedef void (usb_complete_t)(struct urb *); + +#define USB_MAX_FULL_SPEED_ISOC_FRAMES (60 * 1) +#define USB_MAX_HIGH_SPEED_ISOC_FRAMES (60 * 8) + +#define USB_DEVICE_ID_MATCH_DEVICE \ + (USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT) + +#define USB_DEVICE(vend,prod) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = (vend), \ + .idProduct = (prod) + +/* The "usb_driver" structure holds the Linux USB device driver + * callbacks, and a pointer to device ID's which this entry should + * match against. Usually this entry is exposed to the USB emulation + * layer using the "USB_DRIVER_EXPORT()" macro, which is defined + * below. + */ +struct usb_driver { + const char *name; + + int (*probe) (struct usb_interface *intf, + const struct usb_device_id *id); + + void (*disconnect) (struct usb_interface *intf); + + int (*ioctl) (struct usb_interface *intf, unsigned int code, + void *buf); + + int (*suspend) (struct usb_interface *intf, pm_message_t message); + int (*resume) (struct usb_interface *intf); + + const struct usb_device_id *id_table; + + void (*shutdown) (struct usb_interface *intf); + + LIST_ENTRY(usb_driver) linux_driver_list; +}; + +#define USB_DRIVER_EXPORT(id,p_usb_drv) \ + SYSINIT(id,SI_SUB_KLD,SI_ORDER_FIRST,usb_linux_register,p_usb_drv); \ + SYSUNINIT(id,SI_SUB_KLD,SI_ORDER_ANY,usb_linux_deregister,p_usb_drv) + +#define USB_DT_ENDPOINT_SIZE 7 +#define USB_DT_ENDPOINT_AUDIO_SIZE 9 + +/* + * Endpoints + */ +#define USB_ENDPOINT_NUMBER_MASK 0x0f /* in bEndpointAddress */ +#define USB_ENDPOINT_DIR_MASK 0x80 + +#define USB_ENDPOINT_XFERTYPE_MASK 0x03 /* in bmAttributes */ +#define USB_ENDPOINT_XFER_CONTROL 0 +#define USB_ENDPOINT_XFER_ISOC 1 +#define USB_ENDPOINT_XFER_BULK 2 +#define USB_ENDPOINT_XFER_INT 3 +#define USB_ENDPOINT_MAX_ADJUSTABLE 0x80 + +/* CONTROL REQUEST SUPPORT */ + +/* + * Definition of direction mask for + * "bEndpointAddress" and "bmRequestType": + */ +#define USB_DIR_MASK 0x80 +#define USB_DIR_OUT 0x00 /* write to USB device */ +#define USB_DIR_IN 0x80 /* read from USB device */ + +/* + * Definition of type mask for + * "bmRequestType": + */ +#define USB_TYPE_MASK (0x03 << 5) +#define USB_TYPE_STANDARD (0x00 << 5) +#define USB_TYPE_CLASS (0x01 << 5) +#define USB_TYPE_VENDOR (0x02 << 5) +#define USB_TYPE_RESERVED (0x03 << 5) + +/* + * Definition of receiver mask for + * "bmRequestType": + */ +#define USB_RECIP_MASK 0x1f +#define USB_RECIP_DEVICE 0x00 +#define USB_RECIP_INTERFACE 0x01 +#define USB_RECIP_ENDPOINT 0x02 +#define USB_RECIP_OTHER 0x03 + +/* + * Definition of standard request values for + * "bRequest": + */ +#define USB_REQ_GET_STATUS 0x00 +#define USB_REQ_CLEAR_FEATURE 0x01 +#define USB_REQ_SET_FEATURE 0x03 +#define USB_REQ_SET_ADDRESS 0x05 +#define USB_REQ_GET_DESCRIPTOR 0x06 +#define USB_REQ_SET_DESCRIPTOR 0x07 +#define USB_REQ_GET_CONFIGURATION 0x08 +#define USB_REQ_SET_CONFIGURATION 0x09 +#define USB_REQ_GET_INTERFACE 0x0A +#define USB_REQ_SET_INTERFACE 0x0B +#define USB_REQ_SYNCH_FRAME 0x0C + +#define USB_REQ_SET_ENCRYPTION 0x0D /* Wireless USB */ +#define USB_REQ_GET_ENCRYPTION 0x0E +#define USB_REQ_SET_HANDSHAKE 0x0F +#define USB_REQ_GET_HANDSHAKE 0x10 +#define USB_REQ_SET_CONNECTION 0x11 +#define USB_REQ_SET_SECURITY_DATA 0x12 +#define USB_REQ_GET_SECURITY_DATA 0x13 +#define USB_REQ_SET_WUSB_DATA 0x14 +#define USB_REQ_LOOPBACK_DATA_WRITE 0x15 +#define USB_REQ_LOOPBACK_DATA_READ 0x16 +#define USB_REQ_SET_INTERFACE_DS 0x17 + +/* + * USB feature flags are written using USB_REQ_{CLEAR,SET}_FEATURE, and + * are read as a bit array returned by USB_REQ_GET_STATUS. (So there + * are at most sixteen features of each type.) + */ +#define USB_DEVICE_SELF_POWERED 0 /* (read only) */ +#define USB_DEVICE_REMOTE_WAKEUP 1 /* dev may initiate wakeup */ +#define USB_DEVICE_TEST_MODE 2 /* (wired high speed only) */ +#define USB_DEVICE_BATTERY 2 /* (wireless) */ +#define USB_DEVICE_B_HNP_ENABLE 3 /* (otg) dev may initiate HNP */ +#define USB_DEVICE_WUSB_DEVICE 3 /* (wireless) */ +#define USB_DEVICE_A_HNP_SUPPORT 4 /* (otg) RH port supports HNP */ +#define USB_DEVICE_A_ALT_HNP_SUPPORT 5 /* (otg) other RH port does */ +#define USB_DEVICE_DEBUG_MODE 6 /* (special devices only) */ + +#define USB_ENDPOINT_HALT 0 /* IN/OUT will STALL */ + +#define PIPE_ISOCHRONOUS 0x01 /* UE_ISOCHRONOUS */ +#define PIPE_INTERRUPT 0x03 /* UE_INTERRUPT */ +#define PIPE_CONTROL 0x00 /* UE_CONTROL */ +#define PIPE_BULK 0x02 /* UE_BULK */ + +/* Whenever Linux references an USB endpoint: + * a) to initialize "urb->endpoint" + * b) second argument passed to "usb_control_msg()" + * + * Then it uses one of the following macros. The "endpoint" argument + * is the physical endpoint value masked by 0xF. The "dev" argument + * is a pointer to "struct usb_device". + */ +#define usb_sndctrlpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_CONTROL, (endpoint) | USB_DIR_OUT) + +#define usb_rcvctrlpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_CONTROL, (endpoint) | USB_DIR_IN) + +#define usb_sndisocpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_ISOCHRONOUS, (endpoint) | USB_DIR_OUT) + +#define usb_rcvisocpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_ISOCHRONOUS, (endpoint) | USB_DIR_IN) + +#define usb_sndbulkpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_BULK, (endpoint) | USB_DIR_OUT) + +#define usb_rcvbulkpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_BULK, (endpoint) | USB_DIR_IN) + +#define usb_sndintpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_INTERRUPT, (endpoint) | USB_DIR_OUT) + +#define usb_rcvintpipe(dev,endpoint) \ + usb_find_host_endpoint(dev, PIPE_INTERRUPT, (endpoint) | USB_DIR_IN) + +/* + * The following structure is used to extend "struct urb" when we are + * dealing with an isochronous endpoint. It contains information about + * the data offset and data length of an isochronous packet. + * The "actual_length" field is updated before the "complete" + * callback in the "urb" structure is called. + */ +struct usb_iso_packet_descriptor { + uint32_t offset; /* depreciated buffer offset (the + * packets are usually back to back) */ + uint16_t length; /* expected length */ + uint16_t actual_length; + int16_t status; /* transfer status */ +}; + +/* + * The following structure holds various information about an USB + * transfer. This structure is used for all kinds of USB transfers. + * + * URB is short for USB Request Block. + */ +struct urb { + TAILQ_ENTRY(urb) bsd_urb_list; + struct cv cv_wait; + + struct usb_device *dev; /* (in) pointer to associated device */ + struct usb_host_endpoint *endpoint; /* (in) pipe pointer */ + uint8_t *setup_packet; /* (in) setup packet (control only) */ + uint8_t *bsd_data_ptr; + void *transfer_buffer; /* (in) associated data buffer */ + void *context; /* (in) context for completion */ + usb_complete_t *complete; /* (in) completion routine */ + + usb_size_t transfer_buffer_length;/* (in) data buffer length */ + usb_size_t bsd_length_rem; + usb_size_t actual_length; /* (return) actual transfer length */ + usb_timeout_t timeout; /* FreeBSD specific */ + + uint16_t transfer_flags; /* (in) */ +#define URB_SHORT_NOT_OK 0x0001 /* report short transfers like errors */ +#define URB_ISO_ASAP 0x0002 /* ignore "start_frame" field */ +#define URB_ZERO_PACKET 0x0004 /* the USB transfer ends with a short + * packet */ +#define URB_NO_TRANSFER_DMA_MAP 0x0008 /* "transfer_dma" is valid on submit */ +#define URB_WAIT_WAKEUP 0x0010 /* custom flags */ +#define URB_IS_SLEEPING 0x0020 /* custom flags */ + + usb_frcount_t start_frame; /* (modify) start frame (ISO) */ + usb_frcount_t number_of_packets; /* (in) number of ISO packets */ + uint16_t interval; /* (modify) transfer interval + * (INT/ISO) */ + uint16_t error_count; /* (return) number of ISO errors */ + int16_t status; /* (return) status */ + + uint8_t setup_dma; /* (in) not used on FreeBSD */ + uint8_t transfer_dma; /* (in) not used on FreeBSD */ + uint8_t bsd_isread; + uint8_t kill_count; /* FreeBSD specific */ + + struct usb_iso_packet_descriptor iso_frame_desc[]; /* (in) ISO ONLY */ +}; + +/* various prototypes */ + +int usb_submit_urb(struct urb *urb, uint16_t mem_flags); +int usb_unlink_urb(struct urb *urb); +int usb_clear_halt(struct usb_device *dev, struct usb_host_endpoint *uhe); +int usb_control_msg(struct usb_device *dev, struct usb_host_endpoint *ep, + uint8_t request, uint8_t requesttype, uint16_t value, + uint16_t index, void *data, uint16_t size, usb_timeout_t timeout); +int usb_set_interface(struct usb_device *dev, uint8_t ifnum, + uint8_t alternate); +int usb_setup_endpoint(struct usb_device *dev, + struct usb_host_endpoint *uhe, usb_frlength_t bufsize); + +struct usb_host_endpoint *usb_find_host_endpoint(struct usb_device *dev, + uint8_t type, uint8_t ep); +struct urb *usb_alloc_urb(uint16_t iso_packets, uint16_t mem_flags); +struct usb_host_interface *usb_altnum_to_altsetting( + const struct usb_interface *intf, uint8_t alt_index); +struct usb_interface *usb_ifnum_to_if(struct usb_device *dev, uint8_t iface_no); + +void *usb_buffer_alloc(struct usb_device *dev, usb_size_t size, + uint16_t mem_flags, uint8_t *dma_addr); +void *usbd_get_intfdata(struct usb_interface *intf); + +void usb_buffer_free(struct usb_device *dev, usb_size_t size, void *addr, uint8_t dma_addr); +void usb_free_urb(struct urb *urb); +void usb_init_urb(struct urb *urb); +void usb_kill_urb(struct urb *urb); +void usb_set_intfdata(struct usb_interface *intf, void *data); +void usb_linux_register(void *arg); +void usb_linux_deregister(void *arg); + +void usb_fill_bulk_urb(struct urb *, struct usb_device *, + struct usb_host_endpoint *, void *, int, usb_complete_t, void *); +int usb_bulk_msg(struct usb_device *, struct usb_host_endpoint *, + void *, int, uint16_t *, usb_timeout_t); + +#define interface_to_usbdev(intf) (intf)->linux_udev +#define interface_to_bsddev(intf) (intf)->linux_udev + +#endif /* _USB_COMPAT_LINUX_H */ diff --git a/sys/bus/u4b/usb_controller.h b/sys/bus/u4b/usb_controller.h new file mode 100644 index 0000000000..4ffc041ebb --- /dev/null +++ b/sys/bus/u4b/usb_controller.h @@ -0,0 +1,237 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_CONTROLLER_H_ +#define _USB_CONTROLLER_H_ + +/* defines */ + +#define USB_BUS_DMA_TAG_MAX 8 + +/* structure prototypes */ + +struct usb_bus; +struct usb_page; +struct usb_endpoint; +struct usb_page_cache; +struct usb_setup_params; +struct usb_hw_ep_profile; +struct usb_fs_isoc_schedule; +struct usb_config_descriptor; +struct usb_endpoint_descriptor; + +/* typedefs */ + +typedef void (usb_bus_mem_sub_cb_t)(struct usb_bus *bus, struct usb_page_cache *pc, struct usb_page *pg, usb_size_t size, usb_size_t align); +typedef void (usb_bus_mem_cb_t)(struct usb_bus *bus, usb_bus_mem_sub_cb_t *scb); + +/* + * The following structure is used to define all the USB BUS + * callbacks. + */ +struct usb_bus_methods { + + /* USB Device and Host mode - Mandatory */ + + usb_handle_req_t *roothub_exec; + + void (*endpoint_init) (struct usb_device *, + struct usb_endpoint_descriptor *, struct usb_endpoint *); + void (*xfer_setup) (struct usb_setup_params *); + void (*xfer_unsetup) (struct usb_xfer *); + void (*get_dma_delay) (struct usb_device *, uint32_t *); + void (*device_suspend) (struct usb_device *); + void (*device_resume) (struct usb_device *); + void (*set_hw_power) (struct usb_bus *); + void (*set_hw_power_sleep) (struct usb_bus *, uint32_t); + /* + * The following flag is set if one or more control transfers are + * active: + */ +#define USB_HW_POWER_CONTROL 0x01 + /* + * The following flag is set if one or more bulk transfers are + * active: + */ +#define USB_HW_POWER_BULK 0x02 + /* + * The following flag is set if one or more interrupt transfers are + * active: + */ +#define USB_HW_POWER_INTERRUPT 0x04 + /* + * The following flag is set if one or more isochronous transfers + * are active: + */ +#define USB_HW_POWER_ISOC 0x08 + /* + * The following flag is set if one or more non-root-HUB devices + * are present on the given USB bus: + */ +#define USB_HW_POWER_NON_ROOT_HUB 0x10 + /* + * The following flag is set if we are suspending + */ +#define USB_HW_POWER_SUSPEND 0x20 + /* + * The following flag is set if we are resuming + */ +#define USB_HW_POWER_RESUME 0x40 + /* + * The following flag is set if we are shutting down + */ +#define USB_HW_POWER_SHUTDOWN 0x60 + + /* USB Device mode only - Mandatory */ + + void (*get_hw_ep_profile) (struct usb_device *udev, const struct usb_hw_ep_profile **ppf, uint8_t ep_addr); + void (*set_stall) (struct usb_device *udev, struct usb_xfer *xfer, struct usb_endpoint *ep, uint8_t *did_stall); + + /* USB Device mode mandatory. USB Host mode optional. */ + + void (*clear_stall) (struct usb_device *udev, struct usb_endpoint *ep); + + /* Optional transfer polling support */ + + void (*xfer_poll) (struct usb_bus *); + + /* Optional fixed power mode support */ + + void (*get_power_mode) (struct usb_device *udev, int8_t *pmode); + + /* Optional endpoint uninit */ + + void (*endpoint_uninit) (struct usb_device *, struct usb_endpoint *); + + /* Optional device init */ + + usb_error_t (*device_init) (struct usb_device *); + + /* Optional device uninit */ + + void (*device_uninit) (struct usb_device *); + + /* Optional for device and host mode */ + + void (*start_dma_delay) (struct usb_xfer *); + + void (*device_state_change) (struct usb_device *); + + /* Optional for host mode */ + + usb_error_t (*set_address) (struct usb_device *, struct mtx *, uint16_t); +}; + +/* + * The following structure is used to define all the USB pipe + * callbacks. + */ +struct usb_pipe_methods { + + /* Mandatory USB Device and Host mode callbacks: */ + + void (*open)(struct usb_xfer *); + void (*close)(struct usb_xfer *); + + void (*enter)(struct usb_xfer *); + void (*start)(struct usb_xfer *); + + /* Optional */ + + void *info; +}; + +/* + * The following structure keeps information about what a hardware USB + * endpoint supports. + */ +struct usb_hw_ep_profile { + uint16_t max_in_frame_size; /* IN-token direction */ + uint16_t max_out_frame_size; /* OUT-token direction */ + uint8_t is_simplex:1; + uint8_t support_multi_buffer:1; + uint8_t support_bulk:1; + uint8_t support_control:1; + uint8_t support_interrupt:1; + uint8_t support_isochronous:1; + uint8_t support_in:1; /* IN-token is supported */ + uint8_t support_out:1; /* OUT-token is supported */ +}; + +/* + * The following structure is used when trying to allocate hardware + * endpoints for an USB configuration in USB device side mode. + */ +struct usb_hw_ep_scratch_sub { + const struct usb_hw_ep_profile *pf; + uint16_t max_frame_size; + uint8_t hw_endpoint_out; + uint8_t hw_endpoint_in; + uint8_t needs_ep_type; + uint8_t needs_in:1; + uint8_t needs_out:1; +}; + +/* + * The following structure is used when trying to allocate hardware + * endpoints for an USB configuration in USB device side mode. + */ +struct usb_hw_ep_scratch { + struct usb_hw_ep_scratch_sub ep[USB_EP_MAX]; + struct usb_hw_ep_scratch_sub *ep_max; + struct usb_config_descriptor *cd; + struct usb_device *udev; + struct usb_bus_methods *methods; + uint8_t bmOutAlloc[(USB_EP_MAX + 15) / 16]; + uint8_t bmInAlloc[(USB_EP_MAX + 15) / 16]; +}; + +/* + * The following structure is used when generating USB descriptors + * from USB templates. + */ +struct usb_temp_setup { + void *buf; + usb_size_t size; + enum usb_dev_speed usb_speed; + uint8_t self_powered; + uint8_t bNumEndpoints; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bConfigurationValue; + usb_error_t err; +}; + +/* prototypes */ + +void usb_bus_mem_flush_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb); +uint8_t usb_bus_mem_alloc_all(struct usb_bus *bus, bus_dma_tag_t dmat, usb_bus_mem_cb_t *cb); +void usb_bus_mem_free_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb); +uint16_t usb_isoc_time_expand(struct usb_bus *bus, uint16_t isoc_time_curr); +uint16_t usbd_fs_isoc_schedule_isoc_time_expand(struct usb_device *udev, struct usb_fs_isoc_schedule **pp_start, struct usb_fs_isoc_schedule **pp_end, uint16_t isoc_time); +uint8_t usbd_fs_isoc_schedule_alloc(struct usb_fs_isoc_schedule *fss, uint8_t *pstart, uint16_t len); + +#endif /* _USB_CONTROLLER_H_ */ diff --git a/sys/bus/u4b/usb_core.c b/sys/bus/u4b/usb_core.c new file mode 100644 index 0000000000..1bd05c869f --- /dev/null +++ b/sys/bus/u4b/usb_core.c @@ -0,0 +1,59 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * USB specifications and other documentation can be found at + * http://www.usb.org/developers/docs/ and + * http://www.usb.org/developers/devclass_docs/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +MALLOC_DEFINE(M_USB, "USB", "USB"); +MALLOC_DEFINE(M_USBDEV, "USBdev", "USB device"); +MALLOC_DEFINE(M_USBHC, "USBHC", "USB host controller"); + +MODULE_VERSION(usb, 1); diff --git a/sys/bus/u4b/usb_core.h b/sys/bus/u4b/usb_core.h new file mode 100644 index 0000000000..3dfd0d1ab6 --- /dev/null +++ b/sys/bus/u4b/usb_core.h @@ -0,0 +1,183 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Including this file is mandatory for all USB related c-files in the kernel. + */ + +#ifndef _USB_CORE_H_ +#define _USB_CORE_H_ + +/* + * The following macro will tell if an USB transfer is currently + * receiving or transferring data. + */ +#define USB_GET_DATA_ISREAD(xfer) ((xfer)->flags_int.usb_mode == \ + USB_MODE_DEVICE ? (((xfer)->endpointno & UE_DIR_IN) ? 0 : 1) : \ + (((xfer)->endpointno & UE_DIR_IN) ? 1 : 0)) + +/* macros */ + +#define USB_BUS_LOCK(_b) mtx_lock(&(_b)->bus_mtx) +#define USB_BUS_UNLOCK(_b) mtx_unlock(&(_b)->bus_mtx) +#define USB_BUS_LOCK_ASSERT(_b, _t) mtx_assert(&(_b)->bus_mtx, _t) +#define USB_XFER_LOCK(_x) mtx_lock((_x)->xroot->xfer_mtx) +#define USB_XFER_UNLOCK(_x) mtx_unlock((_x)->xroot->xfer_mtx) +#define USB_XFER_LOCK_ASSERT(_x, _t) mtx_assert((_x)->xroot->xfer_mtx, _t) + +/* helper for converting pointers to integers */ +#define USB_P2U(ptr) \ + (((const uint8_t *)(ptr)) - ((const uint8_t *)0)) + +/* helper for computing offsets */ +#define USB_ADD_BYTES(ptr,size) \ + ((void *)(USB_P2U(ptr) + (size))) + +/* debug macro */ +#define USB_ASSERT KASSERT + +/* structure prototypes */ + +struct file; +struct usb_bus; +struct usb_device; +struct usb_device_request; +struct usb_page; +struct usb_page_cache; +struct usb_xfer; +struct usb_xfer_root; + +/* typedefs */ + +/* structures */ + +/* + * The following structure defines a set of internal USB transfer + * flags. + */ +struct usb_xfer_flags_int { + + enum usb_hc_mode usb_mode; /* shadow copy of "udev->usb_mode" */ + uint16_t control_rem; /* remainder in bytes */ + + uint8_t open:1; /* set if USB pipe has been opened */ + uint8_t transferring:1; /* set if an USB transfer is in + * progress */ + uint8_t did_dma_delay:1; /* set if we waited for HW DMA */ + uint8_t did_close:1; /* set if we closed the USB transfer */ + uint8_t draining:1; /* set if we are draining an USB + * transfer */ + uint8_t started:1; /* keeps track of started or stopped */ + uint8_t bandwidth_reclaimed:1; + uint8_t control_xfr:1; /* set if control transfer */ + uint8_t control_hdr:1; /* set if control header should be + * sent */ + uint8_t control_act:1; /* set if control transfer is active */ + uint8_t control_stall:1; /* set if control transfer should be stalled */ + + uint8_t short_frames_ok:1; /* filtered version */ + uint8_t short_xfer_ok:1; /* filtered version */ +#if USB_HAVE_BUSDMA + uint8_t bdma_enable:1; /* filtered version (only set if + * hardware supports DMA) */ + uint8_t bdma_no_post_sync:1; /* set if the USB callback wrapper + * should not do the BUS-DMA post sync + * operation */ + uint8_t bdma_setup:1; /* set if BUS-DMA has been setup */ +#endif + uint8_t isochronous_xfr:1; /* set if isochronous transfer */ + uint8_t curr_dma_set:1; /* used by USB HC/DC driver */ + uint8_t can_cancel_immed:1; /* set if USB transfer can be + * cancelled immediately */ + uint8_t doing_callback:1; /* set if executing the callback */ +}; + +/* + * The following structure defines an USB transfer. + */ +struct usb_xfer { + struct usb_callout timeout_handle; + TAILQ_ENTRY(usb_xfer) wait_entry; /* used at various places */ + + struct usb_page_cache *buf_fixup; /* fixup buffer(s) */ + struct usb_xfer_queue *wait_queue; /* pointer to queue that we + * are waiting on */ + struct usb_page *dma_page_ptr; + struct usb_endpoint *endpoint; /* our USB endpoint */ + struct usb_xfer_root *xroot; /* used by HC driver */ + void *qh_start[2]; /* used by HC driver */ + void *td_start[2]; /* used by HC driver */ + void *td_transfer_first; /* used by HC driver */ + void *td_transfer_last; /* used by HC driver */ + void *td_transfer_cache; /* used by HC driver */ + void *priv_sc; /* device driver data pointer 1 */ + void *priv_fifo; /* device driver data pointer 2 */ + void *local_buffer; + usb_frlength_t *frlengths; + struct usb_page_cache *frbuffers; + usb_callback_t *callback; + + usb_frlength_t max_hc_frame_size; + usb_frlength_t max_data_length; + usb_frlength_t sumlen; /* sum of all lengths in bytes */ + usb_frlength_t actlen; /* actual length in bytes */ + usb_timeout_t timeout; /* milliseconds */ + + usb_frcount_t max_frame_count; /* initial value of "nframes" after + * setup */ + usb_frcount_t nframes; /* number of USB frames to transfer */ + usb_frcount_t aframes; /* actual number of USB frames + * transferred */ + + uint16_t max_packet_size; + uint16_t max_frame_size; + uint16_t qh_pos; + uint16_t isoc_time_complete; /* in ms */ + usb_timeout_t interval; /* milliseconds */ + + uint8_t address; /* physical USB address */ + uint8_t endpointno; /* physical USB endpoint */ + uint8_t max_packet_count; + uint8_t usb_state; + uint8_t fps_shift; /* down shift of FPS, 0..3 */ + + usb_error_t error; + + struct usb_xfer_flags flags; + struct usb_xfer_flags_int flags_int; +}; + +/* external variables */ + +extern struct mtx usb_ref_lock; + +/* typedefs */ + +typedef struct malloc_type *usb_malloc_type; + +/* prototypes */ + +#endif /* _USB_CORE_H_ */ diff --git a/sys/bus/u4b/usb_debug.c b/sys/bus/u4b/usb_debug.c new file mode 100644 index 0000000000..a39d400361 --- /dev/null +++ b/sys/bus/u4b/usb_debug.c @@ -0,0 +1,176 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * Define this unconditionally in case a kernel module is loaded that + * has been compiled with debugging options. + */ +int usb_debug = 0; + +SYSCTL_NODE(_hw, OID_AUTO, usb, CTLFLAG_RW, 0, "USB debugging"); +SYSCTL_INT(_hw_usb, OID_AUTO, debug, CTLFLAG_RW, + &usb_debug, 0, "Debug level"); + +TUNABLE_INT("hw.usb.debug", &usb_debug); + +/*------------------------------------------------------------------------* + * usb_dump_iface + * + * This function dumps information about an USB interface. + *------------------------------------------------------------------------*/ +void +usb_dump_iface(struct usb_interface *iface) +{ + printf("usb_dump_iface: iface=%p\n", iface); + if (iface == NULL) { + return; + } + printf(" iface=%p idesc=%p altindex=%d\n", + iface, iface->idesc, iface->alt_index); +} + +/*------------------------------------------------------------------------* + * usb_dump_device + * + * This function dumps information about an USB device. + *------------------------------------------------------------------------*/ +void +usb_dump_device(struct usb_device *udev) +{ + printf("usb_dump_device: dev=%p\n", udev); + if (udev == NULL) { + return; + } + printf(" bus=%p \n" + " address=%d config=%d depth=%d speed=%d self_powered=%d\n" + " power=%d langid=%d\n", + udev->bus, + udev->address, udev->curr_config_no, udev->depth, udev->speed, + udev->flags.self_powered, udev->power, udev->langid); +} + +/*------------------------------------------------------------------------* + * usb_dump_queue + * + * This function dumps the USB transfer that are queued up on an USB endpoint. + *------------------------------------------------------------------------*/ +void +usb_dump_queue(struct usb_endpoint *ep) +{ + struct usb_xfer *xfer; + + printf("usb_dump_queue: endpoint=%p xfer: ", ep); + TAILQ_FOREACH(xfer, &ep->endpoint_q.head, wait_entry) { + printf(" %p", xfer); + } + printf("\n"); +} + +/*------------------------------------------------------------------------* + * usb_dump_endpoint + * + * This function dumps information about an USB endpoint. + *------------------------------------------------------------------------*/ +void +usb_dump_endpoint(struct usb_endpoint *ep) +{ + if (ep) { + printf("usb_dump_endpoint: endpoint=%p", ep); + + printf(" edesc=%p isoc_next=%d toggle_next=%d", + ep->edesc, ep->isoc_next, ep->toggle_next); + + if (ep->edesc) { + printf(" bEndpointAddress=0x%02x", + ep->edesc->bEndpointAddress); + } + printf("\n"); + usb_dump_queue(ep); + } else { + printf("usb_dump_endpoint: endpoint=NULL\n"); + } +} + +/*------------------------------------------------------------------------* + * usb_dump_xfer + * + * This function dumps information about an USB transfer. + *------------------------------------------------------------------------*/ +void +usb_dump_xfer(struct usb_xfer *xfer) +{ + struct usb_device *udev; + printf("usb_dump_xfer: xfer=%p\n", xfer); + if (xfer == NULL) { + return; + } + if (xfer->endpoint == NULL) { + printf("xfer %p: endpoint=NULL\n", + xfer); + return; + } + udev = xfer->xroot->udev; + printf("xfer %p: udev=%p vid=0x%04x pid=0x%04x addr=%d " + "endpoint=%p ep=0x%02x attr=0x%02x\n", + xfer, udev, + UGETW(udev->ddesc.idVendor), + UGETW(udev->ddesc.idProduct), + udev->address, xfer->endpoint, + xfer->endpoint->edesc->bEndpointAddress, + xfer->endpoint->edesc->bmAttributes); +} diff --git a/sys/bus/u4b/usb_debug.h b/sys/bus/u4b/usb_debug.h new file mode 100644 index 0000000000..8718c89bef --- /dev/null +++ b/sys/bus/u4b/usb_debug.h @@ -0,0 +1,62 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* This file contains various factored out debug macros. */ + +#ifndef _USB_DEBUG_H_ +#define _USB_DEBUG_H_ + +/* Declare global USB debug variable. */ +extern int usb_debug; + +/* Check if USB debugging is enabled. */ +#ifdef USB_DEBUG_VAR +#ifdef USB_DEBUG +#define DPRINTFN(n,fmt,...) do { \ + if ((USB_DEBUG_VAR) >= (n)) { \ + printf("%s: " fmt, \ + __FUNCTION__,## __VA_ARGS__); \ + } \ +} while (0) +#define DPRINTF(...) DPRINTFN(1, __VA_ARGS__) +#else +#define DPRINTF(...) do { } while (0) +#define DPRINTFN(...) do { } while (0) +#endif +#endif + +struct usb_interface; +struct usb_device; +struct usb_endpoint; +struct usb_xfer; + +void usb_dump_iface(struct usb_interface *iface); +void usb_dump_device(struct usb_device *udev); +void usb_dump_queue(struct usb_endpoint *ep); +void usb_dump_endpoint(struct usb_endpoint *ep); +void usb_dump_xfer(struct usb_xfer *xfer); + +#endif /* _USB_DEBUG_H_ */ diff --git a/sys/bus/u4b/usb_dev.c b/sys/bus/u4b/usb_dev.c new file mode 100644 index 0000000000..2b99036410 --- /dev/null +++ b/sys/bus/u4b/usb_dev.c @@ -0,0 +1,2295 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2006-2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * + * usb_dev.c - An abstraction layer for creating devices under /dev/... + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb_fifo_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#if USB_HAVE_UGEN + +#ifdef USB_DEBUG +static int usb_fifo_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, dev, CTLFLAG_RW, 0, "USB device"); +SYSCTL_INT(_hw_usb_dev, OID_AUTO, debug, CTLFLAG_RW, + &usb_fifo_debug, 0, "Debug Level"); + +TUNABLE_INT("hw.usb.dev.debug", &usb_fifo_debug); +#endif + +#if ((__FreeBSD_version >= 700001) || (__FreeBSD_version == 0) || \ + ((__FreeBSD_version >= 600034) && (__FreeBSD_version < 700000))) +#define USB_UCRED struct ucred *ucred, +#else +#define USB_UCRED +#endif + +/* prototypes */ + +static int usb_fifo_open(struct usb_cdev_privdata *, + struct usb_fifo *, int); +static void usb_fifo_close(struct usb_fifo *, int); +static void usb_dev_init(void *); +static void usb_dev_init_post(void *); +static void usb_dev_uninit(void *); +static int usb_fifo_uiomove(struct usb_fifo *, void *, int, + struct uio *); +static void usb_fifo_check_methods(struct usb_fifo_methods *); +static struct usb_fifo *usb_fifo_alloc(void); +static struct usb_endpoint *usb_dev_get_ep(struct usb_device *, uint8_t, + uint8_t); +static void usb_loc_fill(struct usb_fs_privdata *, + struct usb_cdev_privdata *); +static void usb_close(void *); +static usb_error_t usb_ref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *, int); +static usb_error_t usb_usb_ref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *); +static void usb_unref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *); + +static d_open_t usb_open; +static d_ioctl_t usb_ioctl; +static d_read_t usb_read; +static d_write_t usb_write; +static d_poll_t usb_poll; + +static d_ioctl_t usb_static_ioctl; + +static usb_fifo_open_t usb_fifo_dummy_open; +static usb_fifo_close_t usb_fifo_dummy_close; +static usb_fifo_ioctl_t usb_fifo_dummy_ioctl; +static usb_fifo_cmd_t usb_fifo_dummy_cmd; + +/* character device structure used for devices (/dev/ugenX.Y and /dev/uXXX) */ +struct cdevsw usb_devsw = { + .d_version = D_VERSION, + .d_open = usb_open, + .d_ioctl = usb_ioctl, + .d_name = "usbdev", + .d_flags = D_TRACKCLOSE, + .d_read = usb_read, + .d_write = usb_write, + .d_poll = usb_poll +}; + +static struct cdev* usb_dev = NULL; + +/* character device structure used for /dev/usb */ +static struct cdevsw usb_static_devsw = { + .d_version = D_VERSION, + .d_ioctl = usb_static_ioctl, + .d_name = "usb" +}; + +static TAILQ_HEAD(, usb_symlink) usb_sym_head; +static struct sx usb_sym_lock; + +struct mtx usb_ref_lock; + +/*------------------------------------------------------------------------* + * usb_loc_fill + * + * This is used to fill out a usb_cdev_privdata structure based on the + * device's address as contained in usb_fs_privdata. + *------------------------------------------------------------------------*/ +static void +usb_loc_fill(struct usb_fs_privdata* pd, struct usb_cdev_privdata *cpd) +{ + cpd->bus_index = pd->bus_index; + cpd->dev_index = pd->dev_index; + cpd->ep_addr = pd->ep_addr; + cpd->fifo_index = pd->fifo_index; +} + +/*------------------------------------------------------------------------* + * usb_ref_device + * + * This function is used to atomically refer an USB device by its + * device location. If this function returns success the USB device + * will not dissappear until the USB device is unreferenced. + * + * Return values: + * 0: Success, refcount incremented on the given USB device. + * Else: Failure. + *------------------------------------------------------------------------*/ +static usb_error_t +usb_ref_device(struct usb_cdev_privdata *cpd, + struct usb_cdev_refdata *crd, int need_uref) +{ + struct usb_fifo **ppf; + struct usb_fifo *f; + + DPRINTFN(2, "cpd=%p need uref=%d\n", cpd, need_uref); + + /* clear all refs */ + memset(crd, 0, sizeof(*crd)); + + mtx_lock(&usb_ref_lock); + cpd->bus = devclass_get_softc(usb_devclass_ptr, cpd->bus_index); + if (cpd->bus == NULL) { + DPRINTFN(2, "no bus at %u\n", cpd->bus_index); + goto error; + } + cpd->udev = cpd->bus->devices[cpd->dev_index]; + if (cpd->udev == NULL) { + DPRINTFN(2, "no device at %u\n", cpd->dev_index); + goto error; + } + if (cpd->udev->refcount == USB_DEV_REF_MAX) { + DPRINTFN(2, "no dev ref\n"); + goto error; + } + if (need_uref) { + DPRINTFN(2, "ref udev - needed\n"); + cpd->udev->refcount++; + + mtx_unlock(&usb_ref_lock); + + /* + * We need to grab the sx-lock before grabbing the + * FIFO refs to avoid deadlock at detach! + */ + usbd_enum_lock(cpd->udev); + + mtx_lock(&usb_ref_lock); + + /* + * Set "is_uref" after grabbing the default SX lock + */ + crd->is_uref = 1; + } + + /* check if we are doing an open */ + if (cpd->fflags == 0) { + /* use zero defaults */ + } else { + /* check for write */ + if (cpd->fflags & FWRITE) { + ppf = cpd->udev->fifo; + f = ppf[cpd->fifo_index + USB_FIFO_TX]; + crd->txfifo = f; + crd->is_write = 1; /* ref */ + if (f == NULL || f->refcount == USB_FIFO_REF_MAX) + goto error; + if (f->curr_cpd != cpd) + goto error; + /* check if USB-FS is active */ + if (f->fs_ep_max != 0) { + crd->is_usbfs = 1; + } + } + + /* check for read */ + if (cpd->fflags & FREAD) { + ppf = cpd->udev->fifo; + f = ppf[cpd->fifo_index + USB_FIFO_RX]; + crd->rxfifo = f; + crd->is_read = 1; /* ref */ + if (f == NULL || f->refcount == USB_FIFO_REF_MAX) + goto error; + if (f->curr_cpd != cpd) + goto error; + /* check if USB-FS is active */ + if (f->fs_ep_max != 0) { + crd->is_usbfs = 1; + } + } + } + + /* when everything is OK we increment the refcounts */ + if (crd->is_write) { + DPRINTFN(2, "ref write\n"); + crd->txfifo->refcount++; + } + if (crd->is_read) { + DPRINTFN(2, "ref read\n"); + crd->rxfifo->refcount++; + } + mtx_unlock(&usb_ref_lock); + + return (0); + +error: + if (crd->is_uref) { + usbd_enum_unlock(cpd->udev); + + if (--(cpd->udev->refcount) == 0) { + cv_signal(&cpd->udev->ref_cv); + } + } + mtx_unlock(&usb_ref_lock); + DPRINTFN(2, "fail\n"); + return (USB_ERR_INVAL); +} + +/*------------------------------------------------------------------------* + * usb_usb_ref_device + * + * This function is used to upgrade an USB reference to include the + * USB device reference on a USB location. + * + * Return values: + * 0: Success, refcount incremented on the given USB device. + * Else: Failure. + *------------------------------------------------------------------------*/ +static usb_error_t +usb_usb_ref_device(struct usb_cdev_privdata *cpd, + struct usb_cdev_refdata *crd) +{ + /* + * Check if we already got an USB reference on this location: + */ + if (crd->is_uref) + return (0); /* success */ + + /* + * To avoid deadlock at detach we need to drop the FIFO ref + * and re-acquire a new ref! + */ + usb_unref_device(cpd, crd); + + return (usb_ref_device(cpd, crd, 1 /* need uref */)); +} + +/*------------------------------------------------------------------------* + * usb_unref_device + * + * This function will release the reference count by one unit for the + * given USB device. + *------------------------------------------------------------------------*/ +static void +usb_unref_device(struct usb_cdev_privdata *cpd, + struct usb_cdev_refdata *crd) +{ + + DPRINTFN(2, "cpd=%p is_uref=%d\n", cpd, crd->is_uref); + + if (crd->is_uref) + usbd_enum_unlock(cpd->udev); + + mtx_lock(&usb_ref_lock); + if (crd->is_read) { + if (--(crd->rxfifo->refcount) == 0) { + cv_signal(&crd->rxfifo->cv_drain); + } + crd->is_read = 0; + } + if (crd->is_write) { + if (--(crd->txfifo->refcount) == 0) { + cv_signal(&crd->txfifo->cv_drain); + } + crd->is_write = 0; + } + if (crd->is_uref) { + if (--(cpd->udev->refcount) == 0) { + cv_signal(&cpd->udev->ref_cv); + } + crd->is_uref = 0; + } + mtx_unlock(&usb_ref_lock); +} + +static struct usb_fifo * +usb_fifo_alloc(void) +{ + struct usb_fifo *f; + + f = malloc(sizeof(*f), M_USBDEV, M_WAITOK | M_ZERO); + if (f) { + cv_init(&f->cv_io, "FIFO-IO"); + cv_init(&f->cv_drain, "FIFO-DRAIN"); + f->refcount = 1; + } + return (f); +} + +/*------------------------------------------------------------------------* + * usb_fifo_create + *------------------------------------------------------------------------*/ +static int +usb_fifo_create(struct usb_cdev_privdata *cpd, + struct usb_cdev_refdata *crd) +{ + struct usb_device *udev = cpd->udev; + struct usb_fifo *f; + struct usb_endpoint *ep; + uint8_t n; + uint8_t is_tx; + uint8_t is_rx; + uint8_t no_null; + uint8_t is_busy; + int e = cpd->ep_addr; + + is_tx = (cpd->fflags & FWRITE) ? 1 : 0; + is_rx = (cpd->fflags & FREAD) ? 1 : 0; + no_null = 1; + is_busy = 0; + + /* Preallocated FIFO */ + if (e < 0) { + DPRINTFN(5, "Preallocated FIFO\n"); + if (is_tx) { + f = udev->fifo[cpd->fifo_index + USB_FIFO_TX]; + if (f == NULL) + return (EINVAL); + crd->txfifo = f; + } + if (is_rx) { + f = udev->fifo[cpd->fifo_index + USB_FIFO_RX]; + if (f == NULL) + return (EINVAL); + crd->rxfifo = f; + } + return (0); + } + + KASSERT(e >= 0 && e <= 15, ("endpoint %d out of range", e)); + + /* search for a free FIFO slot */ + DPRINTFN(5, "Endpoint device, searching for 0x%02x\n", e); + for (n = 0;; n += 2) { + + if (n == USB_FIFO_MAX) { + if (no_null) { + no_null = 0; + n = 0; + } else { + /* end of FIFOs reached */ + DPRINTFN(5, "out of FIFOs\n"); + return (ENOMEM); + } + } + /* Check for TX FIFO */ + if (is_tx) { + f = udev->fifo[n + USB_FIFO_TX]; + if (f != NULL) { + if (f->dev_ep_index != e) { + /* wrong endpoint index */ + continue; + } + if (f->curr_cpd != NULL) { + /* FIFO is opened */ + is_busy = 1; + continue; + } + } else if (no_null) { + continue; + } + } + /* Check for RX FIFO */ + if (is_rx) { + f = udev->fifo[n + USB_FIFO_RX]; + if (f != NULL) { + if (f->dev_ep_index != e) { + /* wrong endpoint index */ + continue; + } + if (f->curr_cpd != NULL) { + /* FIFO is opened */ + is_busy = 1; + continue; + } + } else if (no_null) { + continue; + } + } + break; + } + + if (no_null == 0) { + if (e >= (USB_EP_MAX / 2)) { + /* we don't create any endpoints in this range */ + DPRINTFN(5, "ep out of range\n"); + return (is_busy ? EBUSY : EINVAL); + } + } + + if ((e != 0) && is_busy) { + /* + * Only the default control endpoint is allowed to be + * opened multiple times! + */ + DPRINTFN(5, "busy\n"); + return (EBUSY); + } + + /* Check TX FIFO */ + if (is_tx && + (udev->fifo[n + USB_FIFO_TX] == NULL)) { + ep = usb_dev_get_ep(udev, e, USB_FIFO_TX); + DPRINTFN(5, "dev_get_endpoint(%d, 0x%x)\n", e, USB_FIFO_TX); + if (ep == NULL) { + DPRINTFN(5, "dev_get_endpoint returned NULL\n"); + return (EINVAL); + } + f = usb_fifo_alloc(); + if (f == NULL) { + DPRINTFN(5, "could not alloc tx fifo\n"); + return (ENOMEM); + } + /* update some fields */ + f->fifo_index = n + USB_FIFO_TX; + f->dev_ep_index = e; + f->priv_mtx = &udev->device_mtx; + f->priv_sc0 = ep; + f->methods = &usb_ugen_methods; + f->iface_index = ep->iface_index; + f->udev = udev; + mtx_lock(&usb_ref_lock); + udev->fifo[n + USB_FIFO_TX] = f; + mtx_unlock(&usb_ref_lock); + } + /* Check RX FIFO */ + if (is_rx && + (udev->fifo[n + USB_FIFO_RX] == NULL)) { + + ep = usb_dev_get_ep(udev, e, USB_FIFO_RX); + DPRINTFN(5, "dev_get_endpoint(%d, 0x%x)\n", e, USB_FIFO_RX); + if (ep == NULL) { + DPRINTFN(5, "dev_get_endpoint returned NULL\n"); + return (EINVAL); + } + f = usb_fifo_alloc(); + if (f == NULL) { + DPRINTFN(5, "could not alloc rx fifo\n"); + return (ENOMEM); + } + /* update some fields */ + f->fifo_index = n + USB_FIFO_RX; + f->dev_ep_index = e; + f->priv_mtx = &udev->device_mtx; + f->priv_sc0 = ep; + f->methods = &usb_ugen_methods; + f->iface_index = ep->iface_index; + f->udev = udev; + mtx_lock(&usb_ref_lock); + udev->fifo[n + USB_FIFO_RX] = f; + mtx_unlock(&usb_ref_lock); + } + if (is_tx) { + crd->txfifo = udev->fifo[n + USB_FIFO_TX]; + } + if (is_rx) { + crd->rxfifo = udev->fifo[n + USB_FIFO_RX]; + } + /* fill out fifo index */ + DPRINTFN(5, "fifo index = %d\n", n); + cpd->fifo_index = n; + + /* complete */ + + return (0); +} + +void +usb_fifo_free(struct usb_fifo *f) +{ + uint8_t n; + + if (f == NULL) { + /* be NULL safe */ + return; + } + /* destroy symlink devices, if any */ + for (n = 0; n != 2; n++) { + if (f->symlink[n]) { + usb_free_symlink(f->symlink[n]); + f->symlink[n] = NULL; + } + } + mtx_lock(&usb_ref_lock); + + /* delink ourselves to stop calls from userland */ + if ((f->fifo_index < USB_FIFO_MAX) && + (f->udev != NULL) && + (f->udev->fifo[f->fifo_index] == f)) { + f->udev->fifo[f->fifo_index] = NULL; + } else { + DPRINTFN(0, "USB FIFO %p has not been linked\n", f); + } + + /* decrease refcount */ + f->refcount--; + /* prevent any write flush */ + f->flag_iserror = 1; + /* need to wait until all callers have exited */ + while (f->refcount != 0) { + mtx_unlock(&usb_ref_lock); /* avoid LOR */ + mtx_lock(f->priv_mtx); + /* get I/O thread out of any sleep state */ + if (f->flag_sleeping) { + f->flag_sleeping = 0; + cv_broadcast(&f->cv_io); + } + mtx_unlock(f->priv_mtx); + mtx_lock(&usb_ref_lock); + + /* wait for sync */ + cv_wait(&f->cv_drain, &usb_ref_lock); + } + mtx_unlock(&usb_ref_lock); + + /* take care of closing the device here, if any */ + usb_fifo_close(f, 0); + + cv_destroy(&f->cv_io); + cv_destroy(&f->cv_drain); + + free(f, M_USBDEV); +} + +static struct usb_endpoint * +usb_dev_get_ep(struct usb_device *udev, uint8_t ep_index, uint8_t dir) +{ + struct usb_endpoint *ep; + uint8_t ep_dir; + + if (ep_index == 0) { + ep = &udev->ctrl_ep; + } else { + if (dir == USB_FIFO_RX) { + if (udev->flags.usb_mode == USB_MODE_HOST) { + ep_dir = UE_DIR_IN; + } else { + ep_dir = UE_DIR_OUT; + } + } else { + if (udev->flags.usb_mode == USB_MODE_HOST) { + ep_dir = UE_DIR_OUT; + } else { + ep_dir = UE_DIR_IN; + } + } + ep = usbd_get_ep_by_addr(udev, ep_index | ep_dir); + } + + if (ep == NULL) { + /* if the endpoint does not exist then return */ + return (NULL); + } + if (ep->edesc == NULL) { + /* invalid endpoint */ + return (NULL); + } + return (ep); /* success */ +} + +/*------------------------------------------------------------------------* + * usb_fifo_open + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usb_fifo_open(struct usb_cdev_privdata *cpd, + struct usb_fifo *f, int fflags) +{ + int err; + + if (f == NULL) { + /* no FIFO there */ + DPRINTFN(2, "no FIFO\n"); + return (ENXIO); + } + /* remove FWRITE and FREAD flags */ + fflags &= ~(FWRITE | FREAD); + + /* set correct file flags */ + if ((f->fifo_index & 1) == USB_FIFO_TX) { + fflags |= FWRITE; + } else { + fflags |= FREAD; + } + + /* check if we are already opened */ + /* we don't need any locks when checking this variable */ + if (f->curr_cpd != NULL) { + err = EBUSY; + goto done; + } + + /* reset short flag before open */ + f->flag_short = 0; + + /* call open method */ + err = (f->methods->f_open) (f, fflags); + if (err) { + goto done; + } + mtx_lock(f->priv_mtx); + + /* reset sleep flag */ + f->flag_sleeping = 0; + + /* reset error flag */ + f->flag_iserror = 0; + + /* reset complete flag */ + f->flag_iscomplete = 0; + + /* reset select flag */ + f->flag_isselect = 0; + + /* reset flushing flag */ + f->flag_flushing = 0; + + /* reset ASYNC proc flag */ + f->async_p = NULL; + + mtx_lock(&usb_ref_lock); + /* flag the fifo as opened to prevent others */ + f->curr_cpd = cpd; + mtx_unlock(&usb_ref_lock); + + /* reset queue */ + usb_fifo_reset(f); + + mtx_unlock(f->priv_mtx); +done: + return (err); +} + +/*------------------------------------------------------------------------* + * usb_fifo_reset + *------------------------------------------------------------------------*/ +void +usb_fifo_reset(struct usb_fifo *f) +{ + struct usb_mbuf *m; + + if (f == NULL) { + return; + } + while (1) { + USB_IF_DEQUEUE(&f->used_q, m); + if (m) { + USB_IF_ENQUEUE(&f->free_q, m); + } else { + break; + } + } + /* reset have fragment flag */ + f->flag_have_fragment = 0; +} + +/*------------------------------------------------------------------------* + * usb_fifo_close + *------------------------------------------------------------------------*/ +static void +usb_fifo_close(struct usb_fifo *f, int fflags) +{ + int err; + + /* check if we are not opened */ + if (f->curr_cpd == NULL) { + /* nothing to do - already closed */ + return; + } + mtx_lock(f->priv_mtx); + + /* clear current cdev private data pointer */ + f->curr_cpd = NULL; + + /* check if we are selected */ + if (f->flag_isselect) { + selwakeup(&f->selinfo); + f->flag_isselect = 0; + } + /* check if a thread wants SIGIO */ + if (f->async_p != NULL) { + PROC_LOCK(f->async_p); + kern_psignal(f->async_p, SIGIO); + PROC_UNLOCK(f->async_p); + f->async_p = NULL; + } + /* remove FWRITE and FREAD flags */ + fflags &= ~(FWRITE | FREAD); + + /* flush written data, if any */ + if ((f->fifo_index & 1) == USB_FIFO_TX) { + + if (!f->flag_iserror) { + + /* set flushing flag */ + f->flag_flushing = 1; + + /* get the last packet in */ + if (f->flag_have_fragment) { + struct usb_mbuf *m; + f->flag_have_fragment = 0; + USB_IF_DEQUEUE(&f->free_q, m); + if (m) { + USB_IF_ENQUEUE(&f->used_q, m); + } + } + + /* start write transfer, if not already started */ + (f->methods->f_start_write) (f); + + /* check if flushed already */ + while (f->flag_flushing && + (!f->flag_iserror)) { + /* wait until all data has been written */ + f->flag_sleeping = 1; + err = cv_wait_sig(&f->cv_io, f->priv_mtx); + if (err) { + DPRINTF("signal received\n"); + break; + } + } + } + fflags |= FWRITE; + + /* stop write transfer, if not already stopped */ + (f->methods->f_stop_write) (f); + } else { + fflags |= FREAD; + + /* stop write transfer, if not already stopped */ + (f->methods->f_stop_read) (f); + } + + /* check if we are sleeping */ + if (f->flag_sleeping) { + DPRINTFN(2, "Sleeping at close!\n"); + } + mtx_unlock(f->priv_mtx); + + /* call close method */ + (f->methods->f_close) (f, fflags); + + DPRINTF("closed\n"); +} + +/*------------------------------------------------------------------------* + * usb_open - cdev callback + *------------------------------------------------------------------------*/ +static int +usb_open(struct cdev *dev, int fflags, int devtype, struct thread *td) +{ + struct usb_fs_privdata* pd = (struct usb_fs_privdata*)dev->si_drv1; + struct usb_cdev_refdata refs; + struct usb_cdev_privdata *cpd; + int err, ep; + + DPRINTFN(2, "%s fflags=0x%08x\n", devtoname(dev), fflags); + + KASSERT(fflags & (FREAD|FWRITE), ("invalid open flags")); + if (((fflags & FREAD) && !(pd->mode & FREAD)) || + ((fflags & FWRITE) && !(pd->mode & FWRITE))) { + DPRINTFN(2, "access mode not supported\n"); + return (EPERM); + } + + cpd = malloc(sizeof(*cpd), M_USBDEV, M_WAITOK | M_ZERO); + ep = cpd->ep_addr = pd->ep_addr; + + usb_loc_fill(pd, cpd); + err = usb_ref_device(cpd, &refs, 1); + if (err) { + DPRINTFN(2, "cannot ref device\n"); + free(cpd, M_USBDEV); + return (ENXIO); + } + cpd->fflags = fflags; /* access mode for open lifetime */ + + /* create FIFOs, if any */ + err = usb_fifo_create(cpd, &refs); + /* check for error */ + if (err) { + DPRINTFN(2, "cannot create fifo\n"); + usb_unref_device(cpd, &refs); + free(cpd, M_USBDEV); + return (err); + } + if (fflags & FREAD) { + err = usb_fifo_open(cpd, refs.rxfifo, fflags); + if (err) { + DPRINTFN(2, "read open failed\n"); + usb_unref_device(cpd, &refs); + free(cpd, M_USBDEV); + return (err); + } + } + if (fflags & FWRITE) { + err = usb_fifo_open(cpd, refs.txfifo, fflags); + if (err) { + DPRINTFN(2, "write open failed\n"); + if (fflags & FREAD) { + usb_fifo_close(refs.rxfifo, fflags); + } + usb_unref_device(cpd, &refs); + free(cpd, M_USBDEV); + return (err); + } + } + usb_unref_device(cpd, &refs); + devfs_set_cdevpriv(cpd, usb_close); + + return (0); +} + +/*------------------------------------------------------------------------* + * usb_close - cdev callback + *------------------------------------------------------------------------*/ +static void +usb_close(void *arg) +{ + struct usb_cdev_refdata refs; + struct usb_cdev_privdata *cpd = arg; + int err; + + DPRINTFN(2, "cpd=%p\n", cpd); + + err = usb_ref_device(cpd, &refs, 0); + if (err) + goto done; + + /* + * If this function is not called directly from the root HUB + * thread, there is usually a need to lock the enumeration + * lock. Check this. + */ + if (!usbd_enum_is_locked(cpd->udev)) { + + DPRINTFN(2, "Locking enumeration\n"); + + /* reference device */ + err = usb_usb_ref_device(cpd, &refs); + if (err) + goto done; + } + if (cpd->fflags & FREAD) { + usb_fifo_close(refs.rxfifo, cpd->fflags); + } + if (cpd->fflags & FWRITE) { + usb_fifo_close(refs.txfifo, cpd->fflags); + } + usb_unref_device(cpd, &refs); +done: + free(cpd, M_USBDEV); +} + +static void +usb_dev_init(void *arg) +{ + mtx_init(&usb_ref_lock, "USB ref mutex", NULL, MTX_DEF); + sx_init(&usb_sym_lock, "USB sym mutex"); + TAILQ_INIT(&usb_sym_head); + + /* check the UGEN methods */ + usb_fifo_check_methods(&usb_ugen_methods); +} + +SYSINIT(usb_dev_init, SI_SUB_KLD, SI_ORDER_FIRST, usb_dev_init, NULL); + +static void +usb_dev_init_post(void *arg) +{ + /* + * Create /dev/usb - this is needed for usbconfig(8), which + * needs a well-known device name to access. + */ + usb_dev = make_dev(&usb_static_devsw, 0, UID_ROOT, GID_OPERATOR, + 0644, USB_DEVICE_NAME); + if (usb_dev == NULL) { + DPRINTFN(0, "Could not create usb bus device\n"); + } +} + +SYSINIT(usb_dev_init_post, SI_SUB_KICK_SCHEDULER, SI_ORDER_FIRST, usb_dev_init_post, NULL); + +static void +usb_dev_uninit(void *arg) +{ + if (usb_dev != NULL) { + destroy_dev(usb_dev); + usb_dev = NULL; + } + mtx_destroy(&usb_ref_lock); + sx_destroy(&usb_sym_lock); +} + +SYSUNINIT(usb_dev_uninit, SI_SUB_KICK_SCHEDULER, SI_ORDER_ANY, usb_dev_uninit, NULL); + +static int +usb_ioctl_f_sub(struct usb_fifo *f, u_long cmd, void *addr, + struct thread *td) +{ + int error = 0; + + switch (cmd) { + case FIODTYPE: + *(int *)addr = 0; /* character device */ + break; + + case FIONBIO: + /* handled by upper FS layer */ + break; + + case FIOASYNC: + if (*(int *)addr) { + if (f->async_p != NULL) { + error = EBUSY; + break; + } + f->async_p = USB_TD_GET_PROC(td); + } else { + f->async_p = NULL; + } + break; + + /* XXX this is not the most general solution */ + case TIOCSPGRP: + if (f->async_p == NULL) { + error = EINVAL; + break; + } + if (*(int *)addr != USB_PROC_GET_GID(f->async_p)) { + error = EPERM; + break; + } + break; + default: + return (ENOIOCTL); + } + DPRINTFN(3, "cmd 0x%lx = %d\n", cmd, error); + return (error); +} + +/*------------------------------------------------------------------------* + * usb_ioctl - cdev callback + *------------------------------------------------------------------------*/ +static int +usb_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int fflag, struct thread* td) +{ + struct usb_cdev_refdata refs; + struct usb_cdev_privdata* cpd; + struct usb_fifo *f; + int fflags; + int err; + + DPRINTFN(2, "cmd=0x%lx\n", cmd); + + err = devfs_get_cdevpriv((void **)&cpd); + if (err != 0) + return (err); + + /* + * Performance optimisation: We try to check for IOCTL's that + * don't need the USB reference first. Then we grab the USB + * reference if we need it! + */ + err = usb_ref_device(cpd, &refs, 0 /* no uref */ ); + if (err) + return (ENXIO); + + fflags = cpd->fflags; + + f = NULL; /* set default value */ + err = ENOIOCTL; /* set default value */ + + if (fflags & FWRITE) { + f = refs.txfifo; + err = usb_ioctl_f_sub(f, cmd, addr, td); + } + if (fflags & FREAD) { + f = refs.rxfifo; + err = usb_ioctl_f_sub(f, cmd, addr, td); + } + KASSERT(f != NULL, ("fifo not found")); + if (err != ENOIOCTL) + goto done; + + err = (f->methods->f_ioctl) (f, cmd, addr, fflags); + + DPRINTFN(2, "f_ioctl cmd 0x%lx = %d\n", cmd, err); + + if (err != ENOIOCTL) + goto done; + + if (usb_usb_ref_device(cpd, &refs)) { + err = ENXIO; + goto done; + } + + err = (f->methods->f_ioctl_post) (f, cmd, addr, fflags); + + DPRINTFN(2, "f_ioctl_post cmd 0x%lx = %d\n", cmd, err); + + if (err == ENOIOCTL) + err = ENOTTY; + + if (err) + goto done; + + /* Wait for re-enumeration, if any */ + + while (f->udev->re_enumerate_wait != 0) { + + usb_unref_device(cpd, &refs); + + usb_pause_mtx(NULL, hz / 128); + + if (usb_ref_device(cpd, &refs, 1 /* need uref */)) { + err = ENXIO; + goto done; + } + } + +done: + usb_unref_device(cpd, &refs); + return (err); +} + +/* ARGSUSED */ +static int +usb_poll(struct cdev* dev, int events, struct thread* td) +{ + struct usb_cdev_refdata refs; + struct usb_cdev_privdata* cpd; + struct usb_fifo *f; + struct usb_mbuf *m; + int fflags, revents; + + if (devfs_get_cdevpriv((void **)&cpd) != 0 || + usb_ref_device(cpd, &refs, 0) != 0) + return (events & + (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM)); + + fflags = cpd->fflags; + + /* Figure out who needs service */ + revents = 0; + if ((events & (POLLOUT | POLLWRNORM)) && + (fflags & FWRITE)) { + + f = refs.txfifo; + + mtx_lock(f->priv_mtx); + + if (!refs.is_usbfs) { + if (f->flag_iserror) { + /* we got an error */ + m = (void *)1; + } else { + if (f->queue_data == NULL) { + /* + * start write transfer, if not + * already started + */ + (f->methods->f_start_write) (f); + } + /* check if any packets are available */ + USB_IF_POLL(&f->free_q, m); + } + } else { + if (f->flag_iscomplete) { + m = (void *)1; + } else { + m = NULL; + } + } + + if (m) { + revents |= events & (POLLOUT | POLLWRNORM); + } else { + f->flag_isselect = 1; + selrecord(td, &f->selinfo); + } + + mtx_unlock(f->priv_mtx); + } + if ((events & (POLLIN | POLLRDNORM)) && + (fflags & FREAD)) { + + f = refs.rxfifo; + + mtx_lock(f->priv_mtx); + + if (!refs.is_usbfs) { + if (f->flag_iserror) { + /* we have and error */ + m = (void *)1; + } else { + if (f->queue_data == NULL) { + /* + * start read transfer, if not + * already started + */ + (f->methods->f_start_read) (f); + } + /* check if any packets are available */ + USB_IF_POLL(&f->used_q, m); + } + } else { + if (f->flag_iscomplete) { + m = (void *)1; + } else { + m = NULL; + } + } + + if (m) { + revents |= events & (POLLIN | POLLRDNORM); + } else { + f->flag_isselect = 1; + selrecord(td, &f->selinfo); + + if (!refs.is_usbfs) { + /* start reading data */ + (f->methods->f_start_read) (f); + } + } + + mtx_unlock(f->priv_mtx); + } + usb_unref_device(cpd, &refs); + return (revents); +} + +static int +usb_read(struct cdev *dev, struct uio *uio, int ioflag) +{ + struct usb_cdev_refdata refs; + struct usb_cdev_privdata* cpd; + struct usb_fifo *f; + struct usb_mbuf *m; + int fflags; + int resid; + int io_len; + int err; + uint8_t tr_data = 0; + + err = devfs_get_cdevpriv((void **)&cpd); + if (err != 0) + return (err); + + err = usb_ref_device(cpd, &refs, 0 /* no uref */ ); + if (err) { + return (ENXIO); + } + fflags = cpd->fflags; + + f = refs.rxfifo; + if (f == NULL) { + /* should not happen */ + usb_unref_device(cpd, &refs); + return (EPERM); + } + + resid = uio->uio_resid; + + mtx_lock(f->priv_mtx); + + /* check for permanent read error */ + if (f->flag_iserror) { + err = EIO; + goto done; + } + /* check if USB-FS interface is active */ + if (refs.is_usbfs) { + /* + * The queue is used for events that should be + * retrieved using the "USB_FS_COMPLETE" ioctl. + */ + err = EINVAL; + goto done; + } + while (uio->uio_resid > 0) { + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m == NULL) { + + /* start read transfer, if not already started */ + + (f->methods->f_start_read) (f); + + if (ioflag & IO_NDELAY) { + if (tr_data) { + /* return length before error */ + break; + } + err = EWOULDBLOCK; + break; + } + DPRINTF("sleeping\n"); + + err = usb_fifo_wait(f); + if (err) { + break; + } + continue; + } + if (f->methods->f_filter_read) { + /* + * Sometimes it is convenient to process data at the + * expense of a userland process instead of a kernel + * process. + */ + (f->methods->f_filter_read) (f, m); + } + tr_data = 1; + + io_len = MIN(m->cur_data_len, uio->uio_resid); + + DPRINTFN(2, "transfer %d bytes from %p\n", + io_len, m->cur_data_ptr); + + err = usb_fifo_uiomove(f, + m->cur_data_ptr, io_len, uio); + + m->cur_data_len -= io_len; + m->cur_data_ptr += io_len; + + if (m->cur_data_len == 0) { + + uint8_t last_packet; + + last_packet = m->last_packet; + + USB_IF_ENQUEUE(&f->free_q, m); + + if (last_packet) { + /* keep framing */ + break; + } + } else { + USB_IF_PREPEND(&f->used_q, m); + } + + if (err) { + break; + } + } +done: + mtx_unlock(f->priv_mtx); + + usb_unref_device(cpd, &refs); + + return (err); +} + +static int +usb_write(struct cdev *dev, struct uio *uio, int ioflag) +{ + struct usb_cdev_refdata refs; + struct usb_cdev_privdata* cpd; + struct usb_fifo *f; + struct usb_mbuf *m; + uint8_t *pdata; + int fflags; + int resid; + int io_len; + int err; + uint8_t tr_data = 0; + + DPRINTFN(2, "\n"); + + err = devfs_get_cdevpriv((void **)&cpd); + if (err != 0) + return (err); + + err = usb_ref_device(cpd, &refs, 0 /* no uref */ ); + if (err) { + return (ENXIO); + } + fflags = cpd->fflags; + + f = refs.txfifo; + if (f == NULL) { + /* should not happen */ + usb_unref_device(cpd, &refs); + return (EPERM); + } + resid = uio->uio_resid; + + mtx_lock(f->priv_mtx); + + /* check for permanent write error */ + if (f->flag_iserror) { + err = EIO; + goto done; + } + /* check if USB-FS interface is active */ + if (refs.is_usbfs) { + /* + * The queue is used for events that should be + * retrieved using the "USB_FS_COMPLETE" ioctl. + */ + err = EINVAL; + goto done; + } + if (f->queue_data == NULL) { + /* start write transfer, if not already started */ + (f->methods->f_start_write) (f); + } + /* we allow writing zero length data */ + do { + USB_IF_DEQUEUE(&f->free_q, m); + + if (m == NULL) { + + if (ioflag & IO_NDELAY) { + if (tr_data) { + /* return length before error */ + break; + } + err = EWOULDBLOCK; + break; + } + DPRINTF("sleeping\n"); + + err = usb_fifo_wait(f); + if (err) { + break; + } + continue; + } + tr_data = 1; + + if (f->flag_have_fragment == 0) { + USB_MBUF_RESET(m); + io_len = m->cur_data_len; + pdata = m->cur_data_ptr; + if (io_len > uio->uio_resid) + io_len = uio->uio_resid; + m->cur_data_len = io_len; + } else { + io_len = m->max_data_len - m->cur_data_len; + pdata = m->cur_data_ptr + m->cur_data_len; + if (io_len > uio->uio_resid) + io_len = uio->uio_resid; + m->cur_data_len += io_len; + } + + DPRINTFN(2, "transfer %d bytes to %p\n", + io_len, pdata); + + err = usb_fifo_uiomove(f, pdata, io_len, uio); + + if (err) { + f->flag_have_fragment = 0; + USB_IF_ENQUEUE(&f->free_q, m); + break; + } + + /* check if the buffer is ready to be transmitted */ + + if ((f->flag_write_defrag == 0) || + (m->cur_data_len == m->max_data_len)) { + f->flag_have_fragment = 0; + + /* + * Check for write filter: + * + * Sometimes it is convenient to process data + * at the expense of a userland process + * instead of a kernel process. + */ + if (f->methods->f_filter_write) { + (f->methods->f_filter_write) (f, m); + } + + /* Put USB mbuf in the used queue */ + USB_IF_ENQUEUE(&f->used_q, m); + + /* Start writing data, if not already started */ + (f->methods->f_start_write) (f); + } else { + /* Wait for more data or close */ + f->flag_have_fragment = 1; + USB_IF_PREPEND(&f->free_q, m); + } + + } while (uio->uio_resid > 0); +done: + mtx_unlock(f->priv_mtx); + + usb_unref_device(cpd, &refs); + + return (err); +} + +int +usb_static_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, + struct thread *td) +{ + union { + struct usb_read_dir *urd; + void* data; + } u; + int err; + + u.data = data; + switch (cmd) { + case USB_READ_DIR: + err = usb_read_symlink(u.urd->urd_data, + u.urd->urd_startentry, u.urd->urd_maxlen); + break; + case USB_DEV_QUIRK_GET: + case USB_QUIRK_NAME_GET: + case USB_DEV_QUIRK_ADD: + case USB_DEV_QUIRK_REMOVE: + err = usb_quirk_ioctl_p(cmd, data, fflag, td); + break; + case USB_GET_TEMPLATE: + *(int *)data = usb_template; + err = 0; + break; + case USB_SET_TEMPLATE: + err = priv_check(curthread, PRIV_DRIVER); + if (err) + break; + usb_template = *(int *)data; + break; + default: + err = ENOTTY; + break; + } + return (err); +} + +static int +usb_fifo_uiomove(struct usb_fifo *f, void *cp, + int n, struct uio *uio) +{ + int error; + + mtx_unlock(f->priv_mtx); + + /* + * "uiomove()" can sleep so one needs to make a wrapper, + * exiting the mutex and checking things: + */ + error = uiomove(cp, n, uio); + + mtx_lock(f->priv_mtx); + + return (error); +} + +int +usb_fifo_wait(struct usb_fifo *f) +{ + int err; + + mtx_assert(f->priv_mtx, MA_OWNED); + + if (f->flag_iserror) { + /* we are gone */ + return (EIO); + } + f->flag_sleeping = 1; + + err = cv_wait_sig(&f->cv_io, f->priv_mtx); + + if (f->flag_iserror) { + /* we are gone */ + err = EIO; + } + return (err); +} + +void +usb_fifo_signal(struct usb_fifo *f) +{ + if (f->flag_sleeping) { + f->flag_sleeping = 0; + cv_broadcast(&f->cv_io); + } +} + +void +usb_fifo_wakeup(struct usb_fifo *f) +{ + usb_fifo_signal(f); + + if (f->flag_isselect) { + selwakeup(&f->selinfo); + f->flag_isselect = 0; + } + if (f->async_p != NULL) { + PROC_LOCK(f->async_p); + kern_psignal(f->async_p, SIGIO); + PROC_UNLOCK(f->async_p); + } +} + +static int +usb_fifo_dummy_open(struct usb_fifo *fifo, int fflags) +{ + return (0); +} + +static void +usb_fifo_dummy_close(struct usb_fifo *fifo, int fflags) +{ + return; +} + +static int +usb_fifo_dummy_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags) +{ + return (ENOIOCTL); +} + +static void +usb_fifo_dummy_cmd(struct usb_fifo *fifo) +{ + fifo->flag_flushing = 0; /* not flushing */ +} + +static void +usb_fifo_check_methods(struct usb_fifo_methods *pm) +{ + /* check that all callback functions are OK */ + + if (pm->f_open == NULL) + pm->f_open = &usb_fifo_dummy_open; + + if (pm->f_close == NULL) + pm->f_close = &usb_fifo_dummy_close; + + if (pm->f_ioctl == NULL) + pm->f_ioctl = &usb_fifo_dummy_ioctl; + + if (pm->f_ioctl_post == NULL) + pm->f_ioctl_post = &usb_fifo_dummy_ioctl; + + if (pm->f_start_read == NULL) + pm->f_start_read = &usb_fifo_dummy_cmd; + + if (pm->f_stop_read == NULL) + pm->f_stop_read = &usb_fifo_dummy_cmd; + + if (pm->f_start_write == NULL) + pm->f_start_write = &usb_fifo_dummy_cmd; + + if (pm->f_stop_write == NULL) + pm->f_stop_write = &usb_fifo_dummy_cmd; +} + +/*------------------------------------------------------------------------* + * usb_fifo_attach + * + * The following function will create a duplex FIFO. + * + * Return values: + * 0: Success. + * Else: Failure. + *------------------------------------------------------------------------*/ +int +usb_fifo_attach(struct usb_device *udev, void *priv_sc, + struct mtx *priv_mtx, struct usb_fifo_methods *pm, + struct usb_fifo_sc *f_sc, uint16_t unit, uint16_t subunit, + uint8_t iface_index, uid_t uid, gid_t gid, int mode) +{ + struct usb_fifo *f_tx; + struct usb_fifo *f_rx; + char devname[32]; + uint8_t n; + + f_sc->fp[USB_FIFO_TX] = NULL; + f_sc->fp[USB_FIFO_RX] = NULL; + + if (pm == NULL) + return (EINVAL); + + /* check the methods */ + usb_fifo_check_methods(pm); + + if (priv_mtx == NULL) + priv_mtx = &Giant; + + /* search for a free FIFO slot */ + for (n = 0;; n += 2) { + + if (n == USB_FIFO_MAX) { + /* end of FIFOs reached */ + return (ENOMEM); + } + /* Check for TX FIFO */ + if (udev->fifo[n + USB_FIFO_TX] != NULL) { + continue; + } + /* Check for RX FIFO */ + if (udev->fifo[n + USB_FIFO_RX] != NULL) { + continue; + } + break; + } + + f_tx = usb_fifo_alloc(); + f_rx = usb_fifo_alloc(); + + if ((f_tx == NULL) || (f_rx == NULL)) { + usb_fifo_free(f_tx); + usb_fifo_free(f_rx); + return (ENOMEM); + } + /* initialise FIFO structures */ + + f_tx->fifo_index = n + USB_FIFO_TX; + f_tx->dev_ep_index = -1; + f_tx->priv_mtx = priv_mtx; + f_tx->priv_sc0 = priv_sc; + f_tx->methods = pm; + f_tx->iface_index = iface_index; + f_tx->udev = udev; + + f_rx->fifo_index = n + USB_FIFO_RX; + f_rx->dev_ep_index = -1; + f_rx->priv_mtx = priv_mtx; + f_rx->priv_sc0 = priv_sc; + f_rx->methods = pm; + f_rx->iface_index = iface_index; + f_rx->udev = udev; + + f_sc->fp[USB_FIFO_TX] = f_tx; + f_sc->fp[USB_FIFO_RX] = f_rx; + + mtx_lock(&usb_ref_lock); + udev->fifo[f_tx->fifo_index] = f_tx; + udev->fifo[f_rx->fifo_index] = f_rx; + mtx_unlock(&usb_ref_lock); + + for (n = 0; n != 4; n++) { + + if (pm->basename[n] == NULL) { + continue; + } + if (subunit == 0xFFFF) { + if (snprintf(devname, sizeof(devname), + "%s%u%s", pm->basename[n], + unit, pm->postfix[n] ? + pm->postfix[n] : "")) { + /* ignore */ + } + } else { + if (snprintf(devname, sizeof(devname), + "%s%u.%u%s", pm->basename[n], + unit, subunit, pm->postfix[n] ? + pm->postfix[n] : "")) { + /* ignore */ + } + } + + /* + * Distribute the symbolic links into two FIFO structures: + */ + if (n & 1) { + f_rx->symlink[n / 2] = + usb_alloc_symlink(devname); + } else { + f_tx->symlink[n / 2] = + usb_alloc_symlink(devname); + } + + /* Create the device */ + f_sc->dev = usb_make_dev(udev, devname, -1, + f_tx->fifo_index & f_rx->fifo_index, + FREAD|FWRITE, uid, gid, mode); + } + + DPRINTFN(2, "attached %p/%p\n", f_tx, f_rx); + return (0); +} + +/*------------------------------------------------------------------------* + * usb_fifo_alloc_buffer + * + * Return values: + * 0: Success + * Else failure + *------------------------------------------------------------------------*/ +int +usb_fifo_alloc_buffer(struct usb_fifo *f, usb_size_t bufsize, + uint16_t nbuf) +{ + usb_fifo_free_buffer(f); + + /* allocate an endpoint */ + f->free_q.ifq_maxlen = nbuf; + f->used_q.ifq_maxlen = nbuf; + + f->queue_data = usb_alloc_mbufs( + M_USBDEV, &f->free_q, bufsize, nbuf); + + if ((f->queue_data == NULL) && bufsize && nbuf) { + return (ENOMEM); + } + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb_fifo_free_buffer + * + * This function will free the buffers associated with a FIFO. This + * function can be called multiple times in a row. + *------------------------------------------------------------------------*/ +void +usb_fifo_free_buffer(struct usb_fifo *f) +{ + if (f->queue_data) { + /* free old buffer */ + free(f->queue_data, M_USBDEV); + f->queue_data = NULL; + } + /* reset queues */ + + memset(&f->free_q, 0, sizeof(f->free_q)); + memset(&f->used_q, 0, sizeof(f->used_q)); +} + +void +usb_fifo_detach(struct usb_fifo_sc *f_sc) +{ + if (f_sc == NULL) { + return; + } + usb_fifo_free(f_sc->fp[USB_FIFO_TX]); + usb_fifo_free(f_sc->fp[USB_FIFO_RX]); + + f_sc->fp[USB_FIFO_TX] = NULL; + f_sc->fp[USB_FIFO_RX] = NULL; + + usb_destroy_dev(f_sc->dev); + + f_sc->dev = NULL; + + DPRINTFN(2, "detached %p\n", f_sc); +} + +usb_size_t +usb_fifo_put_bytes_max(struct usb_fifo *f) +{ + struct usb_mbuf *m; + usb_size_t len; + + USB_IF_POLL(&f->free_q, m); + + if (m) { + len = m->max_data_len; + } else { + len = 0; + } + return (len); +} + +/*------------------------------------------------------------------------* + * usb_fifo_put_data + * + * what: + * 0 - normal operation + * 1 - set last packet flag to enforce framing + *------------------------------------------------------------------------*/ +void +usb_fifo_put_data(struct usb_fifo *f, struct usb_page_cache *pc, + usb_frlength_t offset, usb_frlength_t len, uint8_t what) +{ + struct usb_mbuf *m; + usb_frlength_t io_len; + + while (len || (what == 1)) { + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m) { + USB_MBUF_RESET(m); + + io_len = MIN(len, m->cur_data_len); + + usbd_copy_out(pc, offset, m->cur_data_ptr, io_len); + + m->cur_data_len = io_len; + offset += io_len; + len -= io_len; + + if ((len == 0) && (what == 1)) { + m->last_packet = 1; + } + USB_IF_ENQUEUE(&f->used_q, m); + + usb_fifo_wakeup(f); + + if ((len == 0) || (what == 1)) { + break; + } + } else { + break; + } + } +} + +void +usb_fifo_put_data_linear(struct usb_fifo *f, void *ptr, + usb_size_t len, uint8_t what) +{ + struct usb_mbuf *m; + usb_size_t io_len; + + while (len || (what == 1)) { + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m) { + USB_MBUF_RESET(m); + + io_len = MIN(len, m->cur_data_len); + + memcpy(m->cur_data_ptr, ptr, io_len); + + m->cur_data_len = io_len; + ptr = USB_ADD_BYTES(ptr, io_len); + len -= io_len; + + if ((len == 0) && (what == 1)) { + m->last_packet = 1; + } + USB_IF_ENQUEUE(&f->used_q, m); + + usb_fifo_wakeup(f); + + if ((len == 0) || (what == 1)) { + break; + } + } else { + break; + } + } +} + +uint8_t +usb_fifo_put_data_buffer(struct usb_fifo *f, void *ptr, usb_size_t len) +{ + struct usb_mbuf *m; + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m) { + m->cur_data_len = len; + m->cur_data_ptr = ptr; + USB_IF_ENQUEUE(&f->used_q, m); + usb_fifo_wakeup(f); + return (1); + } + return (0); +} + +void +usb_fifo_put_data_error(struct usb_fifo *f) +{ + f->flag_iserror = 1; + usb_fifo_wakeup(f); +} + +/*------------------------------------------------------------------------* + * usb_fifo_get_data + * + * what: + * 0 - normal operation + * 1 - only get one "usb_mbuf" + * + * returns: + * 0 - no more data + * 1 - data in buffer + *------------------------------------------------------------------------*/ +uint8_t +usb_fifo_get_data(struct usb_fifo *f, struct usb_page_cache *pc, + usb_frlength_t offset, usb_frlength_t len, usb_frlength_t *actlen, + uint8_t what) +{ + struct usb_mbuf *m; + usb_frlength_t io_len; + uint8_t tr_data = 0; + + actlen[0] = 0; + + while (1) { + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + + tr_data = 1; + + io_len = MIN(len, m->cur_data_len); + + usbd_copy_in(pc, offset, m->cur_data_ptr, io_len); + + len -= io_len; + offset += io_len; + actlen[0] += io_len; + m->cur_data_ptr += io_len; + m->cur_data_len -= io_len; + + if ((m->cur_data_len == 0) || (what == 1)) { + USB_IF_ENQUEUE(&f->free_q, m); + + usb_fifo_wakeup(f); + + if (what == 1) { + break; + } + } else { + USB_IF_PREPEND(&f->used_q, m); + } + } else { + + if (tr_data) { + /* wait for data to be written out */ + break; + } + if (f->flag_flushing) { + /* check if we should send a short packet */ + if (f->flag_short != 0) { + f->flag_short = 0; + tr_data = 1; + break; + } + /* flushing complete */ + f->flag_flushing = 0; + usb_fifo_wakeup(f); + } + break; + } + if (len == 0) { + break; + } + } + return (tr_data); +} + +uint8_t +usb_fifo_get_data_linear(struct usb_fifo *f, void *ptr, + usb_size_t len, usb_size_t *actlen, uint8_t what) +{ + struct usb_mbuf *m; + usb_size_t io_len; + uint8_t tr_data = 0; + + actlen[0] = 0; + + while (1) { + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + + tr_data = 1; + + io_len = MIN(len, m->cur_data_len); + + memcpy(ptr, m->cur_data_ptr, io_len); + + len -= io_len; + ptr = USB_ADD_BYTES(ptr, io_len); + actlen[0] += io_len; + m->cur_data_ptr += io_len; + m->cur_data_len -= io_len; + + if ((m->cur_data_len == 0) || (what == 1)) { + USB_IF_ENQUEUE(&f->free_q, m); + + usb_fifo_wakeup(f); + + if (what == 1) { + break; + } + } else { + USB_IF_PREPEND(&f->used_q, m); + } + } else { + + if (tr_data) { + /* wait for data to be written out */ + break; + } + if (f->flag_flushing) { + /* check if we should send a short packet */ + if (f->flag_short != 0) { + f->flag_short = 0; + tr_data = 1; + break; + } + /* flushing complete */ + f->flag_flushing = 0; + usb_fifo_wakeup(f); + } + break; + } + if (len == 0) { + break; + } + } + return (tr_data); +} + +uint8_t +usb_fifo_get_data_buffer(struct usb_fifo *f, void **pptr, usb_size_t *plen) +{ + struct usb_mbuf *m; + + USB_IF_POLL(&f->used_q, m); + + if (m) { + *plen = m->cur_data_len; + *pptr = m->cur_data_ptr; + + return (1); + } + return (0); +} + +void +usb_fifo_get_data_error(struct usb_fifo *f) +{ + f->flag_iserror = 1; + usb_fifo_wakeup(f); +} + +/*------------------------------------------------------------------------* + * usb_alloc_symlink + * + * Return values: + * NULL: Failure + * Else: Pointer to symlink entry + *------------------------------------------------------------------------*/ +struct usb_symlink * +usb_alloc_symlink(const char *target) +{ + struct usb_symlink *ps; + + ps = malloc(sizeof(*ps), M_USBDEV, M_WAITOK); + if (ps == NULL) { + return (ps); + } + /* XXX no longer needed */ + strlcpy(ps->src_path, target, sizeof(ps->src_path)); + ps->src_len = strlen(ps->src_path); + strlcpy(ps->dst_path, target, sizeof(ps->dst_path)); + ps->dst_len = strlen(ps->dst_path); + + sx_xlock(&usb_sym_lock); + TAILQ_INSERT_TAIL(&usb_sym_head, ps, sym_entry); + sx_unlock(&usb_sym_lock); + return (ps); +} + +/*------------------------------------------------------------------------* + * usb_free_symlink + *------------------------------------------------------------------------*/ +void +usb_free_symlink(struct usb_symlink *ps) +{ + if (ps == NULL) { + return; + } + sx_xlock(&usb_sym_lock); + TAILQ_REMOVE(&usb_sym_head, ps, sym_entry); + sx_unlock(&usb_sym_lock); + + free(ps, M_USBDEV); +} + +/*------------------------------------------------------------------------* + * usb_read_symlink + * + * Return value: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +int +usb_read_symlink(uint8_t *user_ptr, uint32_t startentry, uint32_t user_len) +{ + struct usb_symlink *ps; + uint32_t temp; + uint32_t delta = 0; + uint8_t len; + int error = 0; + + sx_xlock(&usb_sym_lock); + + TAILQ_FOREACH(ps, &usb_sym_head, sym_entry) { + + /* + * Compute total length of source and destination symlink + * strings pluss one length byte and two NUL bytes: + */ + temp = ps->src_len + ps->dst_len + 3; + + if (temp > 255) { + /* + * Skip entry because this length cannot fit + * into one byte: + */ + continue; + } + if (startentry != 0) { + /* decrement read offset */ + startentry--; + continue; + } + if (temp > user_len) { + /* out of buffer space */ + break; + } + len = temp; + + /* copy out total length */ + + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + if (error) { + break; + } + delta += 1; + + /* copy out source string */ + + error = copyout(ps->src_path, + USB_ADD_BYTES(user_ptr, delta), ps->src_len); + if (error) { + break; + } + len = 0; + delta += ps->src_len; + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + if (error) { + break; + } + delta += 1; + + /* copy out destination string */ + + error = copyout(ps->dst_path, + USB_ADD_BYTES(user_ptr, delta), ps->dst_len); + if (error) { + break; + } + len = 0; + delta += ps->dst_len; + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + if (error) { + break; + } + delta += 1; + + user_len -= temp; + } + + /* a zero length entry indicates the end */ + + if ((user_len != 0) && (error == 0)) { + + len = 0; + + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + } + sx_unlock(&usb_sym_lock); + return (error); +} + +void +usb_fifo_set_close_zlp(struct usb_fifo *f, uint8_t onoff) +{ + if (f == NULL) + return; + + /* send a Zero Length Packet, ZLP, before close */ + f->flag_short = onoff; +} + +void +usb_fifo_set_write_defrag(struct usb_fifo *f, uint8_t onoff) +{ + if (f == NULL) + return; + + /* defrag written data */ + f->flag_write_defrag = onoff; + /* reset defrag state */ + f->flag_have_fragment = 0; +} + +void * +usb_fifo_softc(struct usb_fifo *f) +{ + return (f->priv_sc0); +} +#endif /* USB_HAVE_UGEN */ diff --git a/sys/bus/u4b/usb_dev.h b/sys/bus/u4b/usb_dev.h new file mode 100644 index 0000000000..aa9197f512 --- /dev/null +++ b/sys/bus/u4b/usb_dev.h @@ -0,0 +1,154 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_DEV_H_ +#define _USB_DEV_H_ + +#include +#include +#include +#include +#include + +struct usb_fifo; +struct usb_mbuf; + +struct usb_symlink { + TAILQ_ENTRY(usb_symlink) sym_entry; + char src_path[32]; /* Source path - including terminating + * zero */ + char dst_path[32]; /* Destination path - including + * terminating zero */ + uint8_t src_len; /* String length */ + uint8_t dst_len; /* String length */ +}; + +/* + * Private per-device information. + */ +struct usb_cdev_privdata { + struct usb_bus *bus; + struct usb_device *udev; + struct usb_interface *iface; + int bus_index; /* bus index */ + int dev_index; /* device index */ + int ep_addr; /* endpoint address */ + int fflags; + uint8_t fifo_index; /* FIFO index */ +}; + +/* + * The following structure defines a minimum re-implementation of the + * ifqueue structure in the kernel. + */ +struct usb_ifqueue { + struct usb_mbuf *ifq_head; + struct usb_mbuf *ifq_tail; + + usb_size_t ifq_len; + usb_size_t ifq_maxlen; +}; + +/* + * Private per-device and per-thread reference information + */ +struct usb_cdev_refdata { + struct usb_fifo *rxfifo; + struct usb_fifo *txfifo; + uint8_t is_read; /* location has read access */ + uint8_t is_write; /* location has write access */ + uint8_t is_uref; /* USB refcount decr. needed */ + uint8_t is_usbfs; /* USB-FS is active */ +}; + +struct usb_fs_privdata { + int bus_index; + int dev_index; + int ep_addr; + int mode; + int fifo_index; + struct cdev *cdev; + + LIST_ENTRY(usb_fs_privdata) pd_next; +}; + +/* + * Most of the fields in the "usb_fifo" structure are used by the + * generic USB access layer. + */ +struct usb_fifo { + struct usb_ifqueue free_q; + struct usb_ifqueue used_q; + struct selinfo selinfo; + struct cv cv_io; + struct cv cv_drain; + struct usb_fifo_methods *methods; + struct usb_symlink *symlink[2];/* our symlinks */ + struct proc *async_p; /* process that wants SIGIO */ + struct usb_fs_endpoint *fs_ep_ptr; + struct usb_device *udev; + struct usb_xfer *xfer[2]; + struct usb_xfer **fs_xfer; + struct mtx *priv_mtx; /* client data */ + /* set if FIFO is opened by a FILE: */ + struct usb_cdev_privdata *curr_cpd; + void *priv_sc0; /* client data */ + void *priv_sc1; /* client data */ + void *queue_data; + usb_timeout_t timeout; /* timeout in milliseconds */ + usb_frlength_t bufsize; /* BULK and INTERRUPT buffer size */ + usb_frcount_t nframes; /* for isochronous mode */ + uint16_t dev_ep_index; /* our device endpoint index */ + uint8_t flag_sleeping; /* set if FIFO is sleeping */ + uint8_t flag_iscomplete; /* set if a USB transfer is complete */ + uint8_t flag_iserror; /* set if FIFO error happened */ + uint8_t flag_isselect; /* set if FIFO is selected */ + uint8_t flag_flushing; /* set if FIFO is flushing data */ + uint8_t flag_short; /* set if short_ok or force_short + * transfer flags should be set */ + uint8_t flag_stall; /* set if clear stall should be run */ + uint8_t flag_write_defrag; /* set to defrag written data */ + uint8_t flag_have_fragment; /* set if defragging */ + uint8_t iface_index; /* set to the interface we belong to */ + uint8_t fifo_index; /* set to the FIFO index in "struct + * usb_device" */ + uint8_t fs_ep_max; + uint8_t fifo_zlp; /* zero length packet count */ + uint8_t refcount; +#define USB_FIFO_REF_MAX 0xFF +}; + +extern struct cdevsw usb_devsw; + +int usb_fifo_wait(struct usb_fifo *fifo); +void usb_fifo_signal(struct usb_fifo *fifo); +uint8_t usb_fifo_opened(struct usb_fifo *fifo); +struct usb_symlink *usb_alloc_symlink(const char *target); +void usb_free_symlink(struct usb_symlink *ps); +int usb_read_symlink(uint8_t *user_ptr, uint32_t startentry, + uint32_t user_len); + +#endif /* _USB_DEV_H_ */ diff --git a/sys/bus/u4b/usb_device.c b/sys/bus/u4b/usb_device.c new file mode 100644 index 0000000000..726d7a74f8 --- /dev/null +++ b/sys/bus/u4b/usb_device.c @@ -0,0 +1,2743 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if USB_HAVE_UGEN +#include +#endif + +#include "usbdevs.h" + +#define USB_DEBUG_VAR usb_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if USB_HAVE_UGEN +#include +#include +#endif + +#include + +#include +#include + +/* function prototypes */ + +static void usb_init_endpoint(struct usb_device *, uint8_t, + struct usb_endpoint_descriptor *, + struct usb_endpoint_ss_comp_descriptor *, + struct usb_endpoint *); +static void usb_unconfigure(struct usb_device *, uint8_t); +static void usb_detach_device_sub(struct usb_device *, device_t *, + char **, uint8_t); +static uint8_t usb_probe_and_attach_sub(struct usb_device *, + struct usb_attach_arg *); +static void usb_init_attach_arg(struct usb_device *, + struct usb_attach_arg *); +static void usb_suspend_resume_sub(struct usb_device *, device_t, + uint8_t); +static void usbd_clear_stall_proc(struct usb_proc_msg *_pm); +static usb_error_t usb_config_parse(struct usb_device *, uint8_t, uint8_t); +static void usbd_set_device_strings(struct usb_device *); +#if USB_HAVE_DEVCTL +static void usb_notify_addq(const char *type, struct usb_device *); +#endif +#if USB_HAVE_UGEN +static void usb_fifo_free_wrap(struct usb_device *, uint8_t, uint8_t); +static void usb_cdev_create(struct usb_device *); +static void usb_cdev_free(struct usb_device *); +#endif + +/* This variable is global to allow easy access to it: */ + +int usb_template = 0; + +TUNABLE_INT("hw.usb.usb_template", &usb_template); +SYSCTL_INT(_hw_usb, OID_AUTO, template, CTLFLAG_RW, + &usb_template, 0, "Selected USB device side template"); + +/* English is default language */ + +static int usb_lang_id = 0x0009; +static int usb_lang_mask = 0x00FF; + +TUNABLE_INT("hw.usb.usb_lang_id", &usb_lang_id); +SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_id, CTLFLAG_RW, + &usb_lang_id, 0, "Preferred USB language ID"); + +TUNABLE_INT("hw.usb.usb_lang_mask", &usb_lang_mask); +SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_mask, CTLFLAG_RW, + &usb_lang_mask, 0, "Preferred USB language mask"); + +static const char* statestr[USB_STATE_MAX] = { + [USB_STATE_DETACHED] = "DETACHED", + [USB_STATE_ATTACHED] = "ATTACHED", + [USB_STATE_POWERED] = "POWERED", + [USB_STATE_ADDRESSED] = "ADDRESSED", + [USB_STATE_CONFIGURED] = "CONFIGURED", +}; + +const char * +usb_statestr(enum usb_dev_state state) +{ + return ((state < USB_STATE_MAX) ? statestr[state] : "UNKNOWN"); +} + +const char * +usb_get_manufacturer(struct usb_device *udev) +{ + return (udev->manufacturer ? udev->manufacturer : "Unknown"); +} + +const char * +usb_get_product(struct usb_device *udev) +{ + return (udev->product ? udev->product : ""); +} + +const char * +usb_get_serial(struct usb_device *udev) +{ + return (udev->serial ? udev->serial : ""); +} + +/*------------------------------------------------------------------------* + * usbd_get_ep_by_addr + * + * This function searches for an USB ep by endpoint address and + * direction. + * + * Returns: + * NULL: Failure + * Else: Success + *------------------------------------------------------------------------*/ +struct usb_endpoint * +usbd_get_ep_by_addr(struct usb_device *udev, uint8_t ea_val) +{ + struct usb_endpoint *ep = udev->endpoints; + struct usb_endpoint *ep_end = udev->endpoints + udev->endpoints_max; + enum { + EA_MASK = (UE_DIR_IN | UE_DIR_OUT | UE_ADDR), + }; + + /* + * According to the USB specification not all bits are used + * for the endpoint address. Keep defined bits only: + */ + ea_val &= EA_MASK; + + /* + * Iterate accross all the USB endpoints searching for a match + * based on the endpoint address: + */ + for (; ep != ep_end; ep++) { + + if (ep->edesc == NULL) { + continue; + } + /* do the mask and check the value */ + if ((ep->edesc->bEndpointAddress & EA_MASK) == ea_val) { + goto found; + } + } + + /* + * The default endpoint is always present and is checked separately: + */ + if ((udev->ctrl_ep.edesc) && + ((udev->ctrl_ep.edesc->bEndpointAddress & EA_MASK) == ea_val)) { + ep = &udev->ctrl_ep; + goto found; + } + return (NULL); + +found: + return (ep); +} + +/*------------------------------------------------------------------------* + * usbd_get_endpoint + * + * This function searches for an USB endpoint based on the information + * given by the passed "struct usb_config" pointer. + * + * Return values: + * NULL: No match. + * Else: Pointer to "struct usb_endpoint". + *------------------------------------------------------------------------*/ +struct usb_endpoint * +usbd_get_endpoint(struct usb_device *udev, uint8_t iface_index, + const struct usb_config *setup) +{ + struct usb_endpoint *ep = udev->endpoints; + struct usb_endpoint *ep_end = udev->endpoints + udev->endpoints_max; + uint8_t index = setup->ep_index; + uint8_t ea_mask; + uint8_t ea_val; + uint8_t type_mask; + uint8_t type_val; + + DPRINTFN(10, "udev=%p iface_index=%d address=0x%x " + "type=0x%x dir=0x%x index=%d\n", + udev, iface_index, setup->endpoint, + setup->type, setup->direction, setup->ep_index); + + /* check USB mode */ + + if (setup->usb_mode != USB_MODE_DUAL && + udev->flags.usb_mode != setup->usb_mode) { + /* wrong mode - no endpoint */ + return (NULL); + } + + /* setup expected endpoint direction mask and value */ + + if (setup->direction == UE_DIR_RX) { + ea_mask = (UE_DIR_IN | UE_DIR_OUT); + ea_val = (udev->flags.usb_mode == USB_MODE_DEVICE) ? + UE_DIR_OUT : UE_DIR_IN; + } else if (setup->direction == UE_DIR_TX) { + ea_mask = (UE_DIR_IN | UE_DIR_OUT); + ea_val = (udev->flags.usb_mode == USB_MODE_DEVICE) ? + UE_DIR_IN : UE_DIR_OUT; + } else if (setup->direction == UE_DIR_ANY) { + /* match any endpoint direction */ + ea_mask = 0; + ea_val = 0; + } else { + /* match the given endpoint direction */ + ea_mask = (UE_DIR_IN | UE_DIR_OUT); + ea_val = (setup->direction & (UE_DIR_IN | UE_DIR_OUT)); + } + + /* setup expected endpoint address */ + + if (setup->endpoint == UE_ADDR_ANY) { + /* match any endpoint address */ + } else { + /* match the given endpoint address */ + ea_mask |= UE_ADDR; + ea_val |= (setup->endpoint & UE_ADDR); + } + + /* setup expected endpoint type */ + + if (setup->type == UE_BULK_INTR) { + /* this will match BULK and INTERRUPT endpoints */ + type_mask = 2; + type_val = 2; + } else if (setup->type == UE_TYPE_ANY) { + /* match any endpoint type */ + type_mask = 0; + type_val = 0; + } else { + /* match the given endpoint type */ + type_mask = UE_XFERTYPE; + type_val = (setup->type & UE_XFERTYPE); + } + + /* + * Iterate accross all the USB endpoints searching for a match + * based on the endpoint address. Note that we are searching + * the endpoints from the beginning of the "udev->endpoints" array. + */ + for (; ep != ep_end; ep++) { + + if ((ep->edesc == NULL) || + (ep->iface_index != iface_index)) { + continue; + } + /* do the masks and check the values */ + + if (((ep->edesc->bEndpointAddress & ea_mask) == ea_val) && + ((ep->edesc->bmAttributes & type_mask) == type_val)) { + if (!index--) { + goto found; + } + } + } + + /* + * Match against default endpoint last, so that "any endpoint", "any + * address" and "any direction" returns the first endpoint of the + * interface. "iface_index" and "direction" is ignored: + */ + if ((udev->ctrl_ep.edesc) && + ((udev->ctrl_ep.edesc->bEndpointAddress & ea_mask) == ea_val) && + ((udev->ctrl_ep.edesc->bmAttributes & type_mask) == type_val) && + (!index)) { + ep = &udev->ctrl_ep; + goto found; + } + return (NULL); + +found: + return (ep); +} + +/*------------------------------------------------------------------------* + * usbd_interface_count + * + * This function stores the number of USB interfaces excluding + * alternate settings, which the USB config descriptor reports into + * the unsigned 8-bit integer pointed to by "count". + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_interface_count(struct usb_device *udev, uint8_t *count) +{ + if (udev->cdesc == NULL) { + *count = 0; + return (USB_ERR_NOT_CONFIGURED); + } + *count = udev->ifaces_max; + return (USB_ERR_NORMAL_COMPLETION); +} + + +/*------------------------------------------------------------------------* + * usb_init_endpoint + * + * This function will initialise the USB endpoint structure pointed to by + * the "endpoint" argument. The structure pointed to by "endpoint" must be + * zeroed before calling this function. + *------------------------------------------------------------------------*/ +static void +usb_init_endpoint(struct usb_device *udev, uint8_t iface_index, + struct usb_endpoint_descriptor *edesc, + struct usb_endpoint_ss_comp_descriptor *ecomp, + struct usb_endpoint *ep) +{ + struct usb_bus_methods *methods; + + methods = udev->bus->methods; + + (methods->endpoint_init) (udev, edesc, ep); + + /* initialise USB endpoint structure */ + ep->edesc = edesc; + ep->ecomp = ecomp; + ep->iface_index = iface_index; + TAILQ_INIT(&ep->endpoint_q.head); + ep->endpoint_q.command = &usbd_pipe_start; + + /* the pipe is not supported by the hardware */ + if (ep->methods == NULL) + return; + + /* clear stall, if any */ + if (methods->clear_stall != NULL) { + USB_BUS_LOCK(udev->bus); + (methods->clear_stall) (udev, ep); + USB_BUS_UNLOCK(udev->bus); + } +} + +/*-----------------------------------------------------------------------* + * usb_endpoint_foreach + * + * This function will iterate all the USB endpoints except the control + * endpoint. This function is NULL safe. + * + * Return values: + * NULL: End of USB endpoints + * Else: Pointer to next USB endpoint + *------------------------------------------------------------------------*/ +struct usb_endpoint * +usb_endpoint_foreach(struct usb_device *udev, struct usb_endpoint *ep) +{ + struct usb_endpoint *ep_end; + + /* be NULL safe */ + if (udev == NULL) + return (NULL); + + ep_end = udev->endpoints + udev->endpoints_max; + + /* get next endpoint */ + if (ep == NULL) + ep = udev->endpoints; + else + ep++; + + /* find next allocated ep */ + while (ep != ep_end) { + if (ep->edesc != NULL) + return (ep); + ep++; + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_unconfigure + * + * This function will free all USB interfaces and USB endpoints belonging + * to an USB device. + * + * Flag values, see "USB_UNCFG_FLAG_XXX". + *------------------------------------------------------------------------*/ +static void +usb_unconfigure(struct usb_device *udev, uint8_t flag) +{ + uint8_t do_unlock; + + /* automatic locking */ + if (usbd_enum_is_locked(udev)) { + do_unlock = 0; + } else { + do_unlock = 1; + usbd_enum_lock(udev); + } + + /* detach all interface drivers */ + usb_detach_device(udev, USB_IFACE_INDEX_ANY, flag); + +#if USB_HAVE_UGEN + /* free all FIFOs except control endpoint FIFOs */ + usb_fifo_free_wrap(udev, USB_IFACE_INDEX_ANY, flag); + + /* + * Free all cdev's, if any. + */ + usb_cdev_free(udev); +#endif + +#if USB_HAVE_COMPAT_LINUX + /* free Linux compat device, if any */ + if (udev->linux_endpoint_start) { + usb_linux_free_device(udev); + udev->linux_endpoint_start = NULL; + } +#endif + + usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_FREE); + + /* free "cdesc" after "ifaces" and "endpoints", if any */ + if (udev->cdesc != NULL) { + if (udev->flags.usb_mode != USB_MODE_DEVICE) + free(udev->cdesc, M_USB); + udev->cdesc = NULL; + } + /* set unconfigured state */ + udev->curr_config_no = USB_UNCONFIG_NO; + udev->curr_config_index = USB_UNCONFIG_INDEX; + + if (do_unlock) + usbd_enum_unlock(udev); +} + +/*------------------------------------------------------------------------* + * usbd_set_config_index + * + * This function selects configuration by index, independent of the + * actual configuration number. This function should not be used by + * USB drivers. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_set_config_index(struct usb_device *udev, uint8_t index) +{ + struct usb_status ds; + struct usb_config_descriptor *cdp; + uint16_t power; + uint16_t max_power; + uint8_t selfpowered; + uint8_t do_unlock; + usb_error_t err; + + DPRINTFN(6, "udev=%p index=%d\n", udev, index); + + /* automatic locking */ + if (usbd_enum_is_locked(udev)) { + do_unlock = 0; + } else { + do_unlock = 1; + usbd_enum_lock(udev); + } + + usb_unconfigure(udev, 0); + + if (index == USB_UNCONFIG_INDEX) { + /* + * Leave unallocated when unconfiguring the + * device. "usb_unconfigure()" will also reset + * the current config number and index. + */ + err = usbd_req_set_config(udev, NULL, USB_UNCONFIG_NO); + if (udev->state == USB_STATE_CONFIGURED) + usb_set_device_state(udev, USB_STATE_ADDRESSED); + goto done; + } + /* get the full config descriptor */ + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + /* save some memory */ + err = usbd_req_get_descriptor_ptr(udev, &cdp, + (UDESC_CONFIG << 8) | index); + } else { + /* normal request */ + err = usbd_req_get_config_desc_full(udev, + NULL, &cdp, M_USB, index); + } + if (err) { + goto done; + } + /* set the new config descriptor */ + + udev->cdesc = cdp; + + /* Figure out if the device is self or bus powered. */ + selfpowered = 0; + if ((!udev->flags.uq_bus_powered) && + (cdp->bmAttributes & UC_SELF_POWERED) && + (udev->flags.usb_mode == USB_MODE_HOST)) { + /* May be self powered. */ + if (cdp->bmAttributes & UC_BUS_POWERED) { + /* Must ask device. */ + err = usbd_req_get_device_status(udev, NULL, &ds); + if (err) { + DPRINTFN(0, "could not read " + "device status: %s\n", + usbd_errstr(err)); + } else if (UGETW(ds.wStatus) & UDS_SELF_POWERED) { + selfpowered = 1; + } + DPRINTF("status=0x%04x \n", + UGETW(ds.wStatus)); + } else + selfpowered = 1; + } + DPRINTF("udev=%p cdesc=%p (addr %d) cno=%d attr=0x%02x, " + "selfpowered=%d, power=%d\n", + udev, cdp, + udev->address, cdp->bConfigurationValue, cdp->bmAttributes, + selfpowered, cdp->bMaxPower * 2); + + /* Check if we have enough power. */ + power = cdp->bMaxPower * 2; + + if (udev->parent_hub) { + max_power = udev->parent_hub->hub->portpower; + } else { + max_power = USB_MAX_POWER; + } + + if (power > max_power) { + DPRINTFN(0, "power exceeded %d > %d\n", power, max_power); + err = USB_ERR_NO_POWER; + goto done; + } + /* Only update "self_powered" in USB Host Mode */ + if (udev->flags.usb_mode == USB_MODE_HOST) { + udev->flags.self_powered = selfpowered; + } + udev->power = power; + udev->curr_config_no = cdp->bConfigurationValue; + udev->curr_config_index = index; + usb_set_device_state(udev, USB_STATE_CONFIGURED); + + /* Set the actual configuration value. */ + err = usbd_req_set_config(udev, NULL, cdp->bConfigurationValue); + if (err) { + goto done; + } + + err = usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_ALLOC); + if (err) { + goto done; + } + + err = usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_INIT); + if (err) { + goto done; + } + +#if USB_HAVE_UGEN + /* create device nodes for each endpoint */ + usb_cdev_create(udev); +#endif + +done: + DPRINTF("error=%s\n", usbd_errstr(err)); + if (err) { + usb_unconfigure(udev, 0); + } + if (do_unlock) + usbd_enum_unlock(udev); + return (err); +} + +/*------------------------------------------------------------------------* + * usb_config_parse + * + * This function will allocate and free USB interfaces and USB endpoints, + * parse the USB configuration structure and initialise the USB endpoints + * and interfaces. If "iface_index" is not equal to + * "USB_IFACE_INDEX_ANY" then the "cmd" parameter is the + * alternate_setting to be selected for the given interface. Else the + * "cmd" parameter is defined by "USB_CFG_XXX". "iface_index" can be + * "USB_IFACE_INDEX_ANY" or a valid USB interface index. This function + * is typically called when setting the configuration or when setting + * an alternate interface. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb_error_t +usb_config_parse(struct usb_device *udev, uint8_t iface_index, uint8_t cmd) +{ + struct usb_idesc_parse_state ips; + struct usb_interface_descriptor *id; + struct usb_endpoint_descriptor *ed; + struct usb_interface *iface; + struct usb_endpoint *ep; + usb_error_t err; + uint8_t ep_curr; + uint8_t ep_max; + uint8_t temp; + uint8_t do_init; + uint8_t alt_index; + + if (iface_index != USB_IFACE_INDEX_ANY) { + /* parameter overload */ + alt_index = cmd; + cmd = USB_CFG_INIT; + } else { + /* not used */ + alt_index = 0; + } + + err = 0; + + DPRINTFN(5, "iface_index=%d cmd=%d\n", + iface_index, cmd); + + if (cmd == USB_CFG_FREE) + goto cleanup; + + if (cmd == USB_CFG_INIT) { + sx_assert(&udev->enum_sx, SA_LOCKED); + + /* check for in-use endpoints */ + + ep = udev->endpoints; + ep_max = udev->endpoints_max; + while (ep_max--) { + /* look for matching endpoints */ + if ((iface_index == USB_IFACE_INDEX_ANY) || + (iface_index == ep->iface_index)) { + if (ep->refcount_alloc != 0) { + /* + * This typically indicates a + * more serious error. + */ + err = USB_ERR_IN_USE; + } else { + /* reset endpoint */ + memset(ep, 0, sizeof(*ep)); + /* make sure we don't zero the endpoint again */ + ep->iface_index = USB_IFACE_INDEX_ANY; + } + } + ep++; + } + + if (err) + return (err); + } + + memset(&ips, 0, sizeof(ips)); + + ep_curr = 0; + ep_max = 0; + + while ((id = usb_idesc_foreach(udev->cdesc, &ips))) { + + /* check for interface overflow */ + if (ips.iface_index == USB_IFACE_MAX) + break; /* crazy */ + + iface = udev->ifaces + ips.iface_index; + + /* check for specific interface match */ + + if (cmd == USB_CFG_INIT) { + if ((iface_index != USB_IFACE_INDEX_ANY) && + (iface_index != ips.iface_index)) { + /* wrong interface */ + do_init = 0; + } else if (alt_index != ips.iface_index_alt) { + /* wrong alternate setting */ + do_init = 0; + } else { + /* initialise interface */ + do_init = 1; + } + } else + do_init = 0; + + /* check for new interface */ + if (ips.iface_index_alt == 0) { + /* update current number of endpoints */ + ep_curr = ep_max; + } + /* check for init */ + if (do_init) { + /* setup the USB interface structure */ + iface->idesc = id; + /* default setting */ + iface->parent_iface_index = USB_IFACE_INDEX_ANY; + /* set alternate index */ + iface->alt_index = alt_index; + } + + DPRINTFN(5, "found idesc nendpt=%d\n", id->bNumEndpoints); + + ed = (struct usb_endpoint_descriptor *)id; + + temp = ep_curr; + + /* iterate all the endpoint descriptors */ + while ((ed = usb_edesc_foreach(udev->cdesc, ed))) { + + if (temp == USB_EP_MAX) + break; /* crazy */ + + ep = udev->endpoints + temp; + + if (do_init) { + void *ecomp; + + ecomp = usb_ed_comp_foreach(udev->cdesc, (void *)ed); + if (ecomp != NULL) + DPRINTFN(5, "Found endpoint companion descriptor\n"); + + usb_init_endpoint(udev, + ips.iface_index, ed, ecomp, ep); + } + + temp ++; + + /* find maximum number of endpoints */ + if (ep_max < temp) + ep_max = temp; + + /* optimalisation */ + id = (struct usb_interface_descriptor *)ed; + } + } + + /* NOTE: It is valid to have no interfaces and no endpoints! */ + + if (cmd == USB_CFG_ALLOC) { + udev->ifaces_max = ips.iface_index; + udev->ifaces = NULL; + if (udev->ifaces_max != 0) { + udev->ifaces = malloc(sizeof(*iface) * udev->ifaces_max, + M_USB, M_WAITOK | M_ZERO); + if (udev->ifaces == NULL) { + err = USB_ERR_NOMEM; + goto done; + } + } + if (ep_max != 0) { + udev->endpoints = malloc(sizeof(*ep) * ep_max, + M_USB, M_WAITOK | M_ZERO); + if (udev->endpoints == NULL) { + err = USB_ERR_NOMEM; + goto done; + } + } else { + udev->endpoints = NULL; + } + USB_BUS_LOCK(udev->bus); + udev->endpoints_max = ep_max; + /* reset any ongoing clear-stall */ + udev->ep_curr = NULL; + USB_BUS_UNLOCK(udev->bus); + } + +done: + if (err) { + if (cmd == USB_CFG_ALLOC) { +cleanup: + USB_BUS_LOCK(udev->bus); + udev->endpoints_max = 0; + /* reset any ongoing clear-stall */ + udev->ep_curr = NULL; + USB_BUS_UNLOCK(udev->bus); + + /* cleanup */ + if (udev->ifaces != NULL) + free(udev->ifaces, M_USB); + if (udev->endpoints != NULL) + free(udev->endpoints, M_USB); + + udev->ifaces = NULL; + udev->endpoints = NULL; + udev->ifaces_max = 0; + } + } + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_set_alt_interface_index + * + * This function will select an alternate interface index for the + * given interface index. The interface should not be in use when this + * function is called. That means there should not be any open USB + * transfers. Else an error is returned. If the alternate setting is + * already set this function will simply return success. This function + * is called in Host mode and Device mode! + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_set_alt_interface_index(struct usb_device *udev, + uint8_t iface_index, uint8_t alt_index) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + usb_error_t err; + uint8_t do_unlock; + + /* automatic locking */ + if (usbd_enum_is_locked(udev)) { + do_unlock = 0; + } else { + do_unlock = 1; + usbd_enum_lock(udev); + } + if (iface == NULL) { + err = USB_ERR_INVAL; + goto done; + } + if (iface->alt_index == alt_index) { + /* + * Optimise away duplicate setting of + * alternate setting in USB Host Mode! + */ + err = 0; + goto done; + } +#if USB_HAVE_UGEN + /* + * Free all generic FIFOs for this interface, except control + * endpoint FIFOs: + */ + usb_fifo_free_wrap(udev, iface_index, 0); +#endif + + err = usb_config_parse(udev, iface_index, alt_index); + if (err) { + goto done; + } + if (iface->alt_index != alt_index) { + /* the alternate setting does not exist */ + err = USB_ERR_INVAL; + goto done; + } + + err = usbd_req_set_alt_interface_no(udev, NULL, iface_index, + iface->idesc->bAlternateSetting); + +done: + if (do_unlock) + usbd_enum_unlock(udev); + + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_set_endpoint_stall + * + * This function is used to make a BULK or INTERRUPT endpoint send + * STALL tokens in USB device mode. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_set_endpoint_stall(struct usb_device *udev, struct usb_endpoint *ep, + uint8_t do_stall) +{ + struct usb_xfer *xfer; + uint8_t et; + uint8_t was_stalled; + + if (ep == NULL) { + /* nothing to do */ + DPRINTF("Cannot find endpoint\n"); + /* + * Pretend that the clear or set stall request is + * successful else some USB host stacks can do + * strange things, especially when a control endpoint + * stalls. + */ + return (0); + } + et = (ep->edesc->bmAttributes & UE_XFERTYPE); + + if ((et != UE_BULK) && + (et != UE_INTERRUPT)) { + /* + * Should not stall control + * nor isochronous endpoints. + */ + DPRINTF("Invalid endpoint\n"); + return (0); + } + USB_BUS_LOCK(udev->bus); + + /* store current stall state */ + was_stalled = ep->is_stalled; + + /* check for no change */ + if (was_stalled && do_stall) { + /* if the endpoint is already stalled do nothing */ + USB_BUS_UNLOCK(udev->bus); + DPRINTF("No change\n"); + return (0); + } + /* set stalled state */ + ep->is_stalled = 1; + + if (do_stall || (!was_stalled)) { + if (!was_stalled) { + /* lookup the current USB transfer, if any */ + xfer = ep->endpoint_q.curr; + } else { + xfer = NULL; + } + + /* + * If "xfer" is non-NULL the "set_stall" method will + * complete the USB transfer like in case of a timeout + * setting the error code "USB_ERR_STALLED". + */ + (udev->bus->methods->set_stall) (udev, xfer, ep, &do_stall); + } + if (!do_stall) { + ep->toggle_next = 0; /* reset data toggle */ + ep->is_stalled = 0; /* clear stalled state */ + + (udev->bus->methods->clear_stall) (udev, ep); + + /* start up the current or next transfer, if any */ + usb_command_wrapper(&ep->endpoint_q, ep->endpoint_q.curr); + } + USB_BUS_UNLOCK(udev->bus); + return (0); +} + +/*------------------------------------------------------------------------* + * usb_reset_iface_endpoints - used in USB device side mode + *------------------------------------------------------------------------*/ +usb_error_t +usb_reset_iface_endpoints(struct usb_device *udev, uint8_t iface_index) +{ + struct usb_endpoint *ep; + struct usb_endpoint *ep_end; + + ep = udev->endpoints; + ep_end = udev->endpoints + udev->endpoints_max; + + for (; ep != ep_end; ep++) { + + if ((ep->edesc == NULL) || + (ep->iface_index != iface_index)) { + continue; + } + /* simulate a clear stall from the peer */ + usbd_set_endpoint_stall(udev, ep, 0); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_detach_device_sub + * + * This function will try to detach an USB device. If it fails a panic + * will result. + * + * Flag values, see "USB_UNCFG_FLAG_XXX". + *------------------------------------------------------------------------*/ +static void +usb_detach_device_sub(struct usb_device *udev, device_t *ppdev, + char **ppnpinfo, uint8_t flag) +{ + device_t dev; + char *pnpinfo; + int err; + + dev = *ppdev; + if (dev) { + /* + * NOTE: It is important to clear "*ppdev" before deleting + * the child due to some device methods being called late + * during the delete process ! + */ + *ppdev = NULL; + + device_printf(dev, "at %s, port %d, addr %d " + "(disconnected)\n", + device_get_nameunit(udev->parent_dev), + udev->port_no, udev->address); + + if (device_is_attached(dev)) { + if (udev->flags.peer_suspended) { + err = DEVICE_RESUME(dev); + if (err) { + device_printf(dev, "Resume failed\n"); + } + } + if (device_detach(dev)) { + goto error; + } + } + if (device_delete_child(udev->parent_dev, dev)) { + goto error; + } + } + + pnpinfo = *ppnpinfo; + if (pnpinfo != NULL) { + *ppnpinfo = NULL; + free(pnpinfo, M_USBDEV); + } + return; + +error: + /* Detach is not allowed to fail in the USB world */ + panic("usb_detach_device_sub: A USB driver would not detach\n"); +} + +/*------------------------------------------------------------------------* + * usb_detach_device + * + * The following function will detach the matching interfaces. + * This function is NULL safe. + * + * Flag values, see "USB_UNCFG_FLAG_XXX". + *------------------------------------------------------------------------*/ +void +usb_detach_device(struct usb_device *udev, uint8_t iface_index, + uint8_t flag) +{ + struct usb_interface *iface; + uint8_t i; + + if (udev == NULL) { + /* nothing to do */ + return; + } + DPRINTFN(4, "udev=%p\n", udev); + + sx_assert(&udev->enum_sx, SA_LOCKED); + + /* + * First detach the child to give the child's detach routine a + * chance to detach the sub-devices in the correct order. + * Then delete the child using "device_delete_child()" which + * will detach all sub-devices from the bottom and upwards! + */ + if (iface_index != USB_IFACE_INDEX_ANY) { + i = iface_index; + iface_index = i + 1; + } else { + i = 0; + iface_index = USB_IFACE_MAX; + } + + /* do the detach */ + + for (; i != iface_index; i++) { + + iface = usbd_get_iface(udev, i); + if (iface == NULL) { + /* looks like the end of the USB interfaces */ + break; + } + usb_detach_device_sub(udev, &iface->subdev, + &iface->pnpinfo, flag); + } +} + +/*------------------------------------------------------------------------* + * usb_probe_and_attach_sub + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +usb_probe_and_attach_sub(struct usb_device *udev, + struct usb_attach_arg *uaa) +{ + struct usb_interface *iface; + device_t dev; + int err; + + iface = uaa->iface; + if (iface->parent_iface_index != USB_IFACE_INDEX_ANY) { + /* leave interface alone */ + return (0); + } + dev = iface->subdev; + if (dev) { + + /* clean up after module unload */ + + if (device_is_attached(dev)) { + /* already a device there */ + return (0); + } + /* clear "iface->subdev" as early as possible */ + + iface->subdev = NULL; + + if (device_delete_child(udev->parent_dev, dev)) { + + /* + * Panic here, else one can get a double call + * to device_detach(). USB devices should + * never fail on detach! + */ + panic("device_delete_child() failed\n"); + } + } + if (uaa->temp_dev == NULL) { + + /* create a new child */ + uaa->temp_dev = device_add_child(udev->parent_dev, NULL, -1); + if (uaa->temp_dev == NULL) { + device_printf(udev->parent_dev, + "Device creation failed\n"); + return (1); /* failure */ + } + device_set_ivars(uaa->temp_dev, uaa); + device_quiet(uaa->temp_dev); + } + /* + * Set "subdev" before probe and attach so that "devd" gets + * the information it needs. + */ + iface->subdev = uaa->temp_dev; + + if (device_probe_and_attach(iface->subdev) == 0) { + /* + * The USB attach arguments are only available during probe + * and attach ! + */ + uaa->temp_dev = NULL; + device_set_ivars(iface->subdev, NULL); + + if (udev->flags.peer_suspended) { + err = DEVICE_SUSPEND(iface->subdev); + if (err) + device_printf(iface->subdev, "Suspend failed\n"); + } + return (0); /* success */ + } else { + /* No USB driver found */ + iface->subdev = NULL; + } + return (1); /* failure */ +} + +/*------------------------------------------------------------------------* + * usbd_set_parent_iface + * + * Using this function will lock the alternate interface setting on an + * interface. It is typically used for multi interface drivers. In USB + * device side mode it is assumed that the alternate interfaces all + * have the same endpoint descriptors. The default parent index value + * is "USB_IFACE_INDEX_ANY". Then the alternate setting value is not + * locked. + *------------------------------------------------------------------------*/ +void +usbd_set_parent_iface(struct usb_device *udev, uint8_t iface_index, + uint8_t parent_index) +{ + struct usb_interface *iface; + + iface = usbd_get_iface(udev, iface_index); + if (iface) { + iface->parent_iface_index = parent_index; + } +} + +static void +usb_init_attach_arg(struct usb_device *udev, + struct usb_attach_arg *uaa) +{ + memset(uaa, 0, sizeof(*uaa)); + + uaa->device = udev; + uaa->usb_mode = udev->flags.usb_mode; + uaa->port = udev->port_no; + uaa->dev_state = UAA_DEV_READY; + + uaa->info.idVendor = UGETW(udev->ddesc.idVendor); + uaa->info.idProduct = UGETW(udev->ddesc.idProduct); + uaa->info.bcdDevice = UGETW(udev->ddesc.bcdDevice); + uaa->info.bDeviceClass = udev->ddesc.bDeviceClass; + uaa->info.bDeviceSubClass = udev->ddesc.bDeviceSubClass; + uaa->info.bDeviceProtocol = udev->ddesc.bDeviceProtocol; + uaa->info.bConfigIndex = udev->curr_config_index; + uaa->info.bConfigNum = udev->curr_config_no; +} + +/*------------------------------------------------------------------------* + * usb_probe_and_attach + * + * This function is called from "uhub_explore_sub()", + * "usb_handle_set_config()" and "usb_handle_request()". + * + * Returns: + * 0: Success + * Else: A control transfer failed + *------------------------------------------------------------------------*/ +usb_error_t +usb_probe_and_attach(struct usb_device *udev, uint8_t iface_index) +{ + struct usb_attach_arg uaa; + struct usb_interface *iface; + uint8_t i; + uint8_t j; + uint8_t do_unlock; + + if (udev == NULL) { + DPRINTF("udev == NULL\n"); + return (USB_ERR_INVAL); + } + /* automatic locking */ + if (usbd_enum_is_locked(udev)) { + do_unlock = 0; + } else { + do_unlock = 1; + usbd_enum_lock(udev); + } + + if (udev->curr_config_index == USB_UNCONFIG_INDEX) { + /* do nothing - no configuration has been set */ + goto done; + } + /* setup USB attach arguments */ + + usb_init_attach_arg(udev, &uaa); + + /* + * If the whole USB device is targeted, invoke the USB event + * handler(s): + */ + if (iface_index == USB_IFACE_INDEX_ANY) { + + EVENTHANDLER_INVOKE(usb_dev_configured, udev, &uaa); + + if (uaa.dev_state != UAA_DEV_READY) { + /* leave device unconfigured */ + usb_unconfigure(udev, 0); + goto done; + } + } + + /* Check if only one interface should be probed: */ + if (iface_index != USB_IFACE_INDEX_ANY) { + i = iface_index; + j = i + 1; + } else { + i = 0; + j = USB_IFACE_MAX; + } + + /* Do the probe and attach */ + for (; i != j; i++) { + + iface = usbd_get_iface(udev, i); + if (iface == NULL) { + /* + * Looks like the end of the USB + * interfaces ! + */ + DPRINTFN(2, "end of interfaces " + "at %u\n", i); + break; + } + if (iface->idesc == NULL) { + /* no interface descriptor */ + continue; + } + uaa.iface = iface; + + uaa.info.bInterfaceClass = + iface->idesc->bInterfaceClass; + uaa.info.bInterfaceSubClass = + iface->idesc->bInterfaceSubClass; + uaa.info.bInterfaceProtocol = + iface->idesc->bInterfaceProtocol; + uaa.info.bIfaceIndex = i; + uaa.info.bIfaceNum = + iface->idesc->bInterfaceNumber; + uaa.driver_info = 0; /* reset driver_info */ + + DPRINTFN(2, "iclass=%u/%u/%u iindex=%u/%u\n", + uaa.info.bInterfaceClass, + uaa.info.bInterfaceSubClass, + uaa.info.bInterfaceProtocol, + uaa.info.bIfaceIndex, + uaa.info.bIfaceNum); + + usb_probe_and_attach_sub(udev, &uaa); + + /* + * Remove the leftover child, if any, to enforce that + * a new nomatch devd event is generated for the next + * interface if no driver is found: + */ + if (uaa.temp_dev == NULL) + continue; + if (device_delete_child(udev->parent_dev, uaa.temp_dev)) + DPRINTFN(0, "device delete child failed\n"); + uaa.temp_dev = NULL; + } +done: + if (do_unlock) + usbd_enum_unlock(udev); + + return (0); +} + +/*------------------------------------------------------------------------* + * usb_suspend_resume_sub + * + * This function is called when the suspend or resume methods should + * be executed on an USB device. + *------------------------------------------------------------------------*/ +static void +usb_suspend_resume_sub(struct usb_device *udev, device_t dev, uint8_t do_suspend) +{ + int err; + + if (dev == NULL) { + return; + } + if (!device_is_attached(dev)) { + return; + } + if (do_suspend) { + err = DEVICE_SUSPEND(dev); + } else { + err = DEVICE_RESUME(dev); + } + if (err) { + device_printf(dev, "%s failed\n", + do_suspend ? "Suspend" : "Resume"); + } +} + +/*------------------------------------------------------------------------* + * usb_suspend_resume + * + * The following function will suspend or resume the USB device. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usb_suspend_resume(struct usb_device *udev, uint8_t do_suspend) +{ + struct usb_interface *iface; + uint8_t i; + + if (udev == NULL) { + /* nothing to do */ + return (0); + } + DPRINTFN(4, "udev=%p do_suspend=%d\n", udev, do_suspend); + + sx_assert(&udev->sr_sx, SA_LOCKED); + + USB_BUS_LOCK(udev->bus); + /* filter the suspend events */ + if (udev->flags.peer_suspended == do_suspend) { + USB_BUS_UNLOCK(udev->bus); + /* nothing to do */ + return (0); + } + udev->flags.peer_suspended = do_suspend; + USB_BUS_UNLOCK(udev->bus); + + /* do the suspend or resume */ + + for (i = 0; i != USB_IFACE_MAX; i++) { + + iface = usbd_get_iface(udev, i); + if (iface == NULL) { + /* looks like the end of the USB interfaces */ + break; + } + usb_suspend_resume_sub(udev, iface->subdev, do_suspend); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usbd_clear_stall_proc + * + * This function performs generic USB clear stall operations. + *------------------------------------------------------------------------*/ +static void +usbd_clear_stall_proc(struct usb_proc_msg *_pm) +{ + struct usb_clear_stall_msg *pm = (void *)_pm; + struct usb_device *udev = pm->udev; + + /* Change lock */ + USB_BUS_UNLOCK(udev->bus); + mtx_lock(&udev->device_mtx); + + /* Start clear stall callback */ + usbd_transfer_start(udev->ctrl_xfer[1]); + + /* Change lock */ + mtx_unlock(&udev->device_mtx); + USB_BUS_LOCK(udev->bus); +} + +/*------------------------------------------------------------------------* + * usb_alloc_device + * + * This function allocates a new USB device. This function is called + * when a new device has been put in the powered state, but not yet in + * the addressed state. Get initial descriptor, set the address, get + * full descriptor and get strings. + * + * Return values: + * 0: Failure + * Else: Success + *------------------------------------------------------------------------*/ +struct usb_device * +usb_alloc_device(device_t parent_dev, struct usb_bus *bus, + struct usb_device *parent_hub, uint8_t depth, uint8_t port_index, + uint8_t port_no, enum usb_dev_speed speed, enum usb_hc_mode mode) +{ + struct usb_attach_arg uaa; + struct usb_device *udev; + struct usb_device *adev; + struct usb_device *hub; + uint8_t *scratch_ptr; + size_t scratch_size; + usb_error_t err; + uint8_t device_index; + uint8_t config_index; + uint8_t config_quirk; + uint8_t set_config_failed; + + DPRINTF("parent_dev=%p, bus=%p, parent_hub=%p, depth=%u, " + "port_index=%u, port_no=%u, speed=%u, usb_mode=%u\n", + parent_dev, bus, parent_hub, depth, port_index, port_no, + speed, mode); + + /* + * Find an unused device index. In USB Host mode this is the + * same as the device address. + * + * Device index zero is not used and device index 1 should + * always be the root hub. + */ + for (device_index = USB_ROOT_HUB_ADDR; + (device_index != bus->devices_max) && + (bus->devices[device_index] != NULL); + device_index++) /* nop */; + + if (device_index == bus->devices_max) { + device_printf(bus->bdev, + "No free USB device index for new device\n"); + return (NULL); + } + + if (depth > 0x10) { + device_printf(bus->bdev, + "Invalid device depth\n"); + return (NULL); + } + udev = malloc(sizeof(*udev), M_USB, M_WAITOK | M_ZERO); + if (udev == NULL) { + return (NULL); + } + /* initialise our SX-lock */ + sx_init_flags(&udev->ctrl_sx, "USB device SX lock", SX_DUPOK); + + /* initialise our SX-lock */ + sx_init_flags(&udev->enum_sx, "USB config SX lock", SX_DUPOK); + sx_init_flags(&udev->sr_sx, "USB suspend and resume SX lock", SX_NOWITNESS); + + cv_init(&udev->ctrlreq_cv, "WCTRL"); + cv_init(&udev->ref_cv, "UGONE"); + + /* initialise our mutex */ + mtx_init(&udev->device_mtx, "USB device mutex", NULL, MTX_DEF); + + /* initialise generic clear stall */ + udev->cs_msg[0].hdr.pm_callback = &usbd_clear_stall_proc; + udev->cs_msg[0].udev = udev; + udev->cs_msg[1].hdr.pm_callback = &usbd_clear_stall_proc; + udev->cs_msg[1].udev = udev; + + /* initialise some USB device fields */ + udev->parent_hub = parent_hub; + udev->parent_dev = parent_dev; + udev->port_index = port_index; + udev->port_no = port_no; + udev->depth = depth; + udev->bus = bus; + udev->address = USB_START_ADDR; /* default value */ + udev->plugtime = (usb_ticks_t)ticks; + /* + * We need to force the power mode to "on" because there are plenty + * of USB devices out there that do not work very well with + * automatic suspend and resume! + */ + udev->power_mode = usbd_filter_power_mode(udev, USB_POWER_MODE_ON); + udev->pwr_save.last_xfer_time = ticks; + /* we are not ready yet */ + udev->refcount = 1; + + /* set up default endpoint descriptor */ + udev->ctrl_ep_desc.bLength = sizeof(udev->ctrl_ep_desc); + udev->ctrl_ep_desc.bDescriptorType = UDESC_ENDPOINT; + udev->ctrl_ep_desc.bEndpointAddress = USB_CONTROL_ENDPOINT; + udev->ctrl_ep_desc.bmAttributes = UE_CONTROL; + udev->ctrl_ep_desc.wMaxPacketSize[0] = USB_MAX_IPACKET; + udev->ctrl_ep_desc.wMaxPacketSize[1] = 0; + udev->ctrl_ep_desc.bInterval = 0; + + /* set up default endpoint companion descriptor */ + udev->ctrl_ep_comp_desc.bLength = sizeof(udev->ctrl_ep_comp_desc); + udev->ctrl_ep_comp_desc.bDescriptorType = UDESC_ENDPOINT_SS_COMP; + + udev->ddesc.bMaxPacketSize = USB_MAX_IPACKET; + + udev->speed = speed; + udev->flags.usb_mode = mode; + + /* search for our High Speed USB HUB, if any */ + + adev = udev; + hub = udev->parent_hub; + + while (hub) { + if (hub->speed == USB_SPEED_HIGH) { + udev->hs_hub_addr = hub->address; + udev->parent_hs_hub = hub; + udev->hs_port_no = adev->port_no; + break; + } + adev = hub; + hub = hub->parent_hub; + } + + /* init the default endpoint */ + usb_init_endpoint(udev, 0, + &udev->ctrl_ep_desc, + &udev->ctrl_ep_comp_desc, + &udev->ctrl_ep); + + /* set device index */ + udev->device_index = device_index; + +#if USB_HAVE_UGEN + /* Create ugen name */ + snprintf(udev->ugen_name, sizeof(udev->ugen_name), + USB_GENERIC_NAME "%u.%u", device_get_unit(bus->bdev), + device_index); + LIST_INIT(&udev->pd_list); + + /* Create the control endpoint device */ + udev->ctrl_dev = usb_make_dev(udev, NULL, 0, 0, + FREAD|FWRITE, UID_ROOT, GID_OPERATOR, 0600); + + /* Create a link from /dev/ugenX.X to the default endpoint */ + if (udev->ctrl_dev != NULL) + make_dev_alias(udev->ctrl_dev->cdev, "%s", udev->ugen_name); +#endif + /* Initialise device */ + if (bus->methods->device_init != NULL) { + err = (bus->methods->device_init) (udev); + if (err != 0) { + DPRINTFN(0, "device init %d failed " + "(%s, ignored)\n", device_index, + usbd_errstr(err)); + goto done; + } + } + /* set powered device state after device init is complete */ + usb_set_device_state(udev, USB_STATE_POWERED); + + if (udev->flags.usb_mode == USB_MODE_HOST) { + + err = usbd_req_set_address(udev, NULL, device_index); + + /* + * This is the new USB device address from now on, if + * the set address request didn't set it already. + */ + if (udev->address == USB_START_ADDR) + udev->address = device_index; + + /* + * We ignore any set-address errors, hence there are + * buggy USB devices out there that actually receive + * the SETUP PID, but manage to set the address before + * the STATUS stage is ACK'ed. If the device responds + * to the subsequent get-descriptor at the new + * address, then we know that the set-address command + * was successful. + */ + if (err) { + DPRINTFN(0, "set address %d failed " + "(%s, ignored)\n", udev->address, + usbd_errstr(err)); + } + } else { + /* We are not self powered */ + udev->flags.self_powered = 0; + + /* Set unconfigured state */ + udev->curr_config_no = USB_UNCONFIG_NO; + udev->curr_config_index = USB_UNCONFIG_INDEX; + + /* Setup USB descriptors */ + err = (usb_temp_setup_by_index_p) (udev, usb_template); + if (err) { + DPRINTFN(0, "setting up USB template failed maybe the USB " + "template module has not been loaded\n"); + goto done; + } + } + usb_set_device_state(udev, USB_STATE_ADDRESSED); + + /* setup the device descriptor and the initial "wMaxPacketSize" */ + err = usbd_setup_device_desc(udev, NULL); + + if (err != 0) { + /* XXX try to re-enumerate the device */ + err = usbd_req_re_enumerate(udev, NULL); + if (err) + goto done; + } + + /* + * Setup temporary USB attach args so that we can figure out some + * basic quirks for this device. + */ + usb_init_attach_arg(udev, &uaa); + + if (usb_test_quirk(&uaa, UQ_BUS_POWERED)) { + udev->flags.uq_bus_powered = 1; + } + if (usb_test_quirk(&uaa, UQ_NO_STRINGS)) { + udev->flags.no_strings = 1; + } + /* + * Workaround for buggy USB devices. + * + * It appears that some string-less USB chips will crash and + * disappear if any attempts are made to read any string + * descriptors. + * + * Try to detect such chips by checking the strings in the USB + * device descriptor. If no strings are present there we + * simply disable all USB strings. + */ + scratch_ptr = udev->bus->scratch[0].data; + scratch_size = sizeof(udev->bus->scratch[0].data); + + if (udev->ddesc.iManufacturer || + udev->ddesc.iProduct || + udev->ddesc.iSerialNumber) { + /* read out the language ID string */ + err = usbd_req_get_string_desc(udev, NULL, + (char *)scratch_ptr, 4, 0, USB_LANGUAGE_TABLE); + } else { + err = USB_ERR_INVAL; + } + + if (err || (scratch_ptr[0] < 4)) { + udev->flags.no_strings = 1; + } else { + uint16_t langid; + uint16_t pref; + uint16_t mask; + uint8_t x; + + /* load preferred value and mask */ + pref = usb_lang_id; + mask = usb_lang_mask; + + /* align length correctly */ + scratch_ptr[0] &= ~1; + + /* fix compiler warning */ + langid = 0; + + /* search for preferred language */ + for (x = 2; (x < scratch_ptr[0]); x += 2) { + langid = UGETW(scratch_ptr + x); + if ((langid & mask) == pref) + break; + } + if (x >= scratch_ptr[0]) { + /* pick the first language as the default */ + DPRINTFN(1, "Using first language\n"); + langid = UGETW(scratch_ptr + 2); + } + + DPRINTFN(1, "Language selected: 0x%04x\n", langid); + udev->langid = langid; + } + + /* assume 100mA bus powered for now. Changed when configured. */ + udev->power = USB_MIN_POWER; + /* fetch the vendor and product strings from the device */ + usbd_set_device_strings(udev); + + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + /* USB device mode setup is complete */ + err = 0; + goto config_done; + } + + /* + * Most USB devices should attach to config index 0 by + * default + */ + if (usb_test_quirk(&uaa, UQ_CFG_INDEX_0)) { + config_index = 0; + config_quirk = 1; + } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_1)) { + config_index = 1; + config_quirk = 1; + } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_2)) { + config_index = 2; + config_quirk = 1; + } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_3)) { + config_index = 3; + config_quirk = 1; + } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_4)) { + config_index = 4; + config_quirk = 1; + } else { + config_index = 0; + config_quirk = 0; + } + + set_config_failed = 0; +repeat_set_config: + + DPRINTF("setting config %u\n", config_index); + + /* get the USB device configured */ + err = usbd_set_config_index(udev, config_index); + if (err) { + if (udev->ddesc.bNumConfigurations != 0) { + if (!set_config_failed) { + set_config_failed = 1; + /* XXX try to re-enumerate the device */ + err = usbd_req_re_enumerate(udev, NULL); + if (err == 0) + goto repeat_set_config; + } + DPRINTFN(0, "Failure selecting configuration index %u:" + "%s, port %u, addr %u (ignored)\n", + config_index, usbd_errstr(err), udev->port_no, + udev->address); + } + /* + * Some USB devices do not have any configurations. Ignore any + * set config failures! + */ + err = 0; + goto config_done; + } + if (!config_quirk && config_index + 1 < udev->ddesc.bNumConfigurations) { + if ((udev->cdesc->bNumInterface < 2) && + usbd_get_no_descriptors(udev->cdesc, UDESC_ENDPOINT) == 0) { + DPRINTFN(0, "Found no endpoints, trying next config\n"); + config_index++; + goto repeat_set_config; + } + if (config_index == 0) { + /* + * Try to figure out if we have an + * auto-install disk there: + */ + if (usb_iface_is_cdrom(udev, 0)) { + DPRINTFN(0, "Found possible auto-install " + "disk (trying next config)\n"); + config_index++; + goto repeat_set_config; + } + } + } + if (set_config_failed == 0 && config_index == 0 && + usb_test_quirk(&uaa, UQ_MSC_NO_SYNC_CACHE) == 0 && + usb_test_quirk(&uaa, UQ_MSC_NO_GETMAXLUN) == 0) { + + /* + * Try to figure out if there are any MSC quirks we + * should apply automatically: + */ + err = usb_msc_auto_quirk(udev, 0); + + if (err != 0) { + set_config_failed = 1; + goto repeat_set_config; + } + } + +config_done: + DPRINTF("new dev (addr %d), udev=%p, parent_hub=%p\n", + udev->address, udev, udev->parent_hub); + + /* register our device - we are ready */ + usb_bus_port_set_device(bus, parent_hub ? + parent_hub->hub->ports + port_index : NULL, udev, device_index); + +#if USB_HAVE_UGEN + /* Symlink the ugen device name */ + udev->ugen_symlink = usb_alloc_symlink(udev->ugen_name); + + /* Announce device */ + printf("%s: <%s> at %s\n", udev->ugen_name, + usb_get_manufacturer(udev), + device_get_nameunit(udev->bus->bdev)); +#endif + +#if USB_HAVE_DEVCTL + usb_notify_addq("ATTACH", udev); +#endif +done: + if (err) { + /* + * Free USB device and all subdevices, if any. + */ + usb_free_device(udev, 0); + udev = NULL; + } + return (udev); +} + +#if USB_HAVE_UGEN +struct usb_fs_privdata * +usb_make_dev(struct usb_device *udev, const char *devname, int ep, + int fi, int rwmode, uid_t uid, gid_t gid, int mode) +{ + struct usb_fs_privdata* pd; + char buffer[32]; + + /* Store information to locate ourselves again later */ + pd = malloc(sizeof(struct usb_fs_privdata), M_USBDEV, + M_WAITOK | M_ZERO); + pd->bus_index = device_get_unit(udev->bus->bdev); + pd->dev_index = udev->device_index; + pd->ep_addr = ep; + pd->fifo_index = fi; + pd->mode = rwmode; + + /* Now, create the device itself */ + if (devname == NULL) { + devname = buffer; + snprintf(buffer, sizeof(buffer), USB_DEVICE_DIR "/%u.%u.%u", + pd->bus_index, pd->dev_index, pd->ep_addr); + } + + pd->cdev = make_dev(&usb_devsw, 0, uid, gid, mode, "%s", devname); + + if (pd->cdev == NULL) { + DPRINTFN(0, "Failed to create device %s\n", devname); + free(pd, M_USBDEV); + return (NULL); + } + + /* XXX setting si_drv1 and creating the device is not atomic! */ + pd->cdev->si_drv1 = pd; + + return (pd); +} + +void +usb_destroy_dev(struct usb_fs_privdata *pd) +{ + if (pd == NULL) + return; + + destroy_dev(pd->cdev); + + free(pd, M_USBDEV); +} + +static void +usb_cdev_create(struct usb_device *udev) +{ + struct usb_config_descriptor *cd; + struct usb_endpoint_descriptor *ed; + struct usb_descriptor *desc; + struct usb_fs_privdata* pd; + int inmode, outmode, inmask, outmask, mode; + uint8_t ep; + + KASSERT(LIST_FIRST(&udev->pd_list) == NULL, ("stale cdev entries")); + + DPRINTFN(2, "Creating device nodes\n"); + + if (usbd_get_mode(udev) == USB_MODE_DEVICE) { + inmode = FWRITE; + outmode = FREAD; + } else { /* USB_MODE_HOST */ + inmode = FREAD; + outmode = FWRITE; + } + + inmask = 0; + outmask = 0; + desc = NULL; + + /* + * Collect all used endpoint numbers instead of just + * generating 16 static endpoints. + */ + cd = usbd_get_config_descriptor(udev); + while ((desc = usb_desc_foreach(cd, desc))) { + /* filter out all endpoint descriptors */ + if ((desc->bDescriptorType == UDESC_ENDPOINT) && + (desc->bLength >= sizeof(*ed))) { + ed = (struct usb_endpoint_descriptor *)desc; + + /* update masks */ + ep = ed->bEndpointAddress; + if (UE_GET_DIR(ep) == UE_DIR_OUT) + outmask |= 1 << UE_GET_ADDR(ep); + else + inmask |= 1 << UE_GET_ADDR(ep); + } + } + + /* Create all available endpoints except EP0 */ + for (ep = 1; ep < 16; ep++) { + mode = (inmask & (1 << ep)) ? inmode : 0; + mode |= (outmask & (1 << ep)) ? outmode : 0; + if (mode == 0) + continue; /* no IN or OUT endpoint */ + + pd = usb_make_dev(udev, NULL, ep, 0, + mode, UID_ROOT, GID_OPERATOR, 0600); + + if (pd != NULL) + LIST_INSERT_HEAD(&udev->pd_list, pd, pd_next); + } +} + +static void +usb_cdev_free(struct usb_device *udev) +{ + struct usb_fs_privdata* pd; + + DPRINTFN(2, "Freeing device nodes\n"); + + while ((pd = LIST_FIRST(&udev->pd_list)) != NULL) { + KASSERT(pd->cdev->si_drv1 == pd, ("privdata corrupt")); + + LIST_REMOVE(pd, pd_next); + + usb_destroy_dev(pd); + } +} +#endif + +/*------------------------------------------------------------------------* + * usb_free_device + * + * This function is NULL safe and will free an USB device and its + * children devices, if any. + * + * Flag values: Reserved, set to zero. + *------------------------------------------------------------------------*/ +void +usb_free_device(struct usb_device *udev, uint8_t flag) +{ + struct usb_bus *bus; + + if (udev == NULL) + return; /* already freed */ + + DPRINTFN(4, "udev=%p port=%d\n", udev, udev->port_no); + + bus = udev->bus; + usb_set_device_state(udev, USB_STATE_DETACHED); + +#if USB_HAVE_DEVCTL + usb_notify_addq("DETACH", udev); +#endif + +#if USB_HAVE_UGEN + printf("%s: <%s> at %s (disconnected)\n", udev->ugen_name, + usb_get_manufacturer(udev), device_get_nameunit(bus->bdev)); + + /* Destroy UGEN symlink, if any */ + if (udev->ugen_symlink) { + usb_free_symlink(udev->ugen_symlink); + udev->ugen_symlink = NULL; + } +#endif + /* + * Unregister our device first which will prevent any further + * references: + */ + usb_bus_port_set_device(bus, udev->parent_hub ? + udev->parent_hub->hub->ports + udev->port_index : NULL, + NULL, USB_ROOT_HUB_ADDR); + +#if USB_HAVE_UGEN + /* wait for all pending references to go away: */ + mtx_lock(&usb_ref_lock); + udev->refcount--; + while (udev->refcount != 0) { + cv_wait(&udev->ref_cv, &usb_ref_lock); + } + mtx_unlock(&usb_ref_lock); + + usb_destroy_dev(udev->ctrl_dev); +#endif + + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + /* stop receiving any control transfers (Device Side Mode) */ + usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX); + } + + /* the following will get the device unconfigured in software */ + usb_unconfigure(udev, USB_UNCFG_FLAG_FREE_EP0); + + /* unsetup any leftover default USB transfers */ + usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX); + + /* template unsetup, if any */ + (usb_temp_unsetup_p) (udev); + + /* + * Make sure that our clear-stall messages are not queued + * anywhere: + */ + USB_BUS_LOCK(udev->bus); + usb_proc_mwait(&udev->bus->non_giant_callback_proc, + &udev->cs_msg[0], &udev->cs_msg[1]); + USB_BUS_UNLOCK(udev->bus); + + sx_destroy(&udev->ctrl_sx); + sx_destroy(&udev->enum_sx); + sx_destroy(&udev->sr_sx); + + cv_destroy(&udev->ctrlreq_cv); + cv_destroy(&udev->ref_cv); + + mtx_destroy(&udev->device_mtx); +#if USB_HAVE_UGEN + KASSERT(LIST_FIRST(&udev->pd_list) == NULL, ("leaked cdev entries")); +#endif + + /* Uninitialise device */ + if (bus->methods->device_uninit != NULL) + (bus->methods->device_uninit) (udev); + + /* free device */ + free(udev->serial, M_USB); + free(udev->manufacturer, M_USB); + free(udev->product, M_USB); + free(udev, M_USB); +} + +/*------------------------------------------------------------------------* + * usbd_get_iface + * + * This function is the safe way to get the USB interface structure + * pointer by interface index. + * + * Return values: + * NULL: Interface not present. + * Else: Pointer to USB interface structure. + *------------------------------------------------------------------------*/ +struct usb_interface * +usbd_get_iface(struct usb_device *udev, uint8_t iface_index) +{ + struct usb_interface *iface = udev->ifaces + iface_index; + + if (iface_index >= udev->ifaces_max) + return (NULL); + return (iface); +} + +/*------------------------------------------------------------------------* + * usbd_find_descriptor + * + * This function will lookup the first descriptor that matches the + * criteria given by the arguments "type" and "subtype". Descriptors + * will only be searched within the interface having the index + * "iface_index". If the "id" argument points to an USB descriptor, + * it will be skipped before the search is started. This allows + * searching for multiple descriptors using the same criteria. Else + * the search is started after the interface descriptor. + * + * Return values: + * NULL: End of descriptors + * Else: A descriptor matching the criteria + *------------------------------------------------------------------------*/ +void * +usbd_find_descriptor(struct usb_device *udev, void *id, uint8_t iface_index, + uint8_t type, uint8_t type_mask, + uint8_t subtype, uint8_t subtype_mask) +{ + struct usb_descriptor *desc; + struct usb_config_descriptor *cd; + struct usb_interface *iface; + + cd = usbd_get_config_descriptor(udev); + if (cd == NULL) { + return (NULL); + } + if (id == NULL) { + iface = usbd_get_iface(udev, iface_index); + if (iface == NULL) { + return (NULL); + } + id = usbd_get_interface_descriptor(iface); + if (id == NULL) { + return (NULL); + } + } + desc = (void *)id; + + while ((desc = usb_desc_foreach(cd, desc))) { + + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + if (((desc->bDescriptorType & type_mask) == type) && + ((desc->bDescriptorSubtype & subtype_mask) == subtype)) { + return (desc); + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_devinfo + * + * This function will dump information from the device descriptor + * belonging to the USB device pointed to by "udev", to the string + * pointed to by "dst_ptr" having a maximum length of "dst_len" bytes + * including the terminating zero. + *------------------------------------------------------------------------*/ +void +usb_devinfo(struct usb_device *udev, char *dst_ptr, uint16_t dst_len) +{ + struct usb_device_descriptor *udd = &udev->ddesc; + uint16_t bcdDevice; + uint16_t bcdUSB; + + bcdUSB = UGETW(udd->bcdUSB); + bcdDevice = UGETW(udd->bcdDevice); + + if (udd->bDeviceClass != 0xFF) { + snprintf(dst_ptr, dst_len, "%s %s, class %d/%d, rev %x.%02x/" + "%x.%02x, addr %d", + usb_get_manufacturer(udev), + usb_get_product(udev), + udd->bDeviceClass, udd->bDeviceSubClass, + (bcdUSB >> 8), bcdUSB & 0xFF, + (bcdDevice >> 8), bcdDevice & 0xFF, + udev->address); + } else { + snprintf(dst_ptr, dst_len, "%s %s, rev %x.%02x/" + "%x.%02x, addr %d", + usb_get_manufacturer(udev), + usb_get_product(udev), + (bcdUSB >> 8), bcdUSB & 0xFF, + (bcdDevice >> 8), bcdDevice & 0xFF, + udev->address); + } +} + +#ifdef USB_VERBOSE +/* + * Descriptions of of known vendors and devices ("products"). + */ +struct usb_knowndev { + uint16_t vendor; + uint16_t product; + uint32_t flags; + const char *vendorname; + const char *productname; +}; + +#define USB_KNOWNDEV_NOPROD 0x01 /* match on vendor only */ + +#include "usbdevs.h" +#include "usbdevs_data.h" +#endif /* USB_VERBOSE */ + +static void +usbd_set_device_strings(struct usb_device *udev) +{ + struct usb_device_descriptor *udd = &udev->ddesc; +#ifdef USB_VERBOSE + const struct usb_knowndev *kdp; +#endif + char *temp_ptr; + size_t temp_size; + uint16_t vendor_id; + uint16_t product_id; + + temp_ptr = (char *)udev->bus->scratch[0].data; + temp_size = sizeof(udev->bus->scratch[0].data); + + vendor_id = UGETW(udd->idVendor); + product_id = UGETW(udd->idProduct); + + /* get serial number string */ + usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size, + udev->ddesc.iSerialNumber); + udev->serial = strdup(temp_ptr, M_USB); + + /* get manufacturer string */ + usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size, + udev->ddesc.iManufacturer); + usb_trim_spaces(temp_ptr); + if (temp_ptr[0] != '\0') + udev->manufacturer = strdup(temp_ptr, M_USB); + + /* get product string */ + usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size, + udev->ddesc.iProduct); + usb_trim_spaces(temp_ptr); + if (temp_ptr[0] != '\0') + udev->product = strdup(temp_ptr, M_USB); + +#ifdef USB_VERBOSE + if (udev->manufacturer == NULL || udev->product == NULL) { + for (kdp = usb_knowndevs; kdp->vendorname != NULL; kdp++) { + if (kdp->vendor == vendor_id && + (kdp->product == product_id || + (kdp->flags & USB_KNOWNDEV_NOPROD) != 0)) + break; + } + if (kdp->vendorname != NULL) { + /* XXX should use pointer to knowndevs string */ + if (udev->manufacturer == NULL) { + udev->manufacturer = strdup(kdp->vendorname, + M_USB); + } + if (udev->product == NULL && + (kdp->flags & USB_KNOWNDEV_NOPROD) == 0) { + udev->product = strdup(kdp->productname, + M_USB); + } + } + } +#endif + /* Provide default strings if none were found */ + if (udev->manufacturer == NULL) { + snprintf(temp_ptr, temp_size, "vendor 0x%04x", vendor_id); + udev->manufacturer = strdup(temp_ptr, M_USB); + } + if (udev->product == NULL) { + snprintf(temp_ptr, temp_size, "product 0x%04x", product_id); + udev->product = strdup(temp_ptr, M_USB); + } +} + +/* + * Returns: + * See: USB_MODE_XXX + */ +enum usb_hc_mode +usbd_get_mode(struct usb_device *udev) +{ + return (udev->flags.usb_mode); +} + +/* + * Returns: + * See: USB_SPEED_XXX + */ +enum usb_dev_speed +usbd_get_speed(struct usb_device *udev) +{ + return (udev->speed); +} + +uint32_t +usbd_get_isoc_fps(struct usb_device *udev) +{ + ; /* indent fix */ + switch (udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + return (1000); + default: + return (8000); + } +} + +struct usb_device_descriptor * +usbd_get_device_descriptor(struct usb_device *udev) +{ + if (udev == NULL) + return (NULL); /* be NULL safe */ + return (&udev->ddesc); +} + +struct usb_config_descriptor * +usbd_get_config_descriptor(struct usb_device *udev) +{ + if (udev == NULL) + return (NULL); /* be NULL safe */ + return (udev->cdesc); +} + +/*------------------------------------------------------------------------* + * usb_test_quirk - test a device for a given quirk + * + * Return values: + * 0: The USB device does not have the given quirk. + * Else: The USB device has the given quirk. + *------------------------------------------------------------------------*/ +uint8_t +usb_test_quirk(const struct usb_attach_arg *uaa, uint16_t quirk) +{ + uint8_t found; + uint8_t x; + + if (quirk == UQ_NONE) + return (0); + + /* search the automatic per device quirks first */ + + for (x = 0; x != USB_MAX_AUTO_QUIRK; x++) { + if (uaa->device->autoQuirk[x] == quirk) + return (1); + } + + /* search global quirk table, if any */ + + found = (usb_test_quirk_p) (&uaa->info, quirk); + + return (found); +} + +struct usb_interface_descriptor * +usbd_get_interface_descriptor(struct usb_interface *iface) +{ + if (iface == NULL) + return (NULL); /* be NULL safe */ + return (iface->idesc); +} + +uint8_t +usbd_get_interface_altindex(struct usb_interface *iface) +{ + return (iface->alt_index); +} + +uint8_t +usbd_get_bus_index(struct usb_device *udev) +{ + return ((uint8_t)device_get_unit(udev->bus->bdev)); +} + +uint8_t +usbd_get_device_index(struct usb_device *udev) +{ + return (udev->device_index); +} + +#if USB_HAVE_DEVCTL +static void +usb_notify_addq(const char *type, struct usb_device *udev) +{ + struct usb_interface *iface; + struct sbuf *sb; + int i; + + /* announce the device */ + sb = sbuf_new_auto(); + sbuf_printf(sb, +#if USB_HAVE_UGEN + "ugen=%s " + "cdev=%s " +#endif + "vendor=0x%04x " + "product=0x%04x " + "devclass=0x%02x " + "devsubclass=0x%02x " + "sernum=\"%s\" " + "release=0x%04x " + "mode=%s " + "port=%u " +#if USB_HAVE_UGEN + "parent=%s" +#endif + "", +#if USB_HAVE_UGEN + udev->ugen_name, + udev->ugen_name, +#endif + UGETW(udev->ddesc.idVendor), + UGETW(udev->ddesc.idProduct), + udev->ddesc.bDeviceClass, + udev->ddesc.bDeviceSubClass, + usb_get_serial(udev), + UGETW(udev->ddesc.bcdDevice), + (udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device", + udev->port_no +#if USB_HAVE_UGEN + , udev->parent_hub != NULL ? + udev->parent_hub->ugen_name : + device_get_nameunit(device_get_parent(udev->bus->bdev)) +#endif + ); + sbuf_finish(sb); + devctl_notify("USB", "DEVICE", type, sbuf_data(sb)); + sbuf_delete(sb); + + /* announce each interface */ + for (i = 0; i < USB_IFACE_MAX; i++) { + iface = usbd_get_iface(udev, i); + if (iface == NULL) + break; /* end of interfaces */ + if (iface->idesc == NULL) + continue; /* no interface descriptor */ + + sb = sbuf_new_auto(); + sbuf_printf(sb, +#if USB_HAVE_UGEN + "ugen=%s " + "cdev=%s " +#endif + "vendor=0x%04x " + "product=0x%04x " + "devclass=0x%02x " + "devsubclass=0x%02x " + "sernum=\"%s\" " + "release=0x%04x " + "mode=%s " + "interface=%d " + "endpoints=%d " + "intclass=0x%02x " + "intsubclass=0x%02x " + "intprotocol=0x%02x", +#if USB_HAVE_UGEN + udev->ugen_name, + udev->ugen_name, +#endif + UGETW(udev->ddesc.idVendor), + UGETW(udev->ddesc.idProduct), + udev->ddesc.bDeviceClass, + udev->ddesc.bDeviceSubClass, + usb_get_serial(udev), + UGETW(udev->ddesc.bcdDevice), + (udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device", + iface->idesc->bInterfaceNumber, + iface->idesc->bNumEndpoints, + iface->idesc->bInterfaceClass, + iface->idesc->bInterfaceSubClass, + iface->idesc->bInterfaceProtocol); + sbuf_finish(sb); + devctl_notify("USB", "INTERFACE", type, sbuf_data(sb)); + sbuf_delete(sb); + } +} +#endif + +#if USB_HAVE_UGEN +/*------------------------------------------------------------------------* + * usb_fifo_free_wrap + * + * This function will free the FIFOs. + * + * Description of "flag" argument: If the USB_UNCFG_FLAG_FREE_EP0 flag + * is set and "iface_index" is set to "USB_IFACE_INDEX_ANY", we free + * all FIFOs. If the USB_UNCFG_FLAG_FREE_EP0 flag is not set and + * "iface_index" is set to "USB_IFACE_INDEX_ANY", we free all non + * control endpoint FIFOs. If "iface_index" is not set to + * "USB_IFACE_INDEX_ANY" the flag has no effect. + *------------------------------------------------------------------------*/ +static void +usb_fifo_free_wrap(struct usb_device *udev, + uint8_t iface_index, uint8_t flag) +{ + struct usb_fifo *f; + uint16_t i; + + /* + * Free any USB FIFOs on the given interface: + */ + for (i = 0; i != USB_FIFO_MAX; i++) { + f = udev->fifo[i]; + if (f == NULL) { + continue; + } + /* Check if the interface index matches */ + if (iface_index == f->iface_index) { + if (f->methods != &usb_ugen_methods) { + /* + * Don't free any non-generic FIFOs in + * this case. + */ + continue; + } + if ((f->dev_ep_index == 0) && + (f->fs_xfer == NULL)) { + /* no need to free this FIFO */ + continue; + } + } else if (iface_index == USB_IFACE_INDEX_ANY) { + if ((f->methods == &usb_ugen_methods) && + (f->dev_ep_index == 0) && + (!(flag & USB_UNCFG_FLAG_FREE_EP0)) && + (f->fs_xfer == NULL)) { + /* no need to free this FIFO */ + continue; + } + } else { + /* no need to free this FIFO */ + continue; + } + /* free this FIFO */ + usb_fifo_free(f); + } +} +#endif + +/*------------------------------------------------------------------------* + * usb_peer_can_wakeup + * + * Return values: + * 0: Peer cannot do resume signalling. + * Else: Peer can do resume signalling. + *------------------------------------------------------------------------*/ +uint8_t +usb_peer_can_wakeup(struct usb_device *udev) +{ + const struct usb_config_descriptor *cdp; + + cdp = udev->cdesc; + if ((cdp != NULL) && (udev->flags.usb_mode == USB_MODE_HOST)) { + return (cdp->bmAttributes & UC_REMOTE_WAKEUP); + } + return (0); /* not supported */ +} + +void +usb_set_device_state(struct usb_device *udev, enum usb_dev_state state) +{ + + KASSERT(state < USB_STATE_MAX, ("invalid udev state")); + + DPRINTF("udev %p state %s -> %s\n", udev, + usb_statestr(udev->state), usb_statestr(state)); + udev->state = state; + + if (udev->bus->methods->device_state_change != NULL) + (udev->bus->methods->device_state_change) (udev); +} + +enum usb_dev_state +usb_get_device_state(struct usb_device *udev) +{ + if (udev == NULL) + return (USB_STATE_DETACHED); + return (udev->state); +} + +uint8_t +usbd_device_attached(struct usb_device *udev) +{ + return (udev->state > USB_STATE_DETACHED); +} + +/* The following function locks enumerating the given USB device. */ + +void +usbd_enum_lock(struct usb_device *udev) +{ + sx_xlock(&udev->enum_sx); + sx_xlock(&udev->sr_sx); + /* + * NEWBUS LOCK NOTE: We should check if any parent SX locks + * are locked before locking Giant. Else the lock can be + * locked multiple times. + */ + mtx_lock(&Giant); +} + +/* The following function unlocks enumerating the given USB device. */ + +void +usbd_enum_unlock(struct usb_device *udev) +{ + mtx_unlock(&Giant); + sx_xunlock(&udev->enum_sx); + sx_xunlock(&udev->sr_sx); +} + +/* The following function locks suspend and resume. */ + +void +usbd_sr_lock(struct usb_device *udev) +{ + sx_xlock(&udev->sr_sx); + /* + * NEWBUS LOCK NOTE: We should check if any parent SX locks + * are locked before locking Giant. Else the lock can be + * locked multiple times. + */ + mtx_lock(&Giant); +} + +/* The following function unlocks suspend and resume. */ + +void +usbd_sr_unlock(struct usb_device *udev) +{ + mtx_unlock(&Giant); + sx_xunlock(&udev->sr_sx); +} + +/* + * The following function checks the enumerating lock for the given + * USB device. + */ + +uint8_t +usbd_enum_is_locked(struct usb_device *udev) +{ + return (sx_xlocked(&udev->enum_sx)); +} + +/* + * The following function is used to set the per-interface specific + * plug and play information. The string referred to by the pnpinfo + * argument can safely be freed after calling this function. The + * pnpinfo of an interface will be reset at device detach or when + * passing a NULL argument to this function. This function + * returns zero on success, else a USB_ERR_XXX failure code. + */ + +usb_error_t +usbd_set_pnpinfo(struct usb_device *udev, uint8_t iface_index, const char *pnpinfo) +{ + struct usb_interface *iface; + + iface = usbd_get_iface(udev, iface_index); + if (iface == NULL) + return (USB_ERR_INVAL); + + if (iface->pnpinfo != NULL) { + free(iface->pnpinfo, M_USBDEV); + iface->pnpinfo = NULL; + } + + if (pnpinfo == NULL || pnpinfo[0] == 0) + return (0); /* success */ + + iface->pnpinfo = strdup(pnpinfo, M_USBDEV); + if (iface->pnpinfo == NULL) + return (USB_ERR_NOMEM); + + return (0); /* success */ +} + +usb_error_t +usbd_add_dynamic_quirk(struct usb_device *udev, uint16_t quirk) +{ + uint8_t x; + + for (x = 0; x != USB_MAX_AUTO_QUIRK; x++) { + if (udev->autoQuirk[x] == 0 || + udev->autoQuirk[x] == quirk) { + udev->autoQuirk[x] = quirk; + return (0); /* success */ + } + } + return (USB_ERR_NOMEM); +} diff --git a/sys/bus/u4b/usb_device.h b/sys/bus/u4b/usb_device.h new file mode 100644 index 0000000000..bde20b0790 --- /dev/null +++ b/sys/bus/u4b/usb_device.h @@ -0,0 +1,236 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_DEVICE_H_ +#define _USB_DEVICE_H_ + +struct usb_symlink; /* UGEN */ +struct usb_device; /* linux compat */ +struct usb_fs_privdata; + +#define USB_CTRL_XFER_MAX 2 + +/* "usb_parse_config()" commands */ + +#define USB_CFG_ALLOC 0 +#define USB_CFG_FREE 1 +#define USB_CFG_INIT 2 + +/* "usb_unconfigure()" flags */ + +#define USB_UNCFG_FLAG_NONE 0x00 +#define USB_UNCFG_FLAG_FREE_EP0 0x02 /* endpoint zero is freed */ + +struct usb_clear_stall_msg { + struct usb_proc_msg hdr; + struct usb_device *udev; +}; + +/* The following four structures makes up a tree, where we have the + * leaf structure, "usb_host_endpoint", first, and the root structure, + * "usb_device", last. The four structures below mirror the structure + * of the USB descriptors belonging to an USB configuration. Please + * refer to the USB specification for a definition of "endpoints" and + * "interfaces". + */ +struct usb_host_endpoint { + struct usb_endpoint_descriptor desc; + TAILQ_HEAD(, urb) bsd_urb_list; + struct usb_xfer *bsd_xfer[2]; + uint8_t *extra; /* Extra descriptors */ + usb_frlength_t fbsd_buf_size; + uint16_t extralen; + uint8_t bsd_iface_index; +} __aligned(USB_HOST_ALIGN); + +struct usb_host_interface { + struct usb_interface_descriptor desc; + /* the following array has size "desc.bNumEndpoint" */ + struct usb_host_endpoint *endpoint; + const char *string; /* iInterface string, if present */ + uint8_t *extra; /* Extra descriptors */ + uint16_t extralen; + uint8_t bsd_iface_index; +} __aligned(USB_HOST_ALIGN); + +/* + * The following structure defines the USB device flags. + */ +struct usb_device_flags { + enum usb_hc_mode usb_mode; /* host or device mode */ + uint8_t self_powered:1; /* set if USB device is self powered */ + uint8_t no_strings:1; /* set if USB device does not support + * strings */ + uint8_t remote_wakeup:1; /* set if remote wakeup is enabled */ + uint8_t uq_bus_powered:1; /* set if BUS powered quirk is present */ + + /* + * NOTE: Although the flags below will reach the same value + * over time, but the instant values may differ, and + * consequently the flags cannot be merged into one! + */ + uint8_t peer_suspended:1; /* set if peer is suspended */ + uint8_t self_suspended:1; /* set if self is suspended */ +}; + +/* + * The following structure is used for power-save purposes. The data + * in this structure is protected by the USB BUS lock. + */ +struct usb_power_save { + usb_ticks_t last_xfer_time; /* copy of "ticks" */ + usb_size_t type_refs[4]; /* transfer reference count */ + usb_size_t read_refs; /* data read references */ + usb_size_t write_refs; /* data write references */ +}; + +/* + * The following structure defines an USB device. There exists one of + * these structures for every USB device. + */ +struct usb_device { + struct usb_clear_stall_msg cs_msg[2]; /* generic clear stall + * messages */ + struct sx ctrl_sx; + struct sx enum_sx; + struct sx sr_sx; + struct mtx device_mtx; + struct cv ctrlreq_cv; + struct cv ref_cv; + struct usb_interface *ifaces; + struct usb_endpoint ctrl_ep; /* Control Endpoint 0 */ + struct usb_endpoint *endpoints; + struct usb_power_save pwr_save;/* power save data */ + struct usb_bus *bus; /* our USB BUS */ + device_t parent_dev; /* parent device */ + struct usb_device *parent_hub; + struct usb_device *parent_hs_hub; /* high-speed parent HUB */ + struct usb_config_descriptor *cdesc; /* full config descr */ + struct usb_hub *hub; /* only if this is a hub */ + struct usb_xfer *ctrl_xfer[USB_CTRL_XFER_MAX]; + struct usb_temp_data *usb_template_ptr; + struct usb_endpoint *ep_curr; /* current clear stall endpoint */ +#if USB_HAVE_UGEN + struct usb_fifo *fifo[USB_FIFO_MAX]; + struct usb_symlink *ugen_symlink; /* our generic symlink */ + struct usb_fs_privdata *ctrl_dev; /* Control Endpoint 0 device node */ + LIST_HEAD(,usb_fs_privdata) pd_list; + char ugen_name[20]; /* name of ugenX.X device */ +#endif + usb_ticks_t plugtime; /* copy of "ticks" */ + + enum usb_dev_state state; + enum usb_dev_speed speed; + uint16_t refcount; +#define USB_DEV_REF_MAX 0xffff + + uint16_t power; /* mA the device uses */ + uint16_t langid; /* language for strings */ + uint16_t autoQuirk[USB_MAX_AUTO_QUIRK]; /* dynamic quirks */ + + uint8_t address; /* device addess */ + uint8_t device_index; /* device index in "bus->devices" */ + uint8_t controller_slot_id; /* controller specific value */ + uint8_t curr_config_index; /* current configuration index */ + uint8_t curr_config_no; /* current configuration number */ + uint8_t depth; /* distance from root HUB */ + uint8_t port_index; /* parent HUB port index */ + uint8_t port_no; /* parent HUB port number */ + uint8_t hs_hub_addr; /* high-speed HUB address */ + uint8_t hs_port_no; /* high-speed HUB port number */ + uint8_t driver_added_refcount; /* our driver added generation count */ + uint8_t power_mode; /* see USB_POWER_XXX */ + uint8_t re_enumerate_wait; /* set if re-enum. is in progress */ + uint8_t ifaces_max; /* number of interfaces present */ + uint8_t endpoints_max; /* number of endpoints present */ + + /* the "flags" field is write-protected by "bus->mtx" */ + + struct usb_device_flags flags; + + struct usb_endpoint_descriptor ctrl_ep_desc; /* for endpoint 0 */ + struct usb_endpoint_ss_comp_descriptor ctrl_ep_comp_desc; /* for endpoint 0 */ + struct usb_device_descriptor ddesc; /* device descriptor */ + + char *serial; /* serial number, can be NULL */ + char *manufacturer; /* manufacturer string, can be NULL */ + char *product; /* product string, can be NULL */ + +#if USB_HAVE_COMPAT_LINUX + /* Linux compat */ + struct usb_device_descriptor descriptor; + struct usb_host_endpoint ep0; + struct usb_interface *linux_iface_start; + struct usb_interface *linux_iface_end; + struct usb_host_endpoint *linux_endpoint_start; + struct usb_host_endpoint *linux_endpoint_end; + uint16_t devnum; +#endif + + uint32_t clear_stall_errors; /* number of clear-stall failures */ +}; + +/* globals */ + +extern int usb_template; + +/* function prototypes */ + +const char *usb_statestr(enum usb_dev_state state); +struct usb_device *usb_alloc_device(device_t parent_dev, struct usb_bus *bus, + struct usb_device *parent_hub, uint8_t depth, + uint8_t port_index, uint8_t port_no, + enum usb_dev_speed speed, enum usb_hc_mode mode); +#if USB_HAVE_UGEN +struct usb_fs_privdata *usb_make_dev(struct usb_device *, const char *, + int, int, int, uid_t, gid_t, int); +void usb_destroy_dev(struct usb_fs_privdata *); +#endif +usb_error_t usb_probe_and_attach(struct usb_device *udev, + uint8_t iface_index); +void usb_detach_device(struct usb_device *, uint8_t, uint8_t); +usb_error_t usb_reset_iface_endpoints(struct usb_device *udev, + uint8_t iface_index); +usb_error_t usbd_set_config_index(struct usb_device *udev, uint8_t index); +usb_error_t usbd_set_endpoint_stall(struct usb_device *udev, + struct usb_endpoint *ep, uint8_t do_stall); +usb_error_t usb_suspend_resume(struct usb_device *udev, + uint8_t do_suspend); +void usb_devinfo(struct usb_device *udev, char *dst_ptr, uint16_t dst_len); +void usb_free_device(struct usb_device *, uint8_t); +void usb_linux_free_device(struct usb_device *dev); +uint8_t usb_peer_can_wakeup(struct usb_device *udev); +struct usb_endpoint *usb_endpoint_foreach(struct usb_device *udev, struct usb_endpoint *ep); +void usb_set_device_state(struct usb_device *, enum usb_dev_state); +enum usb_dev_state usb_get_device_state(struct usb_device *); + +void usbd_enum_lock(struct usb_device *); +void usbd_enum_unlock(struct usb_device *); +void usbd_sr_lock(struct usb_device *); +void usbd_sr_unlock(struct usb_device *); +uint8_t usbd_enum_is_locked(struct usb_device *); + +#endif /* _USB_DEVICE_H_ */ diff --git a/sys/bus/u4b/usb_dynamic.c b/sys/bus/u4b/usb_dynamic.c new file mode 100644 index 0000000000..1358b30cd0 --- /dev/null +++ b/sys/bus/u4b/usb_dynamic.c @@ -0,0 +1,148 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +/* function prototypes */ +static usb_handle_req_t usb_temp_get_desc_w; +static usb_temp_setup_by_index_t usb_temp_setup_by_index_w; +static usb_temp_unsetup_t usb_temp_unsetup_w; +static usb_test_quirk_t usb_test_quirk_w; +static usb_quirk_ioctl_t usb_quirk_ioctl_w; + +/* global variables */ +usb_handle_req_t *usb_temp_get_desc_p = &usb_temp_get_desc_w; +usb_temp_setup_by_index_t *usb_temp_setup_by_index_p = &usb_temp_setup_by_index_w; +usb_temp_unsetup_t *usb_temp_unsetup_p = &usb_temp_unsetup_w; +usb_test_quirk_t *usb_test_quirk_p = &usb_test_quirk_w; +usb_quirk_ioctl_t *usb_quirk_ioctl_p = &usb_quirk_ioctl_w; +devclass_t usb_devclass_ptr = NULL; + +static usb_error_t +usb_temp_setup_by_index_w(struct usb_device *udev, uint16_t index) +{ + return (USB_ERR_INVAL); +} + +static uint8_t +usb_test_quirk_w(const struct usbd_lookup_info *info, uint16_t quirk) +{ + return (0); /* no match */ +} + +static int +usb_quirk_ioctl_w(unsigned long cmd, caddr_t data, int fflag, struct thread *td) +{ + return (ENOIOCTL); +} + +static usb_error_t +usb_temp_get_desc_w(struct usb_device *udev, struct usb_device_request *req, const void **pPtr, uint16_t *pLength) +{ + /* stall */ + return (USB_ERR_STALLED); +} + +static void +usb_temp_unsetup_w(struct usb_device *udev) +{ + if (udev->usb_template_ptr) { + + free(udev->usb_template_ptr, M_USB); + + udev->usb_template_ptr = NULL; + } +} + +void +usb_quirk_unload(void *arg) +{ + /* reset function pointers */ + + usb_test_quirk_p = &usb_test_quirk_w; + usb_quirk_ioctl_p = &usb_quirk_ioctl_w; + + /* wait for CPU to exit the loaded functions, if any */ + + /* XXX this is a tradeoff */ + + pause("WAIT", hz); +} + +void +usb_temp_unload(void *arg) +{ + /* reset function pointers */ + + usb_temp_get_desc_p = &usb_temp_get_desc_w; + usb_temp_setup_by_index_p = &usb_temp_setup_by_index_w; + usb_temp_unsetup_p = &usb_temp_unsetup_w; + + /* wait for CPU to exit the loaded functions, if any */ + + /* XXX this is a tradeoff */ + + pause("WAIT", hz); +} + +void +usb_bus_unload(void *arg) +{ + /* reset function pointers */ + + usb_devclass_ptr = NULL; + + /* wait for CPU to exit the loaded functions, if any */ + + /* XXX this is a tradeoff */ + + pause("WAIT", hz); +} diff --git a/sys/bus/u4b/usb_dynamic.h b/sys/bus/u4b/usb_dynamic.h new file mode 100644 index 0000000000..568494204c --- /dev/null +++ b/sys/bus/u4b/usb_dynamic.h @@ -0,0 +1,61 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_DYNAMIC_H_ +#define _USB_DYNAMIC_H_ + +/* prototypes */ + +struct usb_device; +struct usbd_lookup_info; +struct usb_device_request; + +/* typedefs */ + +typedef usb_error_t (usb_temp_setup_by_index_t)(struct usb_device *udev, + uint16_t index); +typedef uint8_t (usb_test_quirk_t)(const struct usbd_lookup_info *info, + uint16_t quirk); +typedef int (usb_quirk_ioctl_t)(unsigned long cmd, caddr_t data, + int fflag, struct thread *td); +typedef void (usb_temp_unsetup_t)(struct usb_device *udev); + +/* global function pointers */ + +extern usb_handle_req_t *usb_temp_get_desc_p; +extern usb_temp_setup_by_index_t *usb_temp_setup_by_index_p; +extern usb_temp_unsetup_t *usb_temp_unsetup_p; +extern usb_test_quirk_t *usb_test_quirk_p; +extern usb_quirk_ioctl_t *usb_quirk_ioctl_p; +extern devclass_t usb_devclass_ptr; + +/* function prototypes */ + +void usb_temp_unload(void *); +void usb_quirk_unload(void *); +void usb_bus_unload(void *); + +#endif /* _USB_DYNAMIC_H_ */ diff --git a/sys/bus/u4b/usb_endian.h b/sys/bus/u4b/usb_endian.h new file mode 100644 index 0000000000..29479f1367 --- /dev/null +++ b/sys/bus/u4b/usb_endian.h @@ -0,0 +1,119 @@ +/* $FreeBSD$ */ +/* + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_ENDIAN_H_ +#define _USB_ENDIAN_H_ + +#include +#include + +/* + * Declare the basic USB record types. USB records have an alignment + * of 1 byte and are always packed. + */ +typedef uint8_t uByte; +typedef uint8_t uWord[2]; +typedef uint8_t uDWord[4]; +typedef uint8_t uQWord[8]; + +/* + * Define a set of macros that can get and set data independent of + * CPU endianness and CPU alignment requirements: + */ +#define UGETB(w) \ + ((w)[0]) + +#define UGETW(w) \ + ((w)[0] | \ + (((uint16_t)((w)[1])) << 8)) + +#define UGETDW(w) \ + ((w)[0] | \ + (((uint16_t)((w)[1])) << 8) | \ + (((uint32_t)((w)[2])) << 16) | \ + (((uint32_t)((w)[3])) << 24)) + +#define UGETQW(w) \ + ((w)[0] | \ + (((uint16_t)((w)[1])) << 8) | \ + (((uint32_t)((w)[2])) << 16) | \ + (((uint32_t)((w)[3])) << 24) | \ + (((uint64_t)((w)[4])) << 32) | \ + (((uint64_t)((w)[5])) << 40) | \ + (((uint64_t)((w)[6])) << 48) | \ + (((uint64_t)((w)[7])) << 56)) + +#define USETB(w,v) do { \ + (w)[0] = (uint8_t)(v); \ +} while (0) + +#define USETW(w,v) do { \ + (w)[0] = (uint8_t)(v); \ + (w)[1] = (uint8_t)((v) >> 8); \ +} while (0) + +#define USETDW(w,v) do { \ + (w)[0] = (uint8_t)(v); \ + (w)[1] = (uint8_t)((v) >> 8); \ + (w)[2] = (uint8_t)((v) >> 16); \ + (w)[3] = (uint8_t)((v) >> 24); \ +} while (0) + +#define USETQW(w,v) do { \ + (w)[0] = (uint8_t)(v); \ + (w)[1] = (uint8_t)((v) >> 8); \ + (w)[2] = (uint8_t)((v) >> 16); \ + (w)[3] = (uint8_t)((v) >> 24); \ + (w)[4] = (uint8_t)((v) >> 32); \ + (w)[5] = (uint8_t)((v) >> 40); \ + (w)[6] = (uint8_t)((v) >> 48); \ + (w)[7] = (uint8_t)((v) >> 56); \ +} while (0) + +#define USETW2(w,b1,b0) do { \ + (w)[0] = (uint8_t)(b0); \ + (w)[1] = (uint8_t)(b1); \ +} while (0) + +#define USETW4(w,b3,b2,b1,b0) do { \ + (w)[0] = (uint8_t)(b0); \ + (w)[1] = (uint8_t)(b1); \ + (w)[2] = (uint8_t)(b2); \ + (w)[3] = (uint8_t)(b3); \ +} while (0) + +#define USETW8(w,b7,b6,b5,b4,b3,b2,b1,b0) do { \ + (w)[0] = (uint8_t)(b0); \ + (w)[1] = (uint8_t)(b1); \ + (w)[2] = (uint8_t)(b2); \ + (w)[3] = (uint8_t)(b3); \ + (w)[4] = (uint8_t)(b4); \ + (w)[5] = (uint8_t)(b5); \ + (w)[6] = (uint8_t)(b6); \ + (w)[7] = (uint8_t)(b7); \ +} while (0) + +#endif /* _USB_ENDIAN_H_ */ diff --git a/sys/bus/u4b/usb_error.c b/sys/bus/u4b/usb_error.c new file mode 100644 index 0000000000..119c6175ca --- /dev/null +++ b/sys/bus/u4b/usb_error.c @@ -0,0 +1,90 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static const char* usb_errstr_table[USB_ERR_MAX] = { + [USB_ERR_NORMAL_COMPLETION] = "USB_ERR_NORMAL_COMPLETION", + [USB_ERR_PENDING_REQUESTS] = "USB_ERR_PENDING_REQUESTS", + [USB_ERR_NOT_STARTED] = "USB_ERR_NOT_STARTED", + [USB_ERR_INVAL] = "USB_ERR_INVAL", + [USB_ERR_NOMEM] = "USB_ERR_NOMEM", + [USB_ERR_CANCELLED] = "USB_ERR_CANCELLED", + [USB_ERR_BAD_ADDRESS] = "USB_ERR_BAD_ADDRESS", + [USB_ERR_BAD_BUFSIZE] = "USB_ERR_BAD_BUFSIZE", + [USB_ERR_BAD_FLAG] = "USB_ERR_BAD_FLAG", + [USB_ERR_NO_CALLBACK] = "USB_ERR_NO_CALLBACK", + [USB_ERR_IN_USE] = "USB_ERR_IN_USE", + [USB_ERR_NO_ADDR] = "USB_ERR_NO_ADDR", + [USB_ERR_NO_PIPE] = "USB_ERR_NO_PIPE", + [USB_ERR_ZERO_NFRAMES] = "USB_ERR_ZERO_NFRAMES", + [USB_ERR_ZERO_MAXP] = "USB_ERR_ZERO_MAXP", + [USB_ERR_SET_ADDR_FAILED] = "USB_ERR_SET_ADDR_FAILED", + [USB_ERR_NO_POWER] = "USB_ERR_NO_POWER", + [USB_ERR_TOO_DEEP] = "USB_ERR_TOO_DEEP", + [USB_ERR_IOERROR] = "USB_ERR_IOERROR", + [USB_ERR_NOT_CONFIGURED] = "USB_ERR_NOT_CONFIGURED", + [USB_ERR_TIMEOUT] = "USB_ERR_TIMEOUT", + [USB_ERR_SHORT_XFER] = "USB_ERR_SHORT_XFER", + [USB_ERR_STALLED] = "USB_ERR_STALLED", + [USB_ERR_INTERRUPTED] = "USB_ERR_INTERRUPTED", + [USB_ERR_DMA_LOAD_FAILED] = "USB_ERR_DMA_LOAD_FAILED", + [USB_ERR_BAD_CONTEXT] = "USB_ERR_BAD_CONTEXT", + [USB_ERR_NO_ROOT_HUB] = "USB_ERR_NO_ROOT_HUB", + [USB_ERR_NO_INTR_THREAD] = "USB_ERR_NO_INTR_THREAD", + [USB_ERR_NOT_LOCKED] = "USB_ERR_NOT_LOCKED", +}; + +/*------------------------------------------------------------------------* + * usbd_errstr + * + * This function converts an USB error code into a string. + *------------------------------------------------------------------------*/ +const char * +usbd_errstr(usb_error_t err) +{ + return (err < USB_ERR_MAX ? usb_errstr_table[err] : "USB_ERR_UNKNOWN"); +} diff --git a/sys/bus/u4b/usb_freebsd.h b/sys/bus/u4b/usb_freebsd.h new file mode 100644 index 0000000000..349e13e847 --- /dev/null +++ b/sys/bus/u4b/usb_freebsd.h @@ -0,0 +1,80 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Including this file is mandatory for all USB related c-files in the kernel. + */ + +#ifndef _USB_FREEBSD_H_ +#define _USB_FREEBSD_H_ + +/* Default USB configuration */ +#define USB_HAVE_UGEN 1 +#define USB_HAVE_DEVCTL 1 +#define USB_HAVE_BUSDMA 1 +#define USB_HAVE_COMPAT_LINUX 1 +#define USB_HAVE_USER_IO 1 +#define USB_HAVE_MBUF 1 +#define USB_HAVE_TT_SUPPORT 1 +#define USB_HAVE_POWERD 1 +#define USB_HAVE_MSCTEST 1 +#define USB_HAVE_PF 1 + +#define USB_TD_GET_PROC(td) (td)->td_proc +#define USB_PROC_GET_GID(td) (td)->p_pgid + +#if (!defined(USB_HOST_ALIGN)) || (USB_HOST_ALIGN <= 0) +/* Use default value. */ +#undef USB_HOST_ALIGN +#define USB_HOST_ALIGN 8 /* bytes, must be power of two */ +#endif +/* Sanity check for USB_HOST_ALIGN: Verify power of two. */ +#if ((-USB_HOST_ALIGN) & USB_HOST_ALIGN) != USB_HOST_ALIGN +#error "USB_HOST_ALIGN is not power of two." +#endif +#define USB_FS_ISOC_UFRAME_MAX 4 /* exclusive unit */ +#define USB_BUS_MAX 256 /* units */ +#define USB_MAX_DEVICES 128 /* units */ +#define USB_IFACE_MAX 32 /* units */ +#define USB_FIFO_MAX 128 /* units */ + +#define USB_MAX_FS_ISOC_FRAMES_PER_XFER (120) /* units */ +#define USB_MAX_HS_ISOC_FRAMES_PER_XFER (8*120) /* units */ + +#define USB_HUB_MAX_DEPTH 5 +#define USB_EP0_BUFSIZE 1024 /* bytes */ +#define USB_CS_RESET_LIMIT 20 /* failures = 20 * 50 ms = 1sec */ + +#define USB_MAX_AUTO_QUIRK 4 /* maximum number of dynamic quirks */ + +typedef uint32_t usb_timeout_t; /* milliseconds */ +typedef uint32_t usb_frlength_t; /* bytes */ +typedef uint32_t usb_frcount_t; /* units */ +typedef uint32_t usb_size_t; /* bytes */ +typedef uint32_t usb_ticks_t; /* system defined */ +typedef uint16_t usb_power_mask_t; /* see "USB_HW_POWER_XXX" */ + +#endif /* _USB_FREEBSD_H_ */ diff --git a/sys/bus/u4b/usb_generic.c b/sys/bus/u4b/usb_generic.c new file mode 100644 index 0000000000..f175eb94b0 --- /dev/null +++ b/sys/bus/u4b/usb_generic.c @@ -0,0 +1,2266 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define USB_DEBUG_VAR ugen_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if USB_HAVE_UGEN + +/* defines */ + +#define UGEN_BULK_FS_BUFFER_SIZE (64*32) /* bytes */ +#define UGEN_BULK_HS_BUFFER_SIZE (1024*32) /* bytes */ +#define UGEN_HW_FRAMES 50 /* number of milliseconds per transfer */ + +/* function prototypes */ + +static usb_callback_t ugen_read_clear_stall_callback; +static usb_callback_t ugen_write_clear_stall_callback; +static usb_callback_t ugen_ctrl_read_callback; +static usb_callback_t ugen_ctrl_write_callback; +static usb_callback_t ugen_isoc_read_callback; +static usb_callback_t ugen_isoc_write_callback; +static usb_callback_t ugen_ctrl_fs_callback; + +static usb_fifo_open_t ugen_open; +static usb_fifo_close_t ugen_close; +static usb_fifo_ioctl_t ugen_ioctl; +static usb_fifo_ioctl_t ugen_ioctl_post; +static usb_fifo_cmd_t ugen_start_read; +static usb_fifo_cmd_t ugen_start_write; +static usb_fifo_cmd_t ugen_stop_io; + +static int ugen_transfer_setup(struct usb_fifo *, + const struct usb_config *, uint8_t); +static int ugen_open_pipe_write(struct usb_fifo *); +static int ugen_open_pipe_read(struct usb_fifo *); +static int ugen_set_config(struct usb_fifo *, uint8_t); +static int ugen_set_interface(struct usb_fifo *, uint8_t, uint8_t); +static int ugen_get_cdesc(struct usb_fifo *, struct usb_gen_descriptor *); +static int ugen_get_sdesc(struct usb_fifo *, struct usb_gen_descriptor *); +static int ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd); +static int usb_gen_fill_deviceinfo(struct usb_fifo *, + struct usb_device_info *); +static int ugen_re_enumerate(struct usb_fifo *); +static int ugen_iface_ioctl(struct usb_fifo *, u_long, void *, int); +static uint8_t ugen_fs_get_complete(struct usb_fifo *, uint8_t *); +static int ugen_fs_uninit(struct usb_fifo *f); + +/* structures */ + +struct usb_fifo_methods usb_ugen_methods = { + .f_open = &ugen_open, + .f_close = &ugen_close, + .f_ioctl = &ugen_ioctl, + .f_ioctl_post = &ugen_ioctl_post, + .f_start_read = &ugen_start_read, + .f_stop_read = &ugen_stop_io, + .f_start_write = &ugen_start_write, + .f_stop_write = &ugen_stop_io, +}; + +#ifdef USB_DEBUG +static int ugen_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ugen, CTLFLAG_RW, 0, "USB generic"); +SYSCTL_INT(_hw_usb_ugen, OID_AUTO, debug, CTLFLAG_RW, &ugen_debug, + 0, "Debug level"); + +TUNABLE_INT("hw.usb.ugen.debug", &ugen_debug); +#endif + + +/* prototypes */ + +static int +ugen_transfer_setup(struct usb_fifo *f, + const struct usb_config *setup, uint8_t n_setup) +{ + struct usb_endpoint *ep = usb_fifo_softc(f); + struct usb_device *udev = f->udev; + uint8_t iface_index = ep->iface_index; + int error; + + mtx_unlock(f->priv_mtx); + + /* + * "usbd_transfer_setup()" can sleep so one needs to make a wrapper, + * exiting the mutex and checking things + */ + error = usbd_transfer_setup(udev, &iface_index, f->xfer, + setup, n_setup, f, f->priv_mtx); + if (error == 0) { + + if (f->xfer[0]->nframes == 1) { + error = usb_fifo_alloc_buffer(f, + f->xfer[0]->max_data_length, 2); + } else { + error = usb_fifo_alloc_buffer(f, + f->xfer[0]->max_frame_size, + 2 * f->xfer[0]->nframes); + } + if (error) { + usbd_transfer_unsetup(f->xfer, n_setup); + } + } + mtx_lock(f->priv_mtx); + + return (error); +} + +static int +ugen_open(struct usb_fifo *f, int fflags) +{ + struct usb_endpoint *ep = usb_fifo_softc(f); + struct usb_endpoint_descriptor *ed = ep->edesc; + uint8_t type; + + DPRINTFN(6, "flag=0x%x\n", fflags); + + mtx_lock(f->priv_mtx); + switch (usbd_get_speed(f->udev)) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + f->nframes = UGEN_HW_FRAMES; + f->bufsize = UGEN_BULK_FS_BUFFER_SIZE; + break; + default: + f->nframes = UGEN_HW_FRAMES * 8; + f->bufsize = UGEN_BULK_HS_BUFFER_SIZE; + break; + } + + type = ed->bmAttributes & UE_XFERTYPE; + if (type == UE_INTERRUPT) { + f->bufsize = 0; /* use "wMaxPacketSize" */ + } + f->timeout = USB_NO_TIMEOUT; + f->flag_short = 0; + f->fifo_zlp = 0; + mtx_unlock(f->priv_mtx); + + return (0); +} + +static void +ugen_close(struct usb_fifo *f, int fflags) +{ + DPRINTFN(6, "flag=0x%x\n", fflags); + + /* cleanup */ + + mtx_lock(f->priv_mtx); + usbd_transfer_stop(f->xfer[0]); + usbd_transfer_stop(f->xfer[1]); + mtx_unlock(f->priv_mtx); + + usbd_transfer_unsetup(f->xfer, 2); + usb_fifo_free_buffer(f); + + if (ugen_fs_uninit(f)) { + /* ignore any errors - we are closing */ + DPRINTFN(6, "no FIFOs\n"); + } +} + +static int +ugen_open_pipe_write(struct usb_fifo *f) +{ + struct usb_config usb_config[2]; + struct usb_endpoint *ep = usb_fifo_softc(f); + struct usb_endpoint_descriptor *ed = ep->edesc; + + mtx_assert(f->priv_mtx, MA_OWNED); + + if (f->xfer[0] || f->xfer[1]) { + /* transfers are already opened */ + return (0); + } + memset(usb_config, 0, sizeof(usb_config)); + + usb_config[1].type = UE_CONTROL; + usb_config[1].endpoint = 0; + usb_config[1].direction = UE_DIR_ANY; + usb_config[1].timeout = 1000; /* 1 second */ + usb_config[1].interval = 50;/* 50 milliseconds */ + usb_config[1].bufsize = sizeof(struct usb_device_request); + usb_config[1].callback = &ugen_write_clear_stall_callback; + usb_config[1].usb_mode = USB_MODE_HOST; + + usb_config[0].type = ed->bmAttributes & UE_XFERTYPE; + usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; + usb_config[0].direction = UE_DIR_TX; + usb_config[0].interval = USB_DEFAULT_INTERVAL; + usb_config[0].flags.proxy_buffer = 1; + usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */ + + switch (ed->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + case UE_BULK: + if (f->flag_short) { + usb_config[0].flags.force_short_xfer = 1; + } + usb_config[0].callback = &ugen_ctrl_write_callback; + usb_config[0].timeout = f->timeout; + usb_config[0].frames = 1; + usb_config[0].bufsize = f->bufsize; + if (ugen_transfer_setup(f, usb_config, 2)) { + return (EIO); + } + /* first transfer does not clear stall */ + f->flag_stall = 0; + break; + + case UE_ISOCHRONOUS: + usb_config[0].flags.short_xfer_ok = 1; + usb_config[0].bufsize = 0; /* use default */ + usb_config[0].frames = f->nframes; + usb_config[0].callback = &ugen_isoc_write_callback; + usb_config[0].timeout = 0; + + /* clone configuration */ + usb_config[1] = usb_config[0]; + + if (ugen_transfer_setup(f, usb_config, 2)) { + return (EIO); + } + break; + default: + return (EINVAL); + } + return (0); +} + +static int +ugen_open_pipe_read(struct usb_fifo *f) +{ + struct usb_config usb_config[2]; + struct usb_endpoint *ep = usb_fifo_softc(f); + struct usb_endpoint_descriptor *ed = ep->edesc; + + mtx_assert(f->priv_mtx, MA_OWNED); + + if (f->xfer[0] || f->xfer[1]) { + /* transfers are already opened */ + return (0); + } + memset(usb_config, 0, sizeof(usb_config)); + + usb_config[1].type = UE_CONTROL; + usb_config[1].endpoint = 0; + usb_config[1].direction = UE_DIR_ANY; + usb_config[1].timeout = 1000; /* 1 second */ + usb_config[1].interval = 50;/* 50 milliseconds */ + usb_config[1].bufsize = sizeof(struct usb_device_request); + usb_config[1].callback = &ugen_read_clear_stall_callback; + usb_config[1].usb_mode = USB_MODE_HOST; + + usb_config[0].type = ed->bmAttributes & UE_XFERTYPE; + usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; + usb_config[0].direction = UE_DIR_RX; + usb_config[0].interval = USB_DEFAULT_INTERVAL; + usb_config[0].flags.proxy_buffer = 1; + usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */ + + switch (ed->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + case UE_BULK: + if (f->flag_short) { + usb_config[0].flags.short_xfer_ok = 1; + } + usb_config[0].timeout = f->timeout; + usb_config[0].frames = 1; + usb_config[0].callback = &ugen_ctrl_read_callback; + usb_config[0].bufsize = f->bufsize; + + if (ugen_transfer_setup(f, usb_config, 2)) { + return (EIO); + } + /* first transfer does not clear stall */ + f->flag_stall = 0; + break; + + case UE_ISOCHRONOUS: + usb_config[0].flags.short_xfer_ok = 1; + usb_config[0].bufsize = 0; /* use default */ + usb_config[0].frames = f->nframes; + usb_config[0].callback = &ugen_isoc_read_callback; + usb_config[0].timeout = 0; + + /* clone configuration */ + usb_config[1] = usb_config[0]; + + if (ugen_transfer_setup(f, usb_config, 2)) { + return (EIO); + } + break; + + default: + return (EINVAL); + } + return (0); +} + +static void +ugen_start_read(struct usb_fifo *f) +{ + /* check that pipes are open */ + if (ugen_open_pipe_read(f)) { + /* signal error */ + usb_fifo_put_data_error(f); + } + /* start transfers */ + usbd_transfer_start(f->xfer[0]); + usbd_transfer_start(f->xfer[1]); +} + +static void +ugen_start_write(struct usb_fifo *f) +{ + /* check that pipes are open */ + if (ugen_open_pipe_write(f)) { + /* signal error */ + usb_fifo_get_data_error(f); + } + /* start transfers */ + usbd_transfer_start(f->xfer[0]); + usbd_transfer_start(f->xfer[1]); +} + +static void +ugen_stop_io(struct usb_fifo *f) +{ + /* stop transfers */ + usbd_transfer_stop(f->xfer[0]); + usbd_transfer_stop(f->xfer[1]); +} + +static void +ugen_ctrl_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_fifo *f = usbd_xfer_softc(xfer); + struct usb_mbuf *m; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (xfer->actlen == 0) { + if (f->fifo_zlp != 4) { + f->fifo_zlp++; + } else { + /* + * Throttle a little bit we have multiple ZLPs + * in a row! + */ + xfer->interval = 64; /* ms */ + } + } else { + /* clear throttle */ + xfer->interval = 0; + f->fifo_zlp = 0; + } + usb_fifo_put_data(f, xfer->frbuffers, 0, + xfer->actlen, 1); + + case USB_ST_SETUP: + if (f->flag_stall) { + usbd_transfer_start(f->xfer[1]); + break; + } + USB_IF_POLL(&f->free_q, m); + if (m) { + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* send a zero length packet to userland */ + usb_fifo_put_data(f, xfer->frbuffers, 0, 0, 1); + f->flag_stall = 1; + f->fifo_zlp = 0; + usbd_transfer_start(f->xfer[1]); + } + break; + } +} + +static void +ugen_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_fifo *f = usbd_xfer_softc(xfer); + usb_frlength_t actlen; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + /* + * If writing is in stall, just jump to clear stall + * callback and solve the situation. + */ + if (f->flag_stall) { + usbd_transfer_start(f->xfer[1]); + break; + } + /* + * Write data, setup and perform hardware transfer. + */ + if (usb_fifo_get_data(f, xfer->frbuffers, 0, + xfer->max_data_length, &actlen, 0)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + f->flag_stall = 1; + usbd_transfer_start(f->xfer[1]); + } + break; + } +} + +static void +ugen_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_fifo *f = usbd_xfer_softc(xfer); + struct usb_xfer *xfer_other = f->xfer[0]; + + if (f->flag_stall == 0) { + /* nothing to do */ + return; + } + if (usbd_clear_stall_callback(xfer, xfer_other)) { + DPRINTFN(5, "f=%p: stall cleared\n", f); + f->flag_stall = 0; + usbd_transfer_start(xfer_other); + } +} + +static void +ugen_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_fifo *f = usbd_xfer_softc(xfer); + struct usb_xfer *xfer_other = f->xfer[0]; + + if (f->flag_stall == 0) { + /* nothing to do */ + return; + } + if (usbd_clear_stall_callback(xfer, xfer_other)) { + DPRINTFN(5, "f=%p: stall cleared\n", f); + f->flag_stall = 0; + usbd_transfer_start(xfer_other); + } +} + +static void +ugen_isoc_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_fifo *f = usbd_xfer_softc(xfer); + usb_frlength_t offset; + usb_frcount_t n; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTFN(6, "actlen=%d\n", xfer->actlen); + + offset = 0; + + for (n = 0; n != xfer->aframes; n++) { + usb_fifo_put_data(f, xfer->frbuffers, offset, + xfer->frlengths[n], 1); + offset += xfer->max_frame_size; + } + + case USB_ST_SETUP: +tr_setup: + for (n = 0; n != xfer->nframes; n++) { + /* setup size for next transfer */ + usbd_xfer_set_frame_len(xfer, n, xfer->max_frame_size); + } + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + goto tr_setup; + } +} + +static void +ugen_isoc_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_fifo *f = usbd_xfer_softc(xfer); + usb_frlength_t actlen; + usb_frlength_t offset; + usb_frcount_t n; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: +tr_setup: + offset = 0; + for (n = 0; n != xfer->nframes; n++) { + if (usb_fifo_get_data(f, xfer->frbuffers, offset, + xfer->max_frame_size, &actlen, 1)) { + usbd_xfer_set_frame_len(xfer, n, actlen); + offset += actlen; + } else { + break; + } + } + + for (; n != xfer->nframes; n++) { + /* fill in zero frames */ + usbd_xfer_set_frame_len(xfer, n, 0); + } + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + goto tr_setup; + } +} + +static int +ugen_set_config(struct usb_fifo *f, uint8_t index) +{ + DPRINTFN(2, "index %u\n", index); + + if (f->udev->flags.usb_mode != USB_MODE_HOST) { + /* not possible in device side mode */ + return (ENOTTY); + } + if (f->udev->curr_config_index == index) { + /* no change needed */ + return (0); + } + /* make sure all FIFO's are gone */ + /* else there can be a deadlock */ + if (ugen_fs_uninit(f)) { + /* ignore any errors */ + DPRINTFN(6, "no FIFOs\n"); + } + /* change setting - will free generic FIFOs, if any */ + if (usbd_set_config_index(f->udev, index)) { + return (EIO); + } + /* probe and attach */ + if (usb_probe_and_attach(f->udev, USB_IFACE_INDEX_ANY)) { + return (EIO); + } + return (0); +} + +static int +ugen_set_interface(struct usb_fifo *f, + uint8_t iface_index, uint8_t alt_index) +{ + DPRINTFN(2, "%u, %u\n", iface_index, alt_index); + + if (f->udev->flags.usb_mode != USB_MODE_HOST) { + /* not possible in device side mode */ + return (ENOTTY); + } + /* make sure all FIFO's are gone */ + /* else there can be a deadlock */ + if (ugen_fs_uninit(f)) { + /* ignore any errors */ + DPRINTFN(6, "no FIFOs\n"); + } + /* change setting - will free generic FIFOs, if any */ + if (usbd_set_alt_interface_index(f->udev, iface_index, alt_index)) { + return (EIO); + } + /* probe and attach */ + if (usb_probe_and_attach(f->udev, iface_index)) { + return (EIO); + } + return (0); +} + +/*------------------------------------------------------------------------* + * ugen_get_cdesc + * + * This function will retrieve the complete configuration descriptor + * at the given index. + *------------------------------------------------------------------------*/ +static int +ugen_get_cdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd) +{ + struct usb_config_descriptor *cdesc; + struct usb_device *udev = f->udev; + int error; + uint16_t len; + uint8_t free_data; + + DPRINTFN(6, "\n"); + + if (ugd->ugd_data == NULL) { + /* userland pointer should not be zero */ + return (EINVAL); + } + if ((ugd->ugd_config_index == USB_UNCONFIG_INDEX) || + (ugd->ugd_config_index == udev->curr_config_index)) { + cdesc = usbd_get_config_descriptor(udev); + if (cdesc == NULL) { + return (ENXIO); + } + free_data = 0; + + } else { + if (usbd_req_get_config_desc_full(udev, + NULL, &cdesc, M_USBDEV, + ugd->ugd_config_index)) { + return (ENXIO); + } + free_data = 1; + } + + len = UGETW(cdesc->wTotalLength); + if (len > ugd->ugd_maxlen) { + len = ugd->ugd_maxlen; + } + DPRINTFN(6, "len=%u\n", len); + + ugd->ugd_actlen = len; + ugd->ugd_offset = 0; + + error = copyout(cdesc, ugd->ugd_data, len); + + if (free_data) { + free(cdesc, M_USBDEV); + } + return (error); +} + +static int +ugen_get_sdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd) +{ + void *ptr = f->udev->bus->scratch[0].data; + uint16_t size = sizeof(f->udev->bus->scratch[0].data); + int error; + + if (usbd_req_get_string_desc(f->udev, NULL, ptr, + size, ugd->ugd_lang_id, ugd->ugd_string_index)) { + error = EINVAL; + } else { + + if (size > ((uint8_t *)ptr)[0]) { + size = ((uint8_t *)ptr)[0]; + } + if (size > ugd->ugd_maxlen) { + size = ugd->ugd_maxlen; + } + ugd->ugd_actlen = size; + ugd->ugd_offset = 0; + + error = copyout(ptr, ugd->ugd_data, size); + } + return (error); +} + +/*------------------------------------------------------------------------* + * ugen_get_iface_driver + * + * This function generates an USB interface description for userland. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd) +{ + struct usb_device *udev = f->udev; + struct usb_interface *iface; + const char *ptr; + const char *desc; + unsigned int len; + unsigned int maxlen; + char buf[128]; + int error; + + DPRINTFN(6, "\n"); + + if ((ugd->ugd_data == NULL) || (ugd->ugd_maxlen == 0)) { + /* userland pointer should not be zero */ + return (EINVAL); + } + + iface = usbd_get_iface(udev, ugd->ugd_iface_index); + if ((iface == NULL) || (iface->idesc == NULL)) { + /* invalid interface index */ + return (EINVAL); + } + + /* read out device nameunit string, if any */ + if ((iface->subdev != NULL) && + device_is_attached(iface->subdev) && + (ptr = device_get_nameunit(iface->subdev)) && + (desc = device_get_desc(iface->subdev))) { + + /* print description */ + snprintf(buf, sizeof(buf), "%s: <%s>", ptr, desc); + + /* range checks */ + maxlen = ugd->ugd_maxlen - 1; + len = strlen(buf); + if (len > maxlen) + len = maxlen; + + /* update actual length, including terminating zero */ + ugd->ugd_actlen = len + 1; + + /* copy out interface description */ + error = copyout(buf, ugd->ugd_data, ugd->ugd_actlen); + } else { + /* zero length string is default */ + error = copyout("", ugd->ugd_data, 1); + } + return (error); +} + +/*------------------------------------------------------------------------* + * usb_gen_fill_deviceinfo + * + * This function dumps information about an USB device to the + * structure pointed to by the "di" argument. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usb_gen_fill_deviceinfo(struct usb_fifo *f, struct usb_device_info *di) +{ + struct usb_device *udev; + struct usb_device *hub; + + udev = f->udev; + + bzero(di, sizeof(di[0])); + + di->udi_bus = device_get_unit(udev->bus->bdev); + di->udi_addr = udev->address; + di->udi_index = udev->device_index; + strlcpy(di->udi_serial, usb_get_serial(udev), sizeof(di->udi_serial)); + strlcpy(di->udi_vendor, usb_get_manufacturer(udev), sizeof(di->udi_vendor)); + strlcpy(di->udi_product, usb_get_product(udev), sizeof(di->udi_product)); + usb_printbcd(di->udi_release, sizeof(di->udi_release), + UGETW(udev->ddesc.bcdDevice)); + di->udi_vendorNo = UGETW(udev->ddesc.idVendor); + di->udi_productNo = UGETW(udev->ddesc.idProduct); + di->udi_releaseNo = UGETW(udev->ddesc.bcdDevice); + di->udi_class = udev->ddesc.bDeviceClass; + di->udi_subclass = udev->ddesc.bDeviceSubClass; + di->udi_protocol = udev->ddesc.bDeviceProtocol; + di->udi_config_no = udev->curr_config_no; + di->udi_config_index = udev->curr_config_index; + di->udi_power = udev->flags.self_powered ? 0 : udev->power; + di->udi_speed = udev->speed; + di->udi_mode = udev->flags.usb_mode; + di->udi_power_mode = udev->power_mode; + di->udi_suspended = udev->flags.peer_suspended; + + hub = udev->parent_hub; + if (hub) { + di->udi_hubaddr = hub->address; + di->udi_hubindex = hub->device_index; + di->udi_hubport = udev->port_no; + } + return (0); +} + +/*------------------------------------------------------------------------* + * ugen_check_request + * + * Return values: + * 0: Access allowed + * Else: No access + *------------------------------------------------------------------------*/ +static int +ugen_check_request(struct usb_device *udev, struct usb_device_request *req) +{ + struct usb_endpoint *ep; + int error; + + /* + * Avoid requests that would damage the bus integrity: + */ + if (((req->bmRequestType == UT_WRITE_DEVICE) && + (req->bRequest == UR_SET_ADDRESS)) || + ((req->bmRequestType == UT_WRITE_DEVICE) && + (req->bRequest == UR_SET_CONFIG)) || + ((req->bmRequestType == UT_WRITE_INTERFACE) && + (req->bRequest == UR_SET_INTERFACE))) { + /* + * These requests can be useful for testing USB drivers. + */ + error = priv_check(curthread, PRIV_DRIVER); + if (error) { + return (error); + } + } + /* + * Special case - handle clearing of stall + */ + if (req->bmRequestType == UT_WRITE_ENDPOINT) { + + ep = usbd_get_ep_by_addr(udev, req->wIndex[0]); + if (ep == NULL) { + return (EINVAL); + } + if ((req->bRequest == UR_CLEAR_FEATURE) && + (UGETW(req->wValue) == UF_ENDPOINT_HALT)) { + usbd_clear_data_toggle(udev, ep); + } + } + /* TODO: add more checks to verify the interface index */ + + return (0); +} + +int +ugen_do_request(struct usb_fifo *f, struct usb_ctl_request *ur) +{ + int error; + uint16_t len; + uint16_t actlen; + + if (ugen_check_request(f->udev, &ur->ucr_request)) { + return (EPERM); + } + len = UGETW(ur->ucr_request.wLength); + + /* check if "ucr_data" is valid */ + if (len != 0) { + if (ur->ucr_data == NULL) { + return (EFAULT); + } + } + /* do the USB request */ + error = usbd_do_request_flags + (f->udev, NULL, &ur->ucr_request, ur->ucr_data, + (ur->ucr_flags & USB_SHORT_XFER_OK) | + USB_USER_DATA_PTR, &actlen, + USB_DEFAULT_TIMEOUT); + + ur->ucr_actlen = actlen; + + if (error) { + error = EIO; + } + return (error); +} + +/*------------------------------------------------------------------------ + * ugen_re_enumerate + *------------------------------------------------------------------------*/ +static int +ugen_re_enumerate(struct usb_fifo *f) +{ + struct usb_device *udev = f->udev; + int error; + + /* + * This request can be useful for testing USB drivers: + */ + error = priv_check(curthread, PRIV_DRIVER); + if (error) { + return (error); + } + if (udev->flags.usb_mode != USB_MODE_HOST) { + /* not possible in device side mode */ + DPRINTFN(6, "device mode\n"); + return (ENOTTY); + } + if (udev->parent_hub == NULL) { + /* the root HUB cannot be re-enumerated */ + DPRINTFN(6, "cannot reset root HUB\n"); + return (EINVAL); + } + /* make sure all FIFO's are gone */ + /* else there can be a deadlock */ + if (ugen_fs_uninit(f)) { + /* ignore any errors */ + DPRINTFN(6, "no FIFOs\n"); + } + /* start re-enumeration of device */ + usbd_start_re_enumerate(udev); + return (0); +} + +int +ugen_fs_uninit(struct usb_fifo *f) +{ + if (f->fs_xfer == NULL) { + return (EINVAL); + } + usbd_transfer_unsetup(f->fs_xfer, f->fs_ep_max); + free(f->fs_xfer, M_USB); + f->fs_xfer = NULL; + f->fs_ep_max = 0; + f->fs_ep_ptr = NULL; + f->flag_iscomplete = 0; + usb_fifo_free_buffer(f); + return (0); +} + +static uint8_t +ugen_fs_get_complete(struct usb_fifo *f, uint8_t *pindex) +{ + struct usb_mbuf *m; + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + *pindex = *((uint8_t *)(m->cur_data_ptr)); + + USB_IF_ENQUEUE(&f->free_q, m); + + return (0); /* success */ + } else { + + *pindex = 0; /* fix compiler warning */ + + f->flag_iscomplete = 0; + } + return (1); /* failure */ +} + +static void +ugen_fs_set_complete(struct usb_fifo *f, uint8_t index) +{ + struct usb_mbuf *m; + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m == NULL) { + /* can happen during close */ + DPRINTF("out of buffers\n"); + return; + } + USB_MBUF_RESET(m); + + *((uint8_t *)(m->cur_data_ptr)) = index; + + USB_IF_ENQUEUE(&f->used_q, m); + + f->flag_iscomplete = 1; + + usb_fifo_wakeup(f); +} + +static int +ugen_fs_copy_in(struct usb_fifo *f, uint8_t ep_index) +{ + struct usb_device_request *req; + struct usb_xfer *xfer; + struct usb_fs_endpoint fs_ep; + void *uaddr; /* userland pointer */ + void *kaddr; + usb_frlength_t offset; + usb_frlength_t rem; + usb_frcount_t n; + uint32_t length; + int error; + uint8_t isread; + + if (ep_index >= f->fs_ep_max) { + return (EINVAL); + } + xfer = f->fs_xfer[ep_index]; + if (xfer == NULL) { + return (EINVAL); + } + mtx_lock(f->priv_mtx); + if (usbd_transfer_pending(xfer)) { + mtx_unlock(f->priv_mtx); + return (EBUSY); /* should not happen */ + } + mtx_unlock(f->priv_mtx); + + error = copyin(f->fs_ep_ptr + + ep_index, &fs_ep, sizeof(fs_ep)); + if (error) { + return (error); + } + /* security checks */ + + if (fs_ep.nFrames > xfer->max_frame_count) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + if (fs_ep.nFrames == 0) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + error = copyin(fs_ep.ppBuffer, + &uaddr, sizeof(uaddr)); + if (error) { + return (error); + } + /* reset first frame */ + usbd_xfer_set_frame_offset(xfer, 0, 0); + + if (xfer->flags_int.control_xfr) { + + req = xfer->frbuffers[0].buffer; + + error = copyin(fs_ep.pLength, + &length, sizeof(length)); + if (error) { + return (error); + } + if (length != sizeof(*req)) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + if (length != 0) { + error = copyin(uaddr, req, length); + if (error) { + return (error); + } + } + if (ugen_check_request(f->udev, req)) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + usbd_xfer_set_frame_len(xfer, 0, length); + + /* Host mode only ! */ + if ((req->bmRequestType & + (UT_READ | UT_WRITE)) == UT_READ) { + isread = 1; + } else { + isread = 0; + } + n = 1; + offset = sizeof(*req); + + } else { + /* Device and Host mode */ + if (USB_GET_DATA_ISREAD(xfer)) { + isread = 1; + } else { + isread = 0; + } + n = 0; + offset = 0; + } + + rem = usbd_xfer_max_len(xfer); + xfer->nframes = fs_ep.nFrames; + xfer->timeout = fs_ep.timeout; + if (xfer->timeout > 65535) { + xfer->timeout = 65535; + } + if (fs_ep.flags & USB_FS_FLAG_SINGLE_SHORT_OK) + xfer->flags.short_xfer_ok = 1; + else + xfer->flags.short_xfer_ok = 0; + + if (fs_ep.flags & USB_FS_FLAG_MULTI_SHORT_OK) + xfer->flags.short_frames_ok = 1; + else + xfer->flags.short_frames_ok = 0; + + if (fs_ep.flags & USB_FS_FLAG_FORCE_SHORT) + xfer->flags.force_short_xfer = 1; + else + xfer->flags.force_short_xfer = 0; + + if (fs_ep.flags & USB_FS_FLAG_CLEAR_STALL) + usbd_xfer_set_stall(xfer); + else + xfer->flags.stall_pipe = 0; + + for (; n != xfer->nframes; n++) { + + error = copyin(fs_ep.pLength + n, + &length, sizeof(length)); + if (error) { + break; + } + usbd_xfer_set_frame_len(xfer, n, length); + + if (length > rem) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + rem -= length; + + if (!isread) { + + /* we need to know the source buffer */ + error = copyin(fs_ep.ppBuffer + n, + &uaddr, sizeof(uaddr)); + if (error) { + break; + } + if (xfer->flags_int.isochronous_xfr) { + /* get kernel buffer address */ + kaddr = xfer->frbuffers[0].buffer; + kaddr = USB_ADD_BYTES(kaddr, offset); + } else { + /* set current frame offset */ + usbd_xfer_set_frame_offset(xfer, offset, n); + + /* get kernel buffer address */ + kaddr = xfer->frbuffers[n].buffer; + } + + /* move data */ + error = copyin(uaddr, kaddr, length); + if (error) { + break; + } + } + offset += length; + } + return (error); + +complete: + mtx_lock(f->priv_mtx); + ugen_fs_set_complete(f, ep_index); + mtx_unlock(f->priv_mtx); + return (0); +} + +static int +ugen_fs_copy_out(struct usb_fifo *f, uint8_t ep_index) +{ + struct usb_device_request *req; + struct usb_xfer *xfer; + struct usb_fs_endpoint fs_ep; + struct usb_fs_endpoint *fs_ep_uptr; /* userland ptr */ + void *uaddr; /* userland ptr */ + void *kaddr; + usb_frlength_t offset; + usb_frlength_t rem; + usb_frcount_t n; + uint32_t length; + uint32_t temp; + int error; + uint8_t isread; + + if (ep_index >= f->fs_ep_max) + return (EINVAL); + + xfer = f->fs_xfer[ep_index]; + if (xfer == NULL) + return (EINVAL); + + mtx_lock(f->priv_mtx); + if (usbd_transfer_pending(xfer)) { + mtx_unlock(f->priv_mtx); + return (EBUSY); /* should not happen */ + } + mtx_unlock(f->priv_mtx); + + fs_ep_uptr = f->fs_ep_ptr + ep_index; + error = copyin(fs_ep_uptr, &fs_ep, sizeof(fs_ep)); + if (error) { + return (error); + } + fs_ep.status = xfer->error; + fs_ep.aFrames = xfer->aframes; + fs_ep.isoc_time_complete = xfer->isoc_time_complete; + if (xfer->error) { + goto complete; + } + if (xfer->flags_int.control_xfr) { + req = xfer->frbuffers[0].buffer; + + /* Host mode only ! */ + if ((req->bmRequestType & (UT_READ | UT_WRITE)) == UT_READ) { + isread = 1; + } else { + isread = 0; + } + if (xfer->nframes == 0) + n = 0; /* should never happen */ + else + n = 1; + } else { + /* Device and Host mode */ + if (USB_GET_DATA_ISREAD(xfer)) { + isread = 1; + } else { + isread = 0; + } + n = 0; + } + + /* Update lengths and copy out data */ + + rem = usbd_xfer_max_len(xfer); + offset = 0; + + for (; n != xfer->nframes; n++) { + + /* get initial length into "temp" */ + error = copyin(fs_ep.pLength + n, + &temp, sizeof(temp)); + if (error) { + return (error); + } + if (temp > rem) { + /* the userland length has been corrupted */ + DPRINTF("corrupt userland length " + "%u > %u\n", temp, rem); + fs_ep.status = USB_ERR_INVAL; + goto complete; + } + rem -= temp; + + /* get actual transfer length */ + length = xfer->frlengths[n]; + if (length > temp) { + /* data overflow */ + fs_ep.status = USB_ERR_INVAL; + DPRINTF("data overflow %u > %u\n", + length, temp); + goto complete; + } + if (isread) { + + /* we need to know the destination buffer */ + error = copyin(fs_ep.ppBuffer + n, + &uaddr, sizeof(uaddr)); + if (error) { + return (error); + } + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + kaddr = USB_ADD_BYTES( + xfer->frbuffers[0].buffer, offset); + } else { + /* multiple frame buffers */ + kaddr = xfer->frbuffers[n].buffer; + } + + /* move data */ + error = copyout(kaddr, uaddr, length); + if (error) { + return (error); + } + } + /* + * Update offset according to initial length, which is + * needed by isochronous transfers! + */ + offset += temp; + + /* update length */ + error = copyout(&length, + fs_ep.pLength + n, sizeof(length)); + if (error) { + return (error); + } + } + +complete: + /* update "aFrames" */ + error = copyout(&fs_ep.aFrames, &fs_ep_uptr->aFrames, + sizeof(fs_ep.aFrames)); + if (error) + goto done; + + /* update "isoc_time_complete" */ + error = copyout(&fs_ep.isoc_time_complete, + &fs_ep_uptr->isoc_time_complete, + sizeof(fs_ep.isoc_time_complete)); + if (error) + goto done; + /* update "status" */ + error = copyout(&fs_ep.status, &fs_ep_uptr->status, + sizeof(fs_ep.status)); +done: + return (error); +} + +static uint8_t +ugen_fifo_in_use(struct usb_fifo *f, int fflags) +{ + struct usb_fifo *f_rx; + struct usb_fifo *f_tx; + + f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX]; + f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX]; + + if ((fflags & FREAD) && f_rx && + (f_rx->xfer[0] || f_rx->xfer[1])) { + return (1); /* RX FIFO in use */ + } + if ((fflags & FWRITE) && f_tx && + (f_tx->xfer[0] || f_tx->xfer[1])) { + return (1); /* TX FIFO in use */ + } + return (0); /* not in use */ +} + +static int +ugen_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags) +{ + struct usb_config usb_config[1]; + struct usb_device_request req; + union { + struct usb_fs_complete *pcomp; + struct usb_fs_start *pstart; + struct usb_fs_stop *pstop; + struct usb_fs_open *popen; + struct usb_fs_close *pclose; + struct usb_fs_clear_stall_sync *pstall; + void *addr; + } u; + struct usb_endpoint *ep; + struct usb_endpoint_descriptor *ed; + struct usb_xfer *xfer; + int error = 0; + uint8_t iface_index; + uint8_t isread; + uint8_t ep_index; + uint8_t pre_scale; + + u.addr = addr; + + DPRINTFN(6, "cmd=0x%08lx\n", cmd); + + switch (cmd) { + case USB_FS_COMPLETE: + mtx_lock(f->priv_mtx); + error = ugen_fs_get_complete(f, &ep_index); + mtx_unlock(f->priv_mtx); + + if (error) { + error = EBUSY; + break; + } + u.pcomp->ep_index = ep_index; + error = ugen_fs_copy_out(f, u.pcomp->ep_index); + break; + + case USB_FS_START: + error = ugen_fs_copy_in(f, u.pstart->ep_index); + if (error) + break; + mtx_lock(f->priv_mtx); + xfer = f->fs_xfer[u.pstart->ep_index]; + usbd_transfer_start(xfer); + mtx_unlock(f->priv_mtx); + break; + + case USB_FS_STOP: + if (u.pstop->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + mtx_lock(f->priv_mtx); + xfer = f->fs_xfer[u.pstart->ep_index]; + if (usbd_transfer_pending(xfer)) { + usbd_transfer_stop(xfer); + /* + * Check if the USB transfer was stopped + * before it was even started. Else a cancel + * callback will be pending. + */ + if (!xfer->flags_int.transferring) { + ugen_fs_set_complete(xfer->priv_sc, + USB_P2U(xfer->priv_fifo)); + } + } + mtx_unlock(f->priv_mtx); + break; + + case USB_FS_OPEN: + if (u.popen->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + if (f->fs_xfer[u.popen->ep_index] != NULL) { + error = EBUSY; + break; + } + if (u.popen->max_bufsize > USB_FS_MAX_BUFSIZE) { + u.popen->max_bufsize = USB_FS_MAX_BUFSIZE; + } + if (u.popen->max_frames & USB_FS_MAX_FRAMES_PRE_SCALE) { + pre_scale = 1; + u.popen->max_frames &= ~USB_FS_MAX_FRAMES_PRE_SCALE; + } else { + pre_scale = 0; + } + if (u.popen->max_frames > USB_FS_MAX_FRAMES) { + u.popen->max_frames = USB_FS_MAX_FRAMES; + break; + } + if (u.popen->max_frames == 0) { + error = EINVAL; + break; + } + ep = usbd_get_ep_by_addr(f->udev, u.popen->ep_no); + if (ep == NULL) { + error = EINVAL; + break; + } + ed = ep->edesc; + if (ed == NULL) { + error = ENXIO; + break; + } + iface_index = ep->iface_index; + + memset(usb_config, 0, sizeof(usb_config)); + + usb_config[0].type = ed->bmAttributes & UE_XFERTYPE; + usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; + usb_config[0].direction = ed->bEndpointAddress & (UE_DIR_OUT | UE_DIR_IN); + usb_config[0].interval = USB_DEFAULT_INTERVAL; + usb_config[0].flags.proxy_buffer = 1; + if (pre_scale != 0) + usb_config[0].flags.pre_scale_frames = 1; + usb_config[0].callback = &ugen_ctrl_fs_callback; + usb_config[0].timeout = 0; /* no timeout */ + usb_config[0].frames = u.popen->max_frames; + usb_config[0].bufsize = u.popen->max_bufsize; + usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */ + + if (usb_config[0].type == UE_CONTROL) { + if (f->udev->flags.usb_mode != USB_MODE_HOST) { + error = EINVAL; + break; + } + } else { + + isread = ((usb_config[0].endpoint & + (UE_DIR_IN | UE_DIR_OUT)) == UE_DIR_IN); + + if (f->udev->flags.usb_mode != USB_MODE_HOST) { + isread = !isread; + } + /* check permissions */ + if (isread) { + if (!(fflags & FREAD)) { + error = EPERM; + break; + } + } else { + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + } + } + error = usbd_transfer_setup(f->udev, &iface_index, + f->fs_xfer + u.popen->ep_index, usb_config, 1, + f, f->priv_mtx); + if (error == 0) { + /* update maximums */ + u.popen->max_packet_length = + f->fs_xfer[u.popen->ep_index]->max_frame_size; + u.popen->max_bufsize = + f->fs_xfer[u.popen->ep_index]->max_data_length; + /* update number of frames */ + u.popen->max_frames = + f->fs_xfer[u.popen->ep_index]->nframes; + /* store index of endpoint */ + f->fs_xfer[u.popen->ep_index]->priv_fifo = + ((uint8_t *)0) + u.popen->ep_index; + } else { + error = ENOMEM; + } + break; + + case USB_FS_CLOSE: + if (u.pclose->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + if (f->fs_xfer[u.pclose->ep_index] == NULL) { + error = EINVAL; + break; + } + usbd_transfer_unsetup(f->fs_xfer + u.pclose->ep_index, 1); + break; + + case USB_FS_CLEAR_STALL_SYNC: + if (u.pstall->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + if (f->fs_xfer[u.pstall->ep_index] == NULL) { + error = EINVAL; + break; + } + if (f->udev->flags.usb_mode != USB_MODE_HOST) { + error = EINVAL; + break; + } + mtx_lock(f->priv_mtx); + error = usbd_transfer_pending(f->fs_xfer[u.pstall->ep_index]); + mtx_unlock(f->priv_mtx); + + if (error) { + return (EBUSY); + } + ep = f->fs_xfer[u.pstall->ep_index]->endpoint; + + /* setup a clear-stall packet */ + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + req.wIndex[0] = ep->edesc->bEndpointAddress; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + error = usbd_do_request(f->udev, NULL, &req, NULL); + if (error == 0) { + usbd_clear_data_toggle(f->udev, ep); + } else { + error = ENXIO; + } + break; + + default: + error = ENOIOCTL; + break; + } + + DPRINTFN(6, "error=%d\n", error); + + return (error); +} + +static int +ugen_set_short_xfer(struct usb_fifo *f, void *addr) +{ + uint8_t t; + + if (*(int *)addr) + t = 1; + else + t = 0; + + if (f->flag_short == t) { + /* same value like before - accept */ + return (0); + } + if (f->xfer[0] || f->xfer[1]) { + /* cannot change this during transfer */ + return (EBUSY); + } + f->flag_short = t; + return (0); +} + +static int +ugen_set_timeout(struct usb_fifo *f, void *addr) +{ + f->timeout = *(int *)addr; + if (f->timeout > 65535) { + /* limit user input */ + f->timeout = 65535; + } + return (0); +} + +static int +ugen_get_frame_size(struct usb_fifo *f, void *addr) +{ + if (f->xfer[0]) { + *(int *)addr = f->xfer[0]->max_frame_size; + } else { + return (EINVAL); + } + return (0); +} + +static int +ugen_set_buffer_size(struct usb_fifo *f, void *addr) +{ + usb_frlength_t t; + + if (*(int *)addr < 0) + t = 0; /* use "wMaxPacketSize" */ + else if (*(int *)addr < (256 * 1024)) + t = *(int *)addr; + else + t = 256 * 1024; + + if (f->bufsize == t) { + /* same value like before - accept */ + return (0); + } + if (f->xfer[0] || f->xfer[1]) { + /* cannot change this during transfer */ + return (EBUSY); + } + f->bufsize = t; + return (0); +} + +static int +ugen_get_buffer_size(struct usb_fifo *f, void *addr) +{ + *(int *)addr = f->bufsize; + return (0); +} + +static int +ugen_get_iface_desc(struct usb_fifo *f, + struct usb_interface_descriptor *idesc) +{ + struct usb_interface *iface; + + iface = usbd_get_iface(f->udev, f->iface_index); + if (iface && iface->idesc) { + *idesc = *(iface->idesc); + } else { + return (EIO); + } + return (0); +} + +static int +ugen_get_endpoint_desc(struct usb_fifo *f, + struct usb_endpoint_descriptor *ed) +{ + struct usb_endpoint *ep; + + ep = usb_fifo_softc(f); + + if (ep && ep->edesc) { + *ed = *ep->edesc; + } else { + return (EINVAL); + } + return (0); +} + +static int +ugen_set_power_mode(struct usb_fifo *f, int mode) +{ + struct usb_device *udev = f->udev; + int err; + uint8_t old_mode; + + if ((udev == NULL) || + (udev->parent_hub == NULL)) { + return (EINVAL); + } + err = priv_check(curthread, PRIV_DRIVER); + if (err) + return (err); + + /* get old power mode */ + old_mode = udev->power_mode; + + /* if no change, then just return */ + if (old_mode == mode) + return (0); + + switch (mode) { + case USB_POWER_MODE_OFF: + /* get the device unconfigured */ + err = ugen_set_config(f, USB_UNCONFIG_INDEX); + if (err) { + DPRINTFN(0, "Could not unconfigure " + "device (ignored)\n"); + } + + /* clear port enable */ + err = usbd_req_clear_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_ENABLE); + break; + + case USB_POWER_MODE_ON: + case USB_POWER_MODE_SAVE: + break; + + case USB_POWER_MODE_RESUME: +#if USB_HAVE_POWERD + /* let USB-powerd handle resume */ + USB_BUS_LOCK(udev->bus); + udev->pwr_save.write_refs++; + udev->pwr_save.last_xfer_time = ticks; + USB_BUS_UNLOCK(udev->bus); + + /* set new power mode */ + usbd_set_power_mode(udev, USB_POWER_MODE_SAVE); + + /* wait for resume to complete */ + usb_pause_mtx(NULL, hz / 4); + + /* clear write reference */ + USB_BUS_LOCK(udev->bus); + udev->pwr_save.write_refs--; + USB_BUS_UNLOCK(udev->bus); +#endif + mode = USB_POWER_MODE_SAVE; + break; + + case USB_POWER_MODE_SUSPEND: +#if USB_HAVE_POWERD + /* let USB-powerd handle suspend */ + USB_BUS_LOCK(udev->bus); + udev->pwr_save.last_xfer_time = ticks - (256 * hz); + USB_BUS_UNLOCK(udev->bus); +#endif + mode = USB_POWER_MODE_SAVE; + break; + + default: + return (EINVAL); + } + + if (err) + return (ENXIO); /* I/O failure */ + + /* if we are powered off we need to re-enumerate first */ + if (old_mode == USB_POWER_MODE_OFF) { + if (udev->flags.usb_mode == USB_MODE_HOST) { + if (udev->re_enumerate_wait == 0) + udev->re_enumerate_wait = 1; + } + /* set power mode will wake up the explore thread */ + } + + /* set new power mode */ + usbd_set_power_mode(udev, mode); + + return (0); /* success */ +} + +static int +ugen_get_power_mode(struct usb_fifo *f) +{ + struct usb_device *udev = f->udev; + + if (udev == NULL) + return (USB_POWER_MODE_ON); + + return (udev->power_mode); +} + +static int +ugen_do_port_feature(struct usb_fifo *f, uint8_t port_no, + uint8_t set, uint16_t feature) +{ + struct usb_device *udev = f->udev; + struct usb_hub *hub; + int err; + + err = priv_check(curthread, PRIV_DRIVER); + if (err) { + return (err); + } + if (port_no == 0) { + return (EINVAL); + } + if ((udev == NULL) || + (udev->hub == NULL)) { + return (EINVAL); + } + hub = udev->hub; + + if (port_no > hub->nports) { + return (EINVAL); + } + if (set) + err = usbd_req_set_port_feature(udev, + NULL, port_no, feature); + else + err = usbd_req_clear_port_feature(udev, + NULL, port_no, feature); + + if (err) + return (ENXIO); /* failure */ + + return (0); /* success */ +} + +static int +ugen_iface_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags) +{ + struct usb_fifo *f_rx; + struct usb_fifo *f_tx; + int error = 0; + + f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX]; + f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX]; + + switch (cmd) { + case USB_SET_RX_SHORT_XFER: + if (fflags & FREAD) { + error = ugen_set_short_xfer(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_TX_FORCE_SHORT: + if (fflags & FWRITE) { + error = ugen_set_short_xfer(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_RX_TIMEOUT: + if (fflags & FREAD) { + error = ugen_set_timeout(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_TX_TIMEOUT: + if (fflags & FWRITE) { + error = ugen_set_timeout(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_FRAME_SIZE: + if (fflags & FREAD) { + error = ugen_get_frame_size(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_FRAME_SIZE: + if (fflags & FWRITE) { + error = ugen_get_frame_size(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_RX_BUFFER_SIZE: + if (fflags & FREAD) { + error = ugen_set_buffer_size(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_TX_BUFFER_SIZE: + if (fflags & FWRITE) { + error = ugen_set_buffer_size(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_BUFFER_SIZE: + if (fflags & FREAD) { + error = ugen_get_buffer_size(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_BUFFER_SIZE: + if (fflags & FWRITE) { + error = ugen_get_buffer_size(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_INTERFACE_DESC: + if (fflags & FREAD) { + error = ugen_get_iface_desc(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_INTERFACE_DESC: + if (fflags & FWRITE) { + error = ugen_get_iface_desc(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_ENDPOINT_DESC: + if (fflags & FREAD) { + error = ugen_get_endpoint_desc(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_ENDPOINT_DESC: + if (fflags & FWRITE) { + error = ugen_get_endpoint_desc(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_RX_STALL_FLAG: + if ((fflags & FREAD) && (*(int *)addr)) { + f_rx->flag_stall = 1; + } + break; + + case USB_SET_TX_STALL_FLAG: + if ((fflags & FWRITE) && (*(int *)addr)) { + f_tx->flag_stall = 1; + } + break; + + default: + error = ENOIOCTL; + break; + } + return (error); +} + +static int +ugen_ioctl_post(struct usb_fifo *f, u_long cmd, void *addr, int fflags) +{ + union { + struct usb_interface_descriptor *idesc; + struct usb_alt_interface *ai; + struct usb_device_descriptor *ddesc; + struct usb_config_descriptor *cdesc; + struct usb_device_stats *stat; + struct usb_fs_init *pinit; + struct usb_fs_uninit *puninit; + uint32_t *ptime; + void *addr; + int *pint; + } u; + struct usb_device_descriptor *dtemp; + struct usb_config_descriptor *ctemp; + struct usb_interface *iface; + int error = 0; + uint8_t n; + + u.addr = addr; + + DPRINTFN(6, "cmd=0x%08lx\n", cmd); + + switch (cmd) { + case USB_DISCOVER: + usb_needs_explore_all(); + break; + + case USB_SETDEBUG: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + usb_debug = *(int *)addr; + break; + + case USB_GET_CONFIG: + *(int *)addr = f->udev->curr_config_index; + break; + + case USB_SET_CONFIG: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + error = ugen_set_config(f, *(int *)addr); + break; + + case USB_GET_ALTINTERFACE: + iface = usbd_get_iface(f->udev, + u.ai->uai_interface_index); + if (iface && iface->idesc) { + u.ai->uai_alt_index = iface->alt_index; + } else { + error = EINVAL; + } + break; + + case USB_SET_ALTINTERFACE: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + error = ugen_set_interface(f, + u.ai->uai_interface_index, u.ai->uai_alt_index); + break; + + case USB_GET_DEVICE_DESC: + dtemp = usbd_get_device_descriptor(f->udev); + if (!dtemp) { + error = EIO; + break; + } + *u.ddesc = *dtemp; + break; + + case USB_GET_CONFIG_DESC: + ctemp = usbd_get_config_descriptor(f->udev); + if (!ctemp) { + error = EIO; + break; + } + *u.cdesc = *ctemp; + break; + + case USB_GET_FULL_DESC: + error = ugen_get_cdesc(f, addr); + break; + + case USB_GET_STRING_DESC: + error = ugen_get_sdesc(f, addr); + break; + + case USB_GET_IFACE_DRIVER: + error = ugen_get_iface_driver(f, addr); + break; + + case USB_REQUEST: + case USB_DO_REQUEST: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + error = ugen_do_request(f, addr); + break; + + case USB_DEVICEINFO: + case USB_GET_DEVICEINFO: + error = usb_gen_fill_deviceinfo(f, addr); + break; + + case USB_DEVICESTATS: + for (n = 0; n != 4; n++) { + + u.stat->uds_requests_fail[n] = + f->udev->bus->stats_err.uds_requests[n]; + + u.stat->uds_requests_ok[n] = + f->udev->bus->stats_ok.uds_requests[n]; + } + break; + + case USB_DEVICEENUMERATE: + error = ugen_re_enumerate(f); + break; + + case USB_GET_PLUGTIME: + *u.ptime = f->udev->plugtime; + break; + + case USB_CLAIM_INTERFACE: + case USB_RELEASE_INTERFACE: + /* TODO */ + break; + + case USB_IFACE_DRIVER_ACTIVE: + + n = *u.pint & 0xFF; + + iface = usbd_get_iface(f->udev, n); + + if (iface && iface->subdev) + error = 0; + else + error = ENXIO; + break; + + case USB_IFACE_DRIVER_DETACH: + + error = priv_check(curthread, PRIV_DRIVER); + + if (error) + break; + + n = *u.pint & 0xFF; + + if (n == USB_IFACE_INDEX_ANY) { + error = EINVAL; + break; + } + + usb_detach_device(f->udev, n, 0); + break; + + case USB_SET_POWER_MODE: + error = ugen_set_power_mode(f, *u.pint); + break; + + case USB_GET_POWER_MODE: + *u.pint = ugen_get_power_mode(f); + break; + + case USB_SET_PORT_ENABLE: + error = ugen_do_port_feature(f, + *u.pint, 1, UHF_PORT_ENABLE); + break; + + case USB_SET_PORT_DISABLE: + error = ugen_do_port_feature(f, + *u.pint, 0, UHF_PORT_ENABLE); + break; + + case USB_FS_INIT: + /* verify input parameters */ + if (u.pinit->pEndpoints == NULL) { + error = EINVAL; + break; + } + if (u.pinit->ep_index_max > 127) { + error = EINVAL; + break; + } + if (u.pinit->ep_index_max == 0) { + error = EINVAL; + break; + } + if (f->fs_xfer != NULL) { + error = EBUSY; + break; + } + if (f->dev_ep_index != 0) { + error = EINVAL; + break; + } + if (ugen_fifo_in_use(f, fflags)) { + error = EBUSY; + break; + } + error = usb_fifo_alloc_buffer(f, 1, u.pinit->ep_index_max); + if (error) { + break; + } + f->fs_xfer = malloc(sizeof(f->fs_xfer[0]) * + u.pinit->ep_index_max, M_USB, M_WAITOK | M_ZERO); + if (f->fs_xfer == NULL) { + usb_fifo_free_buffer(f); + error = ENOMEM; + break; + } + f->fs_ep_max = u.pinit->ep_index_max; + f->fs_ep_ptr = u.pinit->pEndpoints; + break; + + case USB_FS_UNINIT: + if (u.puninit->dummy != 0) { + error = EINVAL; + break; + } + error = ugen_fs_uninit(f); + break; + + default: + mtx_lock(f->priv_mtx); + error = ugen_iface_ioctl(f, cmd, addr, fflags); + mtx_unlock(f->priv_mtx); + break; + } + DPRINTFN(6, "error=%d\n", error); + return (error); +} + +static void +ugen_ctrl_fs_callback(struct usb_xfer *xfer, usb_error_t error) +{ + ; /* workaround for a bug in "indent" */ + + DPRINTF("st=%u alen=%u aframes=%u\n", + USB_GET_STATE(xfer), xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + usbd_transfer_submit(xfer); + break; + default: + ugen_fs_set_complete(xfer->priv_sc, USB_P2U(xfer->priv_fifo)); + break; + } +} +#endif /* USB_HAVE_UGEN */ diff --git a/sys/bus/u4b/usb_generic.h b/sys/bus/u4b/usb_generic.h new file mode 100644 index 0000000000..835ec72afd --- /dev/null +++ b/sys/bus/u4b/usb_generic.h @@ -0,0 +1,33 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_GENERIC_H_ +#define _USB_GENERIC_H_ + +extern struct usb_fifo_methods usb_ugen_methods; +int ugen_do_request(struct usb_fifo *f, struct usb_ctl_request *ur); + +#endif /* _USB_GENERIC_H_ */ diff --git a/sys/bus/u4b/usb_handle_request.c b/sys/bus/u4b/usb_handle_request.c new file mode 100644 index 0000000000..2d8d107c61 --- /dev/null +++ b/sys/bus/u4b/usb_handle_request.c @@ -0,0 +1,806 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usb_if.h" + +#define USB_DEBUG_VAR usb_debug + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* function prototypes */ + +static uint8_t usb_handle_get_stall(struct usb_device *, uint8_t); +static usb_error_t usb_handle_remote_wakeup(struct usb_xfer *, uint8_t); +static usb_error_t usb_handle_request(struct usb_xfer *); +static usb_error_t usb_handle_set_config(struct usb_xfer *, uint8_t); +static usb_error_t usb_handle_set_stall(struct usb_xfer *, uint8_t, + uint8_t); +static usb_error_t usb_handle_iface_request(struct usb_xfer *, void **, + uint16_t *, struct usb_device_request, uint16_t, + uint8_t); + +/*------------------------------------------------------------------------* + * usb_handle_request_callback + * + * This function is the USB callback for generic USB Device control + * transfers. + *------------------------------------------------------------------------*/ +void +usb_handle_request_callback(struct usb_xfer *xfer, usb_error_t error) +{ + usb_error_t err; + + /* check the current transfer state */ + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + + /* handle the request */ + err = usb_handle_request(xfer); + + if (err) { + + if (err == USB_ERR_BAD_CONTEXT) { + /* we need to re-setup the control transfer */ + usb_needs_explore(xfer->xroot->bus, 0); + break; + } + goto tr_restart; + } + usbd_transfer_submit(xfer); + break; + + default: + /* check if a control transfer is active */ + if (xfer->flags_int.control_rem != 0xFFFF) { + /* handle the request */ + err = usb_handle_request(xfer); + } + if (xfer->error != USB_ERR_CANCELLED) { + /* should not happen - try stalling */ + goto tr_restart; + } + break; + } + return; + +tr_restart: + /* + * If a control transfer is active, stall it, and wait for the + * next control transfer. + */ + usbd_xfer_set_frame_len(xfer, 0, sizeof(struct usb_device_request)); + xfer->nframes = 1; + xfer->flags.manual_status = 1; + xfer->flags.force_short_xfer = 0; + usbd_xfer_set_stall(xfer); /* cancel previous transfer, if any */ + usbd_transfer_submit(xfer); +} + +/*------------------------------------------------------------------------* + * usb_handle_set_config + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb_error_t +usb_handle_set_config(struct usb_xfer *xfer, uint8_t conf_no) +{ + struct usb_device *udev = xfer->xroot->udev; + usb_error_t err = 0; + + /* + * We need to protect against other threads doing probe and + * attach: + */ + USB_XFER_UNLOCK(xfer); + + usbd_enum_lock(udev); + + if (conf_no == USB_UNCONFIG_NO) { + conf_no = USB_UNCONFIG_INDEX; + } else { + /* + * The relationship between config number and config index + * is very simple in our case: + */ + conf_no--; + } + + if (usbd_set_config_index(udev, conf_no)) { + DPRINTF("set config %d failed\n", conf_no); + err = USB_ERR_STALLED; + goto done; + } + if (usb_probe_and_attach(udev, USB_IFACE_INDEX_ANY)) { + DPRINTF("probe and attach failed\n"); + err = USB_ERR_STALLED; + goto done; + } +done: + usbd_enum_unlock(udev); + USB_XFER_LOCK(xfer); + return (err); +} + +static usb_error_t +usb_check_alt_setting(struct usb_device *udev, + struct usb_interface *iface, uint8_t alt_index) +{ + uint8_t do_unlock; + usb_error_t err = 0; + + /* automatic locking */ + if (usbd_enum_is_locked(udev)) { + do_unlock = 0; + } else { + do_unlock = 1; + usbd_enum_lock(udev); + } + + if (alt_index >= usbd_get_no_alts(udev->cdesc, iface->idesc)) + err = USB_ERR_INVAL; + + if (do_unlock) + usbd_enum_unlock(udev); + + return (err); +} + +/*------------------------------------------------------------------------* + * usb_handle_iface_request + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb_error_t +usb_handle_iface_request(struct usb_xfer *xfer, + void **ppdata, uint16_t *plen, + struct usb_device_request req, uint16_t off, uint8_t state) +{ + struct usb_interface *iface; + struct usb_interface *iface_parent; /* parent interface */ + struct usb_device *udev = xfer->xroot->udev; + int error; + uint8_t iface_index; + uint8_t temp_state; + + if ((req.bmRequestType & 0x1F) == UT_INTERFACE) { + iface_index = req.wIndex[0]; /* unicast */ + } else { + iface_index = 0; /* broadcast */ + } + + /* + * We need to protect against other threads doing probe and + * attach: + */ + USB_XFER_UNLOCK(xfer); + + usbd_enum_lock(udev); + + error = ENXIO; + +tr_repeat: + iface = usbd_get_iface(udev, iface_index); + if ((iface == NULL) || + (iface->idesc == NULL)) { + /* end of interfaces non-existing interface */ + goto tr_stalled; + } + /* set initial state */ + + temp_state = state; + + /* forward request to interface, if any */ + + if ((error != 0) && + (error != ENOTTY) && + (iface->subdev != NULL) && + device_is_attached(iface->subdev)) { +#if 0 + DEVMETHOD(usb_handle_request, NULL); /* dummy */ +#endif + error = USB_HANDLE_REQUEST(iface->subdev, + &req, ppdata, plen, + off, &temp_state); + } + iface_parent = usbd_get_iface(udev, iface->parent_iface_index); + + if ((iface_parent == NULL) || + (iface_parent->idesc == NULL)) { + /* non-existing interface */ + iface_parent = NULL; + } + /* forward request to parent interface, if any */ + + if ((error != 0) && + (error != ENOTTY) && + (iface_parent != NULL) && + (iface_parent->subdev != NULL) && + ((req.bmRequestType & 0x1F) == UT_INTERFACE) && + (iface_parent->subdev != iface->subdev) && + device_is_attached(iface_parent->subdev)) { + error = USB_HANDLE_REQUEST(iface_parent->subdev, + &req, ppdata, plen, off, &temp_state); + } + if (error == 0) { + /* negativly adjust pointer and length */ + *ppdata = ((uint8_t *)(*ppdata)) - off; + *plen += off; + + if ((state == USB_HR_NOT_COMPLETE) && + (temp_state == USB_HR_COMPLETE_OK)) + goto tr_short; + else + goto tr_valid; + } else if (error == ENOTTY) { + goto tr_stalled; + } + if ((req.bmRequestType & 0x1F) != UT_INTERFACE) { + iface_index++; /* iterate */ + goto tr_repeat; + } + if (state != USB_HR_NOT_COMPLETE) { + /* we are complete */ + goto tr_valid; + } + switch (req.bmRequestType) { + case UT_WRITE_INTERFACE: + switch (req.bRequest) { + case UR_SET_INTERFACE: + /* + * We assume that the endpoints are the same + * accross the alternate settings. + * + * Reset the endpoints, because re-attaching + * only a part of the device is not possible. + */ + error = usb_check_alt_setting(udev, + iface, req.wValue[0]); + if (error) { + DPRINTF("alt setting does not exist %s\n", + usbd_errstr(error)); + goto tr_stalled; + } + error = usb_reset_iface_endpoints(udev, iface_index); + if (error) { + DPRINTF("alt setting failed %s\n", + usbd_errstr(error)); + goto tr_stalled; + } + /* update the current alternate setting */ + iface->alt_index = req.wValue[0]; + break; + + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (req.bRequest) { + case UR_GET_INTERFACE: + *ppdata = &iface->alt_index; + *plen = 1; + break; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } +tr_valid: + usbd_enum_unlock(udev); + USB_XFER_LOCK(xfer); + return (0); + +tr_short: + usbd_enum_unlock(udev); + USB_XFER_LOCK(xfer); + return (USB_ERR_SHORT_XFER); + +tr_stalled: + usbd_enum_unlock(udev); + USB_XFER_LOCK(xfer); + return (USB_ERR_STALLED); +} + +/*------------------------------------------------------------------------* + * usb_handle_stall + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb_error_t +usb_handle_set_stall(struct usb_xfer *xfer, uint8_t ep, uint8_t do_stall) +{ + struct usb_device *udev = xfer->xroot->udev; + usb_error_t err; + + USB_XFER_UNLOCK(xfer); + err = usbd_set_endpoint_stall(udev, + usbd_get_ep_by_addr(udev, ep), do_stall); + USB_XFER_LOCK(xfer); + return (err); +} + +/*------------------------------------------------------------------------* + * usb_handle_get_stall + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +usb_handle_get_stall(struct usb_device *udev, uint8_t ea_val) +{ + struct usb_endpoint *ep; + uint8_t halted; + + ep = usbd_get_ep_by_addr(udev, ea_val); + if (ep == NULL) { + /* nothing to do */ + return (0); + } + USB_BUS_LOCK(udev->bus); + halted = ep->is_stalled; + USB_BUS_UNLOCK(udev->bus); + + return (halted); +} + +/*------------------------------------------------------------------------* + * usb_handle_remote_wakeup + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb_error_t +usb_handle_remote_wakeup(struct usb_xfer *xfer, uint8_t is_on) +{ + struct usb_device *udev; + struct usb_bus *bus; + + udev = xfer->xroot->udev; + bus = udev->bus; + + USB_BUS_LOCK(bus); + + if (is_on) { + udev->flags.remote_wakeup = 1; + } else { + udev->flags.remote_wakeup = 0; + } + + USB_BUS_UNLOCK(bus); + +#if USB_HAVE_POWERD + /* In case we are out of sync, update the power state. */ + usb_bus_power_update(udev->bus); +#endif + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb_handle_request + * + * Internal state sequence: + * + * USB_HR_NOT_COMPLETE -> USB_HR_COMPLETE_OK v USB_HR_COMPLETE_ERR + * + * Returns: + * 0: Ready to start hardware + * Else: Stall current transfer, if any + *------------------------------------------------------------------------*/ +static usb_error_t +usb_handle_request(struct usb_xfer *xfer) +{ + struct usb_device_request req; + struct usb_device *udev; + const void *src_zcopy; /* zero-copy source pointer */ + const void *src_mcopy; /* non zero-copy source pointer */ + uint16_t off; /* data offset */ + uint16_t rem; /* data remainder */ + uint16_t max_len; /* max fragment length */ + uint16_t wValue; + uint16_t wIndex; + uint8_t state; + uint8_t is_complete = 1; + usb_error_t err; + union { + uWord wStatus; + uint8_t buf[2]; + } temp; + + /* + * Filter the USB transfer state into + * something which we understand: + */ + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + state = USB_HR_NOT_COMPLETE; + + if (!xfer->flags_int.control_act) { + /* nothing to do */ + goto tr_stalled; + } + break; + case USB_ST_TRANSFERRED: + if (!xfer->flags_int.control_act) { + state = USB_HR_COMPLETE_OK; + } else { + state = USB_HR_NOT_COMPLETE; + } + break; + default: + state = USB_HR_COMPLETE_ERR; + break; + } + + /* reset frame stuff */ + + usbd_xfer_set_frame_len(xfer, 0, 0); + + usbd_xfer_set_frame_offset(xfer, 0, 0); + usbd_xfer_set_frame_offset(xfer, sizeof(req), 1); + + /* get the current request, if any */ + + usbd_copy_out(xfer->frbuffers, 0, &req, sizeof(req)); + + if (xfer->flags_int.control_rem == 0xFFFF) { + /* first time - not initialised */ + rem = UGETW(req.wLength); + off = 0; + } else { + /* not first time - initialised */ + rem = xfer->flags_int.control_rem; + off = UGETW(req.wLength) - rem; + } + + /* set some defaults */ + + max_len = 0; + src_zcopy = NULL; + src_mcopy = NULL; + udev = xfer->xroot->udev; + + /* get some request fields decoded */ + + wValue = UGETW(req.wValue); + wIndex = UGETW(req.wIndex); + + DPRINTF("req 0x%02x 0x%02x 0x%04x 0x%04x " + "off=0x%x rem=0x%x, state=%d\n", req.bmRequestType, + req.bRequest, wValue, wIndex, off, rem, state); + + /* demultiplex the control request */ + + switch (req.bmRequestType) { + case UT_READ_DEVICE: + if (state != USB_HR_NOT_COMPLETE) { + break; + } + switch (req.bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (req.bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + switch (wValue) { + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (wValue) { + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (req.bRequest) { + case UR_CLEAR_FEATURE: + switch (wValue) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (wValue) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (req.bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + default: + /* we use "USB_ADD_BYTES" to de-const the src_zcopy */ + err = usb_handle_iface_request(xfer, + USB_ADD_BYTES(&src_zcopy, 0), + &max_len, req, off, state); + if (err == 0) { + is_complete = 0; + goto tr_valid; + } else if (err == USB_ERR_SHORT_XFER) { + goto tr_valid; + } + /* + * Reset zero-copy pointer and max length + * variable in case they were unintentionally + * set: + */ + src_zcopy = NULL; + max_len = 0; + + /* + * Check if we have a vendor specific + * descriptor: + */ + goto tr_handle_get_descriptor; + } + goto tr_valid; + +tr_handle_get_descriptor: + err = (usb_temp_get_desc_p) (udev, &req, &src_zcopy, &max_len); + if (err) + goto tr_stalled; + if (src_zcopy == NULL) + goto tr_stalled; + goto tr_valid; + +tr_handle_get_config: + temp.buf[0] = udev->curr_config_no; + src_mcopy = temp.buf; + max_len = 1; + goto tr_valid; + +tr_handle_get_status: + + wValue = 0; + + USB_BUS_LOCK(udev->bus); + if (udev->flags.remote_wakeup) { + wValue |= UDS_REMOTE_WAKEUP; + } + if (udev->flags.self_powered) { + wValue |= UDS_SELF_POWERED; + } + USB_BUS_UNLOCK(udev->bus); + + USETW(temp.wStatus, wValue); + src_mcopy = temp.wStatus; + max_len = sizeof(temp.wStatus); + goto tr_valid; + +tr_handle_set_address: + if (state == USB_HR_NOT_COMPLETE) { + if (wValue >= 0x80) { + /* invalid value */ + goto tr_stalled; + } else if (udev->curr_config_no != 0) { + /* we are configured ! */ + goto tr_stalled; + } + } else if (state != USB_HR_NOT_COMPLETE) { + udev->address = (wValue & 0x7F); + goto tr_bad_context; + } + goto tr_valid; + +tr_handle_set_config: + if (state == USB_HR_NOT_COMPLETE) { + if (usb_handle_set_config(xfer, req.wValue[0])) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_clear_halt: + if (state == USB_HR_NOT_COMPLETE) { + if (usb_handle_set_stall(xfer, req.wIndex[0], 0)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_clear_wakeup: + if (state == USB_HR_NOT_COMPLETE) { + if (usb_handle_remote_wakeup(xfer, 0)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_set_halt: + if (state == USB_HR_NOT_COMPLETE) { + if (usb_handle_set_stall(xfer, req.wIndex[0], 1)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_set_wakeup: + if (state == USB_HR_NOT_COMPLETE) { + if (usb_handle_remote_wakeup(xfer, 1)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_get_ep_status: + if (state == USB_HR_NOT_COMPLETE) { + temp.wStatus[0] = + usb_handle_get_stall(udev, req.wIndex[0]); + temp.wStatus[1] = 0; + src_mcopy = temp.wStatus; + max_len = sizeof(temp.wStatus); + } + goto tr_valid; + +tr_valid: + if (state != USB_HR_NOT_COMPLETE) { + goto tr_stalled; + } + /* subtract offset from length */ + + max_len -= off; + + /* Compute the real maximum data length */ + + if (max_len > xfer->max_data_length) { + max_len = usbd_xfer_max_len(xfer); + } + if (max_len > rem) { + max_len = rem; + } + /* + * If the remainder is greater than the maximum data length, + * we need to truncate the value for the sake of the + * comparison below: + */ + if (rem > xfer->max_data_length) { + rem = usbd_xfer_max_len(xfer); + } + if ((rem != max_len) && (is_complete != 0)) { + /* + * If we don't transfer the data we can transfer, then + * the transfer is short ! + */ + xfer->flags.force_short_xfer = 1; + xfer->nframes = 2; + } else { + /* + * Default case + */ + xfer->flags.force_short_xfer = 0; + xfer->nframes = max_len ? 2 : 1; + } + if (max_len > 0) { + if (src_mcopy) { + src_mcopy = USB_ADD_BYTES(src_mcopy, off); + usbd_copy_in(xfer->frbuffers + 1, 0, + src_mcopy, max_len); + usbd_xfer_set_frame_len(xfer, 1, max_len); + } else { + usbd_xfer_set_frame_data(xfer, 1, + USB_ADD_BYTES(src_zcopy, off), max_len); + } + } else { + /* the end is reached, send status */ + xfer->flags.manual_status = 0; + usbd_xfer_set_frame_len(xfer, 1, 0); + } + DPRINTF("success\n"); + return (0); /* success */ + +tr_stalled: + DPRINTF("%s\n", (state != USB_HR_NOT_COMPLETE) ? + "complete" : "stalled"); + return (USB_ERR_STALLED); + +tr_bad_context: + DPRINTF("bad context\n"); + return (USB_ERR_BAD_CONTEXT); +} diff --git a/sys/bus/u4b/usb_hid.c b/sys/bus/u4b/usb_hid.c new file mode 100644 index 0000000000..6bd51cd5b3 --- /dev/null +++ b/sys/bus/u4b/usb_hid.c @@ -0,0 +1,847 @@ +/* $NetBSD: hid.c,v 1.17 2001/11/13 06:24:53 lukem Exp $ */ + + +#include +__FBSDID("$FreeBSD$"); +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb_debug + +#include +#include +#include +#include +#include + +static void hid_clear_local(struct hid_item *); +static uint8_t hid_get_byte(struct hid_data *s, const uint16_t wSize); + +#define MAXUSAGE 64 +#define MAXPUSH 4 +#define MAXID 16 + +struct hid_pos_data { + int32_t rid; + uint32_t pos; +}; + +struct hid_data { + const uint8_t *start; + const uint8_t *end; + const uint8_t *p; + struct hid_item cur[MAXPUSH]; + struct hid_pos_data last_pos[MAXID]; + int32_t usages_min[MAXUSAGE]; + int32_t usages_max[MAXUSAGE]; + int32_t usage_last; /* last seen usage */ + uint32_t loc_size; /* last seen size */ + uint32_t loc_count; /* last seen count */ + uint8_t kindset; /* we have 5 kinds so 8 bits are enough */ + uint8_t pushlevel; /* current pushlevel */ + uint8_t ncount; /* end usage item count */ + uint8_t icount; /* current usage item count */ + uint8_t nusage; /* end "usages_min/max" index */ + uint8_t iusage; /* current "usages_min/max" index */ + uint8_t ousage; /* current "usages_min/max" offset */ + uint8_t susage; /* usage set flags */ +}; + +/*------------------------------------------------------------------------* + * hid_clear_local + *------------------------------------------------------------------------*/ +static void +hid_clear_local(struct hid_item *c) +{ + + c->loc.count = 0; + c->loc.size = 0; + c->usage = 0; + c->usage_minimum = 0; + c->usage_maximum = 0; + c->designator_index = 0; + c->designator_minimum = 0; + c->designator_maximum = 0; + c->string_index = 0; + c->string_minimum = 0; + c->string_maximum = 0; + c->set_delimiter = 0; +} + +static void +hid_switch_rid(struct hid_data *s, struct hid_item *c, int32_t next_rID) +{ + uint8_t i; + + /* check for same report ID - optimise */ + + if (c->report_ID == next_rID) + return; + + /* save current position for current rID */ + + if (c->report_ID == 0) { + i = 0; + } else { + for (i = 1; i != MAXID; i++) { + if (s->last_pos[i].rid == c->report_ID) + break; + if (s->last_pos[i].rid == 0) + break; + } + } + if (i != MAXID) { + s->last_pos[i].rid = c->report_ID; + s->last_pos[i].pos = c->loc.pos; + } + + /* store next report ID */ + + c->report_ID = next_rID; + + /* lookup last position for next rID */ + + if (next_rID == 0) { + i = 0; + } else { + for (i = 1; i != MAXID; i++) { + if (s->last_pos[i].rid == next_rID) + break; + if (s->last_pos[i].rid == 0) + break; + } + } + if (i != MAXID) { + s->last_pos[i].rid = next_rID; + c->loc.pos = s->last_pos[i].pos; + } else { + DPRINTF("Out of RID entries, position is set to zero!\n"); + c->loc.pos = 0; + } +} + +/*------------------------------------------------------------------------* + * hid_start_parse + *------------------------------------------------------------------------*/ +struct hid_data * +hid_start_parse(const void *d, usb_size_t len, int kindset) +{ + struct hid_data *s; + + if ((kindset-1) & kindset) { + DPRINTFN(0, "Only one bit can be " + "set in the kindset\n"); + return (NULL); + } + + s = malloc(sizeof *s, M_TEMP, M_WAITOK | M_ZERO); + s->start = s->p = d; + s->end = ((const uint8_t *)d) + len; + s->kindset = kindset; + return (s); +} + +/*------------------------------------------------------------------------* + * hid_end_parse + *------------------------------------------------------------------------*/ +void +hid_end_parse(struct hid_data *s) +{ + if (s == NULL) + return; + + free(s, M_TEMP); +} + +/*------------------------------------------------------------------------* + * get byte from HID descriptor + *------------------------------------------------------------------------*/ +static uint8_t +hid_get_byte(struct hid_data *s, const uint16_t wSize) +{ + const uint8_t *ptr; + uint8_t retval; + + ptr = s->p; + + /* check if end is reached */ + if (ptr == s->end) + return (0); + + /* read out a byte */ + retval = *ptr; + + /* check if data pointer can be advanced by "wSize" bytes */ + if ((s->end - ptr) < wSize) + ptr = s->end; + else + ptr += wSize; + + /* update pointer */ + s->p = ptr; + + return (retval); +} + +/*------------------------------------------------------------------------* + * hid_get_item + *------------------------------------------------------------------------*/ +int +hid_get_item(struct hid_data *s, struct hid_item *h) +{ + struct hid_item *c; + unsigned int bTag, bType, bSize; + uint32_t oldpos; + int32_t mask; + int32_t dval; + + if (s == NULL) + return (0); + + c = &s->cur[s->pushlevel]; + + top: + /* check if there is an array of items */ + if (s->icount < s->ncount) { + /* get current usage */ + if (s->iusage < s->nusage) { + dval = s->usages_min[s->iusage] + s->ousage; + c->usage = dval; + s->usage_last = dval; + if (dval == s->usages_max[s->iusage]) { + s->iusage ++; + s->ousage = 0; + } else { + s->ousage ++; + } + } else { + DPRINTFN(1, "Using last usage\n"); + dval = s->usage_last; + } + s->icount ++; + /* + * Only copy HID item, increment position and return + * if correct kindset! + */ + if (s->kindset & (1 << c->kind)) { + *h = *c; + DPRINTFN(1, "%u,%u,%u\n", h->loc.pos, + h->loc.size, h->loc.count); + c->loc.pos += c->loc.size * c->loc.count; + return (1); + } + } + + /* reset state variables */ + s->icount = 0; + s->ncount = 0; + s->iusage = 0; + s->nusage = 0; + s->susage = 0; + s->ousage = 0; + hid_clear_local(c); + + /* get next item */ + while (s->p != s->end) { + + bSize = hid_get_byte(s, 1); + if (bSize == 0xfe) { + /* long item */ + bSize = hid_get_byte(s, 1); + bSize |= hid_get_byte(s, 1) << 8; + bTag = hid_get_byte(s, 1); + bType = 0xff; /* XXX what should it be */ + } else { + /* short item */ + bTag = bSize >> 4; + bType = (bSize >> 2) & 3; + bSize &= 3; + if (bSize == 3) + bSize = 4; + } + switch (bSize) { + case 0: + dval = 0; + mask = 0; + break; + case 1: + dval = (int8_t)hid_get_byte(s, 1); + mask = 0xFF; + break; + case 2: + dval = hid_get_byte(s, 1); + dval |= hid_get_byte(s, 1) << 8; + dval = (int16_t)dval; + mask = 0xFFFF; + break; + case 4: + dval = hid_get_byte(s, 1); + dval |= hid_get_byte(s, 1) << 8; + dval |= hid_get_byte(s, 1) << 16; + dval |= hid_get_byte(s, 1) << 24; + mask = 0xFFFFFFFF; + break; + default: + dval = hid_get_byte(s, bSize); + DPRINTFN(0, "bad length %u (data=0x%02x)\n", + bSize, dval); + continue; + } + + switch (bType) { + case 0: /* Main */ + switch (bTag) { + case 8: /* Input */ + c->kind = hid_input; + c->flags = dval; + ret: + c->loc.count = s->loc_count; + c->loc.size = s->loc_size; + + if (c->flags & HIO_VARIABLE) { + /* range check usage count */ + if (c->loc.count > 255) { + DPRINTFN(0, "Number of " + "items truncated to 255\n"); + s->ncount = 255; + } else + s->ncount = c->loc.count; + + /* + * The "top" loop will return + * one and one item: + */ + c->loc.count = 1; + } else { + s->ncount = 1; + } + goto top; + + case 9: /* Output */ + c->kind = hid_output; + c->flags = dval; + goto ret; + case 10: /* Collection */ + c->kind = hid_collection; + c->collection = dval; + c->collevel++; + c->usage = s->usage_last; + *h = *c; + return (1); + case 11: /* Feature */ + c->kind = hid_feature; + c->flags = dval; + goto ret; + case 12: /* End collection */ + c->kind = hid_endcollection; + if (c->collevel == 0) { + DPRINTFN(0, "invalid end collection\n"); + return (0); + } + c->collevel--; + *h = *c; + return (1); + default: + DPRINTFN(0, "Main bTag=%d\n", bTag); + break; + } + break; + case 1: /* Global */ + switch (bTag) { + case 0: + c->_usage_page = dval << 16; + break; + case 1: + c->logical_minimum = dval; + break; + case 2: + c->logical_maximum = dval; + break; + case 3: + c->physical_minimum = dval; + break; + case 4: + c->physical_maximum = dval; + break; + case 5: + c->unit_exponent = dval; + break; + case 6: + c->unit = dval; + break; + case 7: + /* mask because value is unsigned */ + s->loc_size = dval & mask; + break; + case 8: + hid_switch_rid(s, c, dval); + break; + case 9: + /* mask because value is unsigned */ + s->loc_count = dval & mask; + break; + case 10: /* Push */ + s->pushlevel ++; + if (s->pushlevel < MAXPUSH) { + s->cur[s->pushlevel] = *c; + /* store size and count */ + c->loc.size = s->loc_size; + c->loc.count = s->loc_count; + /* update current item pointer */ + c = &s->cur[s->pushlevel]; + } else { + DPRINTFN(0, "Cannot push " + "item @ %d\n", s->pushlevel); + } + break; + case 11: /* Pop */ + s->pushlevel --; + if (s->pushlevel < MAXPUSH) { + /* preserve position */ + oldpos = c->loc.pos; + c = &s->cur[s->pushlevel]; + /* restore size and count */ + s->loc_size = c->loc.size; + s->loc_count = c->loc.count; + /* set default item location */ + c->loc.pos = oldpos; + c->loc.size = 0; + c->loc.count = 0; + } else { + DPRINTFN(0, "Cannot pop " + "item @ %d\n", s->pushlevel); + } + break; + default: + DPRINTFN(0, "Global bTag=%d\n", bTag); + break; + } + break; + case 2: /* Local */ + switch (bTag) { + case 0: + if (bSize != 4) + dval = (dval & mask) | c->_usage_page; + + /* set last usage, in case of a collection */ + s->usage_last = dval; + + if (s->nusage < MAXUSAGE) { + s->usages_min[s->nusage] = dval; + s->usages_max[s->nusage] = dval; + s->nusage ++; + } else { + DPRINTFN(0, "max usage reached\n"); + } + + /* clear any pending usage sets */ + s->susage = 0; + break; + case 1: + s->susage |= 1; + + if (bSize != 4) + dval = (dval & mask) | c->_usage_page; + c->usage_minimum = dval; + + goto check_set; + case 2: + s->susage |= 2; + + if (bSize != 4) + dval = (dval & mask) | c->_usage_page; + c->usage_maximum = dval; + + check_set: + if (s->susage != 3) + break; + + /* sanity check */ + if ((s->nusage < MAXUSAGE) && + (c->usage_minimum <= c->usage_maximum)) { + /* add usage range */ + s->usages_min[s->nusage] = + c->usage_minimum; + s->usages_max[s->nusage] = + c->usage_maximum; + s->nusage ++; + } else { + DPRINTFN(0, "Usage set dropped\n"); + } + s->susage = 0; + break; + case 3: + c->designator_index = dval; + break; + case 4: + c->designator_minimum = dval; + break; + case 5: + c->designator_maximum = dval; + break; + case 7: + c->string_index = dval; + break; + case 8: + c->string_minimum = dval; + break; + case 9: + c->string_maximum = dval; + break; + case 10: + c->set_delimiter = dval; + break; + default: + DPRINTFN(0, "Local bTag=%d\n", bTag); + break; + } + break; + default: + DPRINTFN(0, "default bType=%d\n", bType); + break; + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * hid_report_size + *------------------------------------------------------------------------*/ +int +hid_report_size(const void *buf, usb_size_t len, enum hid_kind k, uint8_t *id) +{ + struct hid_data *d; + struct hid_item h; + uint32_t temp; + uint32_t hpos; + uint32_t lpos; + uint8_t any_id; + + any_id = 0; + hpos = 0; + lpos = 0xFFFFFFFF; + + for (d = hid_start_parse(buf, len, 1 << k); hid_get_item(d, &h);) { + if (h.kind == k) { + /* check for ID-byte presense */ + if ((h.report_ID != 0) && !any_id) { + if (id != NULL) + *id = h.report_ID; + any_id = 1; + } + /* compute minimum */ + if (lpos > h.loc.pos) + lpos = h.loc.pos; + /* compute end position */ + temp = h.loc.pos + (h.loc.size * h.loc.count); + /* compute maximum */ + if (hpos < temp) + hpos = temp; + } + } + hid_end_parse(d); + + /* safety check - can happen in case of currupt descriptors */ + if (lpos > hpos) + temp = 0; + else + temp = hpos - lpos; + + /* check for ID byte */ + if (any_id) + temp += 8; + else if (id != NULL) + *id = 0; + + /* return length in bytes rounded up */ + return ((temp + 7) / 8); +} + +/*------------------------------------------------------------------------* + * hid_locate + *------------------------------------------------------------------------*/ +int +hid_locate(const void *desc, usb_size_t size, uint32_t u, enum hid_kind k, + uint8_t index, struct hid_location *loc, uint32_t *flags, uint8_t *id) +{ + struct hid_data *d; + struct hid_item h; + + for (d = hid_start_parse(desc, size, 1 << k); hid_get_item(d, &h);) { + if (h.kind == k && !(h.flags & HIO_CONST) && h.usage == u) { + if (index--) + continue; + if (loc != NULL) + *loc = h.loc; + if (flags != NULL) + *flags = h.flags; + if (id != NULL) + *id = h.report_ID; + hid_end_parse(d); + return (1); + } + } + if (loc != NULL) + loc->size = 0; + if (flags != NULL) + *flags = 0; + if (id != NULL) + *id = 0; + hid_end_parse(d); + return (0); +} + +/*------------------------------------------------------------------------* + * hid_get_data + *------------------------------------------------------------------------*/ +static uint32_t +hid_get_data_sub(const uint8_t *buf, usb_size_t len, struct hid_location *loc, + int is_signed) +{ + uint32_t hpos = loc->pos; + uint32_t hsize = loc->size; + uint32_t data; + uint32_t rpos; + uint8_t n; + + DPRINTFN(11, "hid_get_data: loc %d/%d\n", hpos, hsize); + + /* Range check and limit */ + if (hsize == 0) + return (0); + if (hsize > 32) + hsize = 32; + + /* Get data in a safe way */ + data = 0; + rpos = (hpos / 8); + n = (hsize + 7) / 8; + rpos += n; + while (n--) { + rpos--; + if (rpos < len) + data |= buf[rpos] << (8 * n); + } + + /* Correctly shift down data */ + data = (data >> (hpos % 8)); + n = 32 - hsize; + + /* Mask and sign extend in one */ + if (is_signed != 0) + data = (int32_t)((int32_t)data << n) >> n; + else + data = (uint32_t)((uint32_t)data << n) >> n; + + DPRINTFN(11, "hid_get_data: loc %d/%d = %lu\n", + loc->pos, loc->size, (long)data); + return (data); +} + +int32_t +hid_get_data(const uint8_t *buf, usb_size_t len, struct hid_location *loc) +{ + return (hid_get_data_sub(buf, len, loc, 1)); +} + +uint32_t +hid_get_data_unsigned(const uint8_t *buf, usb_size_t len, struct hid_location *loc) +{ + return (hid_get_data_sub(buf, len, loc, 0)); +} + +/*------------------------------------------------------------------------* + * hid_put_data + *------------------------------------------------------------------------*/ +void +hid_put_data_unsigned(uint8_t *buf, usb_size_t len, + struct hid_location *loc, unsigned int value) +{ + uint32_t hpos = loc->pos; + uint32_t hsize = loc->size; + uint64_t data; + uint64_t mask; + uint32_t rpos; + uint8_t n; + + DPRINTFN(11, "hid_put_data: loc %d/%d = %u\n", hpos, hsize, value); + + /* Range check and limit */ + if (hsize == 0) + return; + if (hsize > 32) + hsize = 32; + + /* Put data in a safe way */ + rpos = (hpos / 8); + n = (hsize + 7) / 8; + data = ((uint64_t)value) << (hpos % 8); + mask = ((1ULL << hsize) - 1ULL) << (hpos % 8); + rpos += n; + while (n--) { + rpos--; + if (rpos < len) { + buf[rpos] &= ~(mask >> (8 * n)); + buf[rpos] |= (data >> (8 * n)); + } + } +} + +/*------------------------------------------------------------------------* + * hid_is_collection + *------------------------------------------------------------------------*/ +int +hid_is_collection(const void *desc, usb_size_t size, uint32_t usage) +{ + struct hid_data *hd; + struct hid_item hi; + int err; + + hd = hid_start_parse(desc, size, hid_input); + if (hd == NULL) + return (0); + + while ((err = hid_get_item(hd, &hi))) { + if (hi.kind == hid_collection && + hi.usage == usage) + break; + } + hid_end_parse(hd); + return (err); +} + +/*------------------------------------------------------------------------* + * hid_get_descriptor_from_usb + * + * This function will search for a HID descriptor between two USB + * interface descriptors. + * + * Return values: + * NULL: No more HID descriptors. + * Else: Pointer to HID descriptor. + *------------------------------------------------------------------------*/ +struct usb_hid_descriptor * +hid_get_descriptor_from_usb(struct usb_config_descriptor *cd, + struct usb_interface_descriptor *id) +{ + struct usb_descriptor *desc = (void *)id; + + if (desc == NULL) { + return (NULL); + } + while ((desc = usb_desc_foreach(cd, desc))) { + if ((desc->bDescriptorType == UDESC_HID) && + (desc->bLength >= USB_HID_DESCRIPTOR_SIZE(0))) { + return (void *)desc; + } + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_hid_desc + * + * This function will read out an USB report descriptor from the USB + * device. + * + * Return values: + * NULL: Failure. + * Else: Success. The pointer should eventually be passed to free(). + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_hid_desc(struct usb_device *udev, struct mtx *mtx, + void **descp, uint16_t *sizep, + struct malloc_type *mem, uint8_t iface_index) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_hid_descriptor *hid; + usb_error_t err; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + hid = hid_get_descriptor_from_usb + (usbd_get_config_descriptor(udev), iface->idesc); + + if (hid == NULL) { + return (USB_ERR_IOERROR); + } + *sizep = UGETW(hid->descrs[0].wDescriptorLength); + if (*sizep == 0) { + return (USB_ERR_IOERROR); + } + if (mtx) + mtx_unlock(mtx); + + *descp = malloc(*sizep, mem, M_ZERO | M_WAITOK); + + if (mtx) + mtx_lock(mtx); + + if (*descp == NULL) { + return (USB_ERR_NOMEM); + } + err = usbd_req_get_report_descriptor + (udev, mtx, *descp, *sizep, iface_index); + + if (err) { + free(*descp, mem); + *descp = NULL; + return (err); + } + return (USB_ERR_NORMAL_COMPLETION); +} diff --git a/sys/bus/u4b/usb_hub.c b/sys/bus/u4b/usb_hub.c new file mode 100644 index 0000000000..fe40100ad1 --- /dev/null +++ b/sys/bus/u4b/usb_hub.c @@ -0,0 +1,2540 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * Copyright (c) 2008-2010 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * USB spec: http://www.usb.org/developers/docs/usbspec.zip + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define USB_DEBUG_VAR uhub_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define UHUB_INTR_INTERVAL 250 /* ms */ +#define UHUB_N_TRANSFER 1 + +#ifdef USB_DEBUG +static int uhub_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uhub, CTLFLAG_RW, 0, "USB HUB"); +SYSCTL_INT(_hw_usb_uhub, OID_AUTO, debug, CTLFLAG_RW, &uhub_debug, 0, + "Debug level"); + +TUNABLE_INT("hw.usb.uhub.debug", &uhub_debug); +#endif + +#if USB_HAVE_POWERD +static int usb_power_timeout = 30; /* seconds */ + +SYSCTL_INT(_hw_usb, OID_AUTO, power_timeout, CTLFLAG_RW, + &usb_power_timeout, 0, "USB power timeout"); +#endif + +struct uhub_current_state { + uint16_t port_change; + uint16_t port_status; +}; + +struct uhub_softc { + struct uhub_current_state sc_st;/* current state */ + device_t sc_dev; /* base device */ + struct mtx sc_mtx; /* our mutex */ + struct usb_device *sc_udev; /* USB device */ + struct usb_xfer *sc_xfer[UHUB_N_TRANSFER]; /* interrupt xfer */ + uint8_t sc_flags; +#define UHUB_FLAG_DID_EXPLORE 0x01 + char sc_name[32]; +}; + +#define UHUB_PROTO(sc) ((sc)->sc_udev->ddesc.bDeviceProtocol) +#define UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB) +#define UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT) +#define UHUB_IS_SUPER_SPEED(sc) (UHUB_PROTO(sc) == UDPROTO_SSHUB) + +/* prototypes for type checking: */ + +static device_probe_t uhub_probe; +static device_attach_t uhub_attach; +static device_detach_t uhub_detach; +static device_suspend_t uhub_suspend; +static device_resume_t uhub_resume; + +static bus_driver_added_t uhub_driver_added; +static bus_child_location_str_t uhub_child_location_string; +static bus_child_pnpinfo_str_t uhub_child_pnpinfo_string; + +static usb_callback_t uhub_intr_callback; + +static void usb_dev_resume_peer(struct usb_device *udev); +static void usb_dev_suspend_peer(struct usb_device *udev); +static uint8_t usb_peer_should_wakeup(struct usb_device *udev); + +static const struct usb_config uhub_config[UHUB_N_TRANSFER] = { + + [0] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_ANY, + .timeout = 0, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &uhub_intr_callback, + .interval = UHUB_INTR_INTERVAL, + }, +}; + +/* + * driver instance for "hub" connected to "usb" + * and "hub" connected to "hub" + */ +static devclass_t uhub_devclass; + +static device_method_t uhub_methods[] = { + DEVMETHOD(device_probe, uhub_probe), + DEVMETHOD(device_attach, uhub_attach), + DEVMETHOD(device_detach, uhub_detach), + + DEVMETHOD(device_suspend, uhub_suspend), + DEVMETHOD(device_resume, uhub_resume), + + DEVMETHOD(bus_child_location_str, uhub_child_location_string), + DEVMETHOD(bus_child_pnpinfo_str, uhub_child_pnpinfo_string), + DEVMETHOD(bus_driver_added, uhub_driver_added), + {0, 0} +}; + +static driver_t uhub_driver = { + .name = "uhub", + .methods = uhub_methods, + .size = sizeof(struct uhub_softc) +}; + +DRIVER_MODULE(uhub, usbus, uhub_driver, uhub_devclass, 0, 0); +DRIVER_MODULE(uhub, uhub, uhub_driver, uhub_devclass, NULL, 0); +MODULE_VERSION(uhub, 1); + +static void +uhub_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uhub_softc *sc = usbd_xfer_softc(xfer); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(2, "\n"); + /* + * This is an indication that some port + * has changed status. Notify the bus + * event handler thread that we need + * to be explored again: + */ + usb_needs_explore(sc->sc_udev->bus, 0); + + case USB_ST_SETUP: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* + * Do a clear-stall. The "stall_pipe" flag + * will get cleared before next callback by + * the USB stack. + */ + usbd_xfer_set_stall(xfer); + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + } + break; + } +} + +/*------------------------------------------------------------------------* + * uhub_explore_sub - subroutine + * + * Return values: + * 0: Success + * Else: A control transaction failed + *------------------------------------------------------------------------*/ +static usb_error_t +uhub_explore_sub(struct uhub_softc *sc, struct usb_port *up) +{ + struct usb_bus *bus; + struct usb_device *child; + uint8_t refcount; + usb_error_t err; + + bus = sc->sc_udev->bus; + err = 0; + + /* get driver added refcount from USB bus */ + refcount = bus->driver_added_refcount; + + /* get device assosiated with the given port */ + child = usb_bus_port_get_device(bus, up); + if (child == NULL) { + /* nothing to do */ + goto done; + } + + /* check if device should be re-enumerated */ + + if (child->flags.usb_mode == USB_MODE_HOST) { + usbd_enum_lock(child); + if (child->re_enumerate_wait) { + err = usbd_set_config_index(child, + USB_UNCONFIG_INDEX); + if (err != 0) { + DPRINTF("Unconfigure failed: " + "%s: Ignored.\n", + usbd_errstr(err)); + } + err = usbd_req_re_enumerate(child, NULL); + if (err == 0) + err = usbd_set_config_index(child, 0); + if (err == 0) { + err = usb_probe_and_attach(child, + USB_IFACE_INDEX_ANY); + } + child->re_enumerate_wait = 0; + err = 0; + } + usbd_enum_unlock(child); + } + + /* check if probe and attach should be done */ + + if (child->driver_added_refcount != refcount) { + child->driver_added_refcount = refcount; + err = usb_probe_and_attach(child, + USB_IFACE_INDEX_ANY); + if (err) { + goto done; + } + } + /* start control transfer, if device mode */ + + if (child->flags.usb_mode == USB_MODE_DEVICE) + usbd_ctrl_transfer_setup(child); + + /* if a HUB becomes present, do a recursive HUB explore */ + + if (child->hub) + err = (child->hub->explore) (child); + +done: + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_read_port_status - factored out code + *------------------------------------------------------------------------*/ +static usb_error_t +uhub_read_port_status(struct uhub_softc *sc, uint8_t portno) +{ + struct usb_port_status ps; + usb_error_t err; + + err = usbd_req_get_port_status( + sc->sc_udev, NULL, &ps, portno); + + /* update status regardless of error */ + + sc->sc_st.port_status = UGETW(ps.wPortStatus); + sc->sc_st.port_change = UGETW(ps.wPortChange); + + /* debugging print */ + + DPRINTFN(4, "port %d, wPortStatus=0x%04x, " + "wPortChange=0x%04x, err=%s\n", + portno, sc->sc_st.port_status, + sc->sc_st.port_change, usbd_errstr(err)); + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_reattach_port + * + * Returns: + * 0: Success + * Else: A control transaction failed + *------------------------------------------------------------------------*/ +static usb_error_t +uhub_reattach_port(struct uhub_softc *sc, uint8_t portno) +{ + struct usb_device *child; + struct usb_device *udev; + enum usb_dev_speed speed; + enum usb_hc_mode mode; + usb_error_t err; + uint16_t power_mask; + uint8_t timeout; + + DPRINTF("reattaching port %d\n", portno); + + err = 0; + timeout = 0; + udev = sc->sc_udev; + child = usb_bus_port_get_device(udev->bus, + udev->hub->ports + portno - 1); + +repeat: + + /* first clear the port connection change bit */ + + err = usbd_req_clear_port_feature(udev, NULL, + portno, UHF_C_PORT_CONNECTION); + + if (err) { + goto error; + } + /* check if there is a child */ + + if (child != NULL) { + /* + * Free USB device and all subdevices, if any. + */ + usb_free_device(child, 0); + child = NULL; + } + /* get fresh status */ + + err = uhub_read_port_status(sc, portno); + if (err) { + goto error; + } + /* check if nothing is connected to the port */ + + if (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS)) { + goto error; + } + /* check if there is no power on the port and print a warning */ + + switch (udev->speed) { + case USB_SPEED_HIGH: + case USB_SPEED_FULL: + case USB_SPEED_LOW: + power_mask = UPS_PORT_POWER; + break; + case USB_SPEED_SUPER: + if (udev->parent_hub == NULL) + power_mask = UPS_PORT_POWER; + else + power_mask = UPS_PORT_POWER_SS; + break; + default: + power_mask = 0; + break; + } + if (!(sc->sc_st.port_status & power_mask)) { + DPRINTF("WARNING: strange, connected port %d " + "has no power\n", portno); + } + + /* check if the device is in Host Mode */ + + if (!(sc->sc_st.port_status & UPS_PORT_MODE_DEVICE)) { + + DPRINTF("Port %d is in Host Mode\n", portno); + + if (sc->sc_st.port_status & UPS_SUSPEND) { + /* + * NOTE: Should not get here in SuperSpeed + * mode, because the HUB should report this + * bit as zero. + */ + DPRINTF("Port %d was still " + "suspended, clearing.\n", portno); + err = usbd_req_clear_port_feature(udev, + NULL, portno, UHF_PORT_SUSPEND); + } + + /* USB Host Mode */ + + /* wait for maximum device power up time */ + + usb_pause_mtx(NULL, + USB_MS_TO_TICKS(USB_PORT_POWERUP_DELAY)); + + /* reset port, which implies enabling it */ + + err = usbd_req_reset_port(udev, NULL, portno); + + if (err) { + DPRINTFN(0, "port %d reset " + "failed, error=%s\n", + portno, usbd_errstr(err)); + goto error; + } + /* get port status again, it might have changed during reset */ + + err = uhub_read_port_status(sc, portno); + if (err) { + goto error; + } + /* check if something changed during port reset */ + + if ((sc->sc_st.port_change & UPS_C_CONNECT_STATUS) || + (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS))) { + if (timeout) { + DPRINTFN(0, "giving up port reset " + "- device vanished\n"); + goto error; + } + timeout = 1; + goto repeat; + } + } else { + DPRINTF("Port %d is in Device Mode\n", portno); + } + + /* + * Figure out the device speed + */ + switch (udev->speed) { + case USB_SPEED_HIGH: + if (sc->sc_st.port_status & UPS_HIGH_SPEED) + speed = USB_SPEED_HIGH; + else if (sc->sc_st.port_status & UPS_LOW_SPEED) + speed = USB_SPEED_LOW; + else + speed = USB_SPEED_FULL; + break; + case USB_SPEED_FULL: + if (sc->sc_st.port_status & UPS_LOW_SPEED) + speed = USB_SPEED_LOW; + else + speed = USB_SPEED_FULL; + break; + case USB_SPEED_LOW: + speed = USB_SPEED_LOW; + break; + case USB_SPEED_SUPER: + if (udev->parent_hub == NULL) { + /* Root HUB - special case */ + switch (sc->sc_st.port_status & UPS_OTHER_SPEED) { + case 0: + speed = USB_SPEED_FULL; + break; + case UPS_LOW_SPEED: + speed = USB_SPEED_LOW; + break; + case UPS_HIGH_SPEED: + speed = USB_SPEED_HIGH; + break; + default: + speed = USB_SPEED_SUPER; + break; + } + } else { + speed = USB_SPEED_SUPER; + } + break; + default: + /* same speed like parent */ + speed = udev->speed; + break; + } + if (speed == USB_SPEED_SUPER) { + err = usbd_req_set_hub_u1_timeout(udev, NULL, + portno, 128 - (2 * udev->depth)); + if (err) { + DPRINTFN(0, "port %d U1 timeout " + "failed, error=%s\n", + portno, usbd_errstr(err)); + } + err = usbd_req_set_hub_u2_timeout(udev, NULL, + portno, 128 - (2 * udev->depth)); + if (err) { + DPRINTFN(0, "port %d U2 timeout " + "failed, error=%s\n", + portno, usbd_errstr(err)); + } + } + + /* + * Figure out the device mode + * + * NOTE: This part is currently FreeBSD specific. + */ + if (sc->sc_st.port_status & UPS_PORT_MODE_DEVICE) + mode = USB_MODE_DEVICE; + else + mode = USB_MODE_HOST; + + /* need to create a new child */ + child = usb_alloc_device(sc->sc_dev, udev->bus, udev, + udev->depth + 1, portno - 1, portno, speed, mode); + if (child == NULL) { + DPRINTFN(0, "could not allocate new device\n"); + goto error; + } + return (0); /* success */ + +error: + if (child != NULL) { + /* + * Free USB device and all subdevices, if any. + */ + usb_free_device(child, 0); + child = NULL; + } + if (err == 0) { + if (sc->sc_st.port_status & UPS_PORT_ENABLED) { + err = usbd_req_clear_port_feature( + sc->sc_udev, NULL, + portno, UHF_PORT_ENABLE); + } + } + if (err) { + DPRINTFN(0, "device problem (%s), " + "disabling port %d\n", usbd_errstr(err), portno); + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb_device_20_compatible + * + * Returns: + * 0: HUB does not support suspend and resume + * Else: HUB supports suspend and resume + *------------------------------------------------------------------------*/ +static uint8_t +usb_device_20_compatible(struct usb_device *udev) +{ + if (udev == NULL) + return (0); + switch (udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + return (1); + default: + return (0); + } +} + +/*------------------------------------------------------------------------* + * uhub_suspend_resume_port + * + * Returns: + * 0: Success + * Else: A control transaction failed + *------------------------------------------------------------------------*/ +static usb_error_t +uhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno) +{ + struct usb_device *child; + struct usb_device *udev; + uint8_t is_suspend; + usb_error_t err; + + DPRINTF("port %d\n", portno); + + udev = sc->sc_udev; + child = usb_bus_port_get_device(udev->bus, + udev->hub->ports + portno - 1); + + /* first clear the port suspend change bit */ + + if (usb_device_20_compatible(udev)) { + err = usbd_req_clear_port_feature(udev, NULL, + portno, UHF_C_PORT_SUSPEND); + } else { + err = usbd_req_clear_port_feature(udev, NULL, + portno, UHF_C_PORT_LINK_STATE); + } + + if (err) { + DPRINTF("clearing suspend failed.\n"); + goto done; + } + /* get fresh status */ + + err = uhub_read_port_status(sc, portno); + if (err) { + DPRINTF("reading port status failed.\n"); + goto done; + } + /* convert current state */ + + if (usb_device_20_compatible(udev)) { + if (sc->sc_st.port_status & UPS_SUSPEND) { + is_suspend = 1; + } else { + is_suspend = 0; + } + } else { + switch (UPS_PORT_LINK_STATE_GET(sc->sc_st.port_status)) { + case UPS_PORT_LS_U3: + is_suspend = 1; + break; + case UPS_PORT_LS_SS_INA: + usbd_req_warm_reset_port(udev, NULL, portno); + is_suspend = 0; + break; + default: + is_suspend = 0; + break; + } + } + + DPRINTF("suspended=%u\n", is_suspend); + + /* do the suspend or resume */ + + if (child) { + /* + * This code handle two cases: 1) Host Mode - we can only + * receive resume here 2) Device Mode - we can receive + * suspend and resume here + */ + if (is_suspend == 0) + usb_dev_resume_peer(child); + else if (child->flags.usb_mode == USB_MODE_DEVICE) + usb_dev_suspend_peer(child); + } +done: + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_root_interrupt + * + * This function is called when a Root HUB interrupt has + * happened. "ptr" and "len" makes up the Root HUB interrupt + * packet. This function is called having the "bus_mtx" locked. + *------------------------------------------------------------------------*/ +void +uhub_root_intr(struct usb_bus *bus, const uint8_t *ptr, uint8_t len) +{ + USB_BUS_LOCK_ASSERT(bus, MA_OWNED); + + usb_needs_explore(bus, 0); +} + +static uint8_t +uhub_is_too_deep(struct usb_device *udev) +{ + switch (udev->speed) { + case USB_SPEED_FULL: + case USB_SPEED_LOW: + case USB_SPEED_HIGH: + if (udev->depth > USB_HUB_MAX_DEPTH) + return (1); + break; + case USB_SPEED_SUPER: + if (udev->depth > USB_SS_HUB_DEPTH_MAX) + return (1); + break; + default: + break; + } + return (0); +} + +/*------------------------------------------------------------------------* + * uhub_explore + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb_error_t +uhub_explore(struct usb_device *udev) +{ + struct usb_hub *hub; + struct uhub_softc *sc; + struct usb_port *up; + usb_error_t err; + uint8_t portno; + uint8_t x; + + hub = udev->hub; + sc = hub->hubsoftc; + + DPRINTFN(11, "udev=%p addr=%d\n", udev, udev->address); + + /* ignore devices that are too deep */ + if (uhub_is_too_deep(udev)) + return (USB_ERR_TOO_DEEP); + + /* check if device is suspended */ + if (udev->flags.self_suspended) { + /* need to wait until the child signals resume */ + DPRINTF("Device is suspended!\n"); + return (0); + } + + /* + * Make sure we don't race against user-space applications + * like LibUSB: + */ + usbd_enum_lock(udev); + + for (x = 0; x != hub->nports; x++) { + up = hub->ports + x; + portno = x + 1; + + err = uhub_read_port_status(sc, portno); + if (err) { + /* most likely the HUB is gone */ + break; + } + if (sc->sc_st.port_change & UPS_C_OVERCURRENT_INDICATOR) { + DPRINTF("Overcurrent on port %u.\n", portno); + err = usbd_req_clear_port_feature( + udev, NULL, portno, UHF_C_PORT_OVER_CURRENT); + if (err) { + /* most likely the HUB is gone */ + break; + } + } + if (!(sc->sc_flags & UHUB_FLAG_DID_EXPLORE)) { + /* + * Fake a connect status change so that the + * status gets checked initially! + */ + sc->sc_st.port_change |= + UPS_C_CONNECT_STATUS; + } + if (sc->sc_st.port_change & UPS_C_PORT_ENABLED) { + err = usbd_req_clear_port_feature( + udev, NULL, portno, UHF_C_PORT_ENABLE); + if (err) { + /* most likely the HUB is gone */ + break; + } + if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) { + /* + * Ignore the port error if the device + * has vanished ! + */ + } else if (sc->sc_st.port_status & UPS_PORT_ENABLED) { + DPRINTFN(0, "illegal enable change, " + "port %d\n", portno); + } else { + + if (up->restartcnt == USB_RESTART_MAX) { + /* XXX could try another speed ? */ + DPRINTFN(0, "port error, giving up " + "port %d\n", portno); + } else { + sc->sc_st.port_change |= + UPS_C_CONNECT_STATUS; + up->restartcnt++; + } + } + } + if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) { + err = uhub_reattach_port(sc, portno); + if (err) { + /* most likely the HUB is gone */ + break; + } + } + if (sc->sc_st.port_change & (UPS_C_SUSPEND | + UPS_C_PORT_LINK_STATE)) { + err = uhub_suspend_resume_port(sc, portno); + if (err) { + /* most likely the HUB is gone */ + break; + } + } + err = uhub_explore_sub(sc, up); + if (err) { + /* no device(s) present */ + continue; + } + /* explore succeeded - reset restart counter */ + up->restartcnt = 0; + } + + usbd_enum_unlock(udev); + + /* initial status checked */ + sc->sc_flags |= UHUB_FLAG_DID_EXPLORE; + + /* return success */ + return (USB_ERR_NORMAL_COMPLETION); +} + +static int +uhub_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + + /* + * The subclass for USB HUBs is currently ignored because it + * is 0 for some and 1 for others. + */ + if (uaa->info.bConfigIndex == 0 && + uaa->info.bDeviceClass == UDCLASS_HUB) + return (0); + + return (ENXIO); +} + +/* NOTE: The information returned by this function can be wrong. */ +usb_error_t +uhub_query_info(struct usb_device *udev, uint8_t *pnports, uint8_t *ptt) +{ + struct usb_hub_descriptor hubdesc20; + struct usb_hub_ss_descriptor hubdesc30; + usb_error_t err; + uint8_t nports; + uint8_t tt; + + if (udev->ddesc.bDeviceClass != UDCLASS_HUB) + return (USB_ERR_INVAL); + + nports = 0; + tt = 0; + + switch (udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + /* assuming that there is one port */ + err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1); + if (err) { + DPRINTFN(0, "getting USB 2.0 HUB descriptor failed," + "error=%s\n", usbd_errstr(err)); + break; + } + nports = hubdesc20.bNbrPorts; + if (nports > 127) + nports = 127; + + if (udev->speed == USB_SPEED_HIGH) + tt = (UGETW(hubdesc20.wHubCharacteristics) >> 5) & 3; + break; + + case USB_SPEED_SUPER: + err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1); + if (err) { + DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed," + "error=%s\n", usbd_errstr(err)); + break; + } + nports = hubdesc30.bNbrPorts; + if (nports > 16) + nports = 16; + break; + + default: + err = USB_ERR_INVAL; + break; + } + + if (pnports != NULL) + *pnports = nports; + + if (ptt != NULL) + *ptt = tt; + + return (err); +} + +static int +uhub_attach(device_t dev) +{ + struct uhub_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct usb_device *udev = uaa->device; + struct usb_device *parent_hub = udev->parent_hub; + struct usb_hub *hub; + struct usb_hub_descriptor hubdesc20; + struct usb_hub_ss_descriptor hubdesc30; + uint16_t pwrdly; + uint8_t x; + uint8_t nports; + uint8_t portno; + uint8_t removable; + uint8_t iface_index; + usb_error_t err; + + sc->sc_udev = udev; + sc->sc_dev = dev; + + mtx_init(&sc->sc_mtx, "USB HUB mutex", NULL, MTX_DEF); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + device_set_usb_desc(dev); + + DPRINTFN(2, "depth=%d selfpowered=%d, parent=%p, " + "parent->selfpowered=%d\n", + udev->depth, + udev->flags.self_powered, + parent_hub, + parent_hub ? + parent_hub->flags.self_powered : 0); + + if (uhub_is_too_deep(udev)) { + DPRINTFN(0, "HUB at depth %d, " + "exceeds maximum. HUB ignored\n", (int)udev->depth); + goto error; + } + + if (!udev->flags.self_powered && parent_hub && + !parent_hub->flags.self_powered) { + DPRINTFN(0, "Bus powered HUB connected to " + "bus powered HUB. HUB ignored\n"); + goto error; + } + /* get HUB descriptor */ + + DPRINTFN(2, "Getting HUB descriptor\n"); + + switch (udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + /* assuming that there is one port */ + err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1); + if (err) { + DPRINTFN(0, "getting USB 2.0 HUB descriptor failed," + "error=%s\n", usbd_errstr(err)); + goto error; + } + /* get number of ports */ + nports = hubdesc20.bNbrPorts; + + /* get power delay */ + pwrdly = ((hubdesc20.bPwrOn2PwrGood * UHD_PWRON_FACTOR) + + USB_EXTRA_POWER_UP_TIME); + + /* get complete HUB descriptor */ + if (nports >= 8) { + /* check number of ports */ + if (nports > 127) { + DPRINTFN(0, "Invalid number of USB 2.0 ports," + "error=%s\n", usbd_errstr(err)); + goto error; + } + /* get complete HUB descriptor */ + err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, nports); + + if (err) { + DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed," + "error=%s\n", usbd_errstr(err)); + goto error; + } + if (hubdesc20.bNbrPorts != nports) { + DPRINTFN(0, "Number of ports changed\n"); + goto error; + } + } + break; + case USB_SPEED_SUPER: + if (udev->parent_hub != NULL) { + err = usbd_req_set_hub_depth(udev, NULL, + udev->depth - 1); + if (err) { + DPRINTFN(0, "Setting USB 3.0 HUB depth failed," + "error=%s\n", usbd_errstr(err)); + goto error; + } + } + err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1); + if (err) { + DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed," + "error=%s\n", usbd_errstr(err)); + goto error; + } + /* get number of ports */ + nports = hubdesc30.bNbrPorts; + + /* get power delay */ + pwrdly = ((hubdesc30.bPwrOn2PwrGood * UHD_PWRON_FACTOR) + + USB_EXTRA_POWER_UP_TIME); + + /* get complete HUB descriptor */ + if (nports >= 8) { + /* check number of ports */ + if (nports > ((udev->parent_hub != NULL) ? 15 : 127)) { + DPRINTFN(0, "Invalid number of USB 3.0 ports," + "error=%s\n", usbd_errstr(err)); + goto error; + } + /* get complete HUB descriptor */ + err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, nports); + + if (err) { + DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed," + "error=%s\n", usbd_errstr(err)); + goto error; + } + if (hubdesc30.bNbrPorts != nports) { + DPRINTFN(0, "Number of ports changed\n"); + goto error; + } + } + break; + default: + DPRINTF("Assuming HUB has only one port\n"); + /* default number of ports */ + nports = 1; + /* default power delay */ + pwrdly = ((10 * UHD_PWRON_FACTOR) + USB_EXTRA_POWER_UP_TIME); + break; + } + if (nports == 0) { + DPRINTFN(0, "portless HUB\n"); + goto error; + } + hub = malloc(sizeof(hub[0]) + (sizeof(hub->ports[0]) * nports), + M_USBDEV, M_WAITOK | M_ZERO); + + if (hub == NULL) { + goto error; + } + udev->hub = hub; + +#if USB_HAVE_TT_SUPPORT + /* init FULL-speed ISOCHRONOUS schedule */ + usbd_fs_isoc_schedule_init_all(hub->fs_isoc_schedule); +#endif + /* initialize HUB structure */ + hub->hubsoftc = sc; + hub->explore = &uhub_explore; + hub->nports = nports; + hub->hubudev = udev; + + /* if self powered hub, give ports maximum current */ + if (udev->flags.self_powered) { + hub->portpower = USB_MAX_POWER; + } else { + hub->portpower = USB_MIN_POWER; + } + + /* set up interrupt pipe */ + iface_index = 0; + if (udev->parent_hub == NULL) { + /* root HUB is special */ + err = 0; + } else { + /* normal HUB */ + err = usbd_transfer_setup(udev, &iface_index, sc->sc_xfer, + uhub_config, UHUB_N_TRANSFER, sc, &sc->sc_mtx); + } + if (err) { + DPRINTFN(0, "cannot setup interrupt transfer, " + "errstr=%s\n", usbd_errstr(err)); + goto error; + } + /* wait with power off for a while */ + usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_POWER_DOWN_TIME)); + + /* + * To have the best chance of success we do things in the exact same + * order as Windoze98. This should not be necessary, but some + * devices do not follow the USB specs to the letter. + * + * These are the events on the bus when a hub is attached: + * Get device and config descriptors (see attach code) + * Get hub descriptor (see above) + * For all ports + * turn on power + * wait for power to become stable + * (all below happens in explore code) + * For all ports + * clear C_PORT_CONNECTION + * For all ports + * get port status + * if device connected + * wait 100 ms + * turn on reset + * wait + * clear C_PORT_RESET + * get port status + * proceed with device attachment + */ + + /* XXX should check for none, individual, or ganged power? */ + + removable = 0; + + for (x = 0; x != nports; x++) { + /* set up data structures */ + struct usb_port *up = hub->ports + x; + + up->device_index = 0; + up->restartcnt = 0; + portno = x + 1; + + /* check if port is removable */ + switch (udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + if (!UHD_NOT_REMOV(&hubdesc20, portno)) + removable++; + break; + case USB_SPEED_SUPER: + if (!UHD_NOT_REMOV(&hubdesc30, portno)) + removable++; + break; + default: + DPRINTF("Assuming removable port\n"); + removable++; + break; + } + if (!err) { + /* turn the power on */ + err = usbd_req_set_port_feature(udev, NULL, + portno, UHF_PORT_POWER); + } + if (err) { + DPRINTFN(0, "port %d power on failed, %s\n", + portno, usbd_errstr(err)); + } + DPRINTF("turn on port %d power\n", + portno); + + /* wait for stable power */ + usb_pause_mtx(NULL, USB_MS_TO_TICKS(pwrdly)); + } + + device_printf(dev, "%d port%s with %d " + "removable, %s powered\n", nports, (nports != 1) ? "s" : "", + removable, udev->flags.self_powered ? "self" : "bus"); + + /* Start the interrupt endpoint, if any */ + + if (sc->sc_xfer[0] != NULL) { + mtx_lock(&sc->sc_mtx); + usbd_transfer_start(sc->sc_xfer[0]); + mtx_unlock(&sc->sc_mtx); + } + + /* Enable automatic power save on all USB HUBs */ + + usbd_set_power_mode(udev, USB_POWER_MODE_SAVE); + + return (0); + +error: + usbd_transfer_unsetup(sc->sc_xfer, UHUB_N_TRANSFER); + + if (udev->hub) { + free(udev->hub, M_USBDEV); + udev->hub = NULL; + } + + mtx_destroy(&sc->sc_mtx); + + return (ENXIO); +} + +/* + * Called from process context when the hub is gone. + * Detach all devices on active ports. + */ +static int +uhub_detach(device_t dev) +{ + struct uhub_softc *sc = device_get_softc(dev); + struct usb_hub *hub = sc->sc_udev->hub; + struct usb_device *child; + uint8_t x; + + if (hub == NULL) /* must be partially working */ + return (0); + + /* Make sure interrupt transfer is gone. */ + usbd_transfer_unsetup(sc->sc_xfer, UHUB_N_TRANSFER); + + /* Detach all ports */ + for (x = 0; x != hub->nports; x++) { + + child = usb_bus_port_get_device(sc->sc_udev->bus, hub->ports + x); + + if (child == NULL) { + continue; + } + + /* + * Free USB device and all subdevices, if any. + */ + usb_free_device(child, 0); + } + + free(hub, M_USBDEV); + sc->sc_udev->hub = NULL; + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static int +uhub_suspend(device_t dev) +{ + DPRINTF("\n"); + /* Sub-devices are not suspended here! */ + return (0); +} + +static int +uhub_resume(device_t dev) +{ + DPRINTF("\n"); + /* Sub-devices are not resumed here! */ + return (0); +} + +static void +uhub_driver_added(device_t dev, driver_t *driver) +{ + usb_needs_explore_all(); +} + +struct hub_result { + struct usb_device *udev; + uint8_t portno; + uint8_t iface_index; +}; + +static void +uhub_find_iface_index(struct usb_hub *hub, device_t child, + struct hub_result *res) +{ + struct usb_interface *iface; + struct usb_device *udev; + uint8_t nports; + uint8_t x; + uint8_t i; + + nports = hub->nports; + for (x = 0; x != nports; x++) { + udev = usb_bus_port_get_device(hub->hubudev->bus, + hub->ports + x); + if (!udev) { + continue; + } + for (i = 0; i != USB_IFACE_MAX; i++) { + iface = usbd_get_iface(udev, i); + if (iface && + (iface->subdev == child)) { + res->iface_index = i; + res->udev = udev; + res->portno = x + 1; + return; + } + } + } + res->iface_index = 0; + res->udev = NULL; + res->portno = 0; +} + +static int +uhub_child_location_string(device_t parent, device_t child, + char *buf, size_t buflen) +{ + struct uhub_softc *sc; + struct usb_hub *hub; + struct hub_result res; + + if (!device_is_attached(parent)) { + if (buflen) + buf[0] = 0; + return (0); + } + + sc = device_get_softc(parent); + hub = sc->sc_udev->hub; + + mtx_lock(&Giant); + uhub_find_iface_index(hub, child, &res); + if (!res.udev) { + DPRINTF("device not on hub\n"); + if (buflen) { + buf[0] = '\0'; + } + goto done; + } + snprintf(buf, buflen, "bus=%u hubaddr=%u port=%u devaddr=%u interface=%u", + (res.udev->parent_hub != NULL) ? res.udev->parent_hub->device_index : 0, + res.portno, device_get_unit(res.udev->bus->bdev), + res.udev->device_index, res.iface_index); +done: + mtx_unlock(&Giant); + + return (0); +} + +static int +uhub_child_pnpinfo_string(device_t parent, device_t child, + char *buf, size_t buflen) +{ + struct uhub_softc *sc; + struct usb_hub *hub; + struct usb_interface *iface; + struct hub_result res; + + if (!device_is_attached(parent)) { + if (buflen) + buf[0] = 0; + return (0); + } + + sc = device_get_softc(parent); + hub = sc->sc_udev->hub; + + mtx_lock(&Giant); + uhub_find_iface_index(hub, child, &res); + if (!res.udev) { + DPRINTF("device not on hub\n"); + if (buflen) { + buf[0] = '\0'; + } + goto done; + } + iface = usbd_get_iface(res.udev, res.iface_index); + if (iface && iface->idesc) { + snprintf(buf, buflen, "vendor=0x%04x product=0x%04x " + "devclass=0x%02x devsubclass=0x%02x " + "sernum=\"%s\" " + "release=0x%04x " + "mode=%s " + "intclass=0x%02x intsubclass=0x%02x " + "intprotocol=0x%02x " "%s%s", + UGETW(res.udev->ddesc.idVendor), + UGETW(res.udev->ddesc.idProduct), + res.udev->ddesc.bDeviceClass, + res.udev->ddesc.bDeviceSubClass, + usb_get_serial(res.udev), + UGETW(res.udev->ddesc.bcdDevice), + (res.udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device", + iface->idesc->bInterfaceClass, + iface->idesc->bInterfaceSubClass, + iface->idesc->bInterfaceProtocol, + iface->pnpinfo ? " " : "", + iface->pnpinfo ? iface->pnpinfo : ""); + } else { + if (buflen) { + buf[0] = '\0'; + } + goto done; + } +done: + mtx_unlock(&Giant); + + return (0); +} + +/* + * The USB Transaction Translator: + * =============================== + * + * When doing LOW- and FULL-speed USB transfers accross a HIGH-speed + * USB HUB, bandwidth must be allocated for ISOCHRONOUS and INTERRUPT + * USB transfers. To utilize bandwidth dynamically the "scatter and + * gather" principle must be applied. This means that bandwidth must + * be divided into equal parts of bandwidth. With regard to USB all + * data is transferred in smaller packets with length + * "wMaxPacketSize". The problem however is that "wMaxPacketSize" is + * not a constant! + * + * The bandwidth scheduler which I have implemented will simply pack + * the USB transfers back to back until there is no more space in the + * schedule. Out of the 8 microframes which the USB 2.0 standard + * provides, only 6 are available for non-HIGH-speed devices. I have + * reserved the first 4 microframes for ISOCHRONOUS transfers. The + * last 2 microframes I have reserved for INTERRUPT transfers. Without + * this division, it is very difficult to allocate and free bandwidth + * dynamically. + * + * NOTE about the Transaction Translator in USB HUBs: + * + * USB HUBs have a very simple Transaction Translator, that will + * simply pipeline all the SPLIT transactions. That means that the + * transactions will be executed in the order they are queued! + * + */ + +/*------------------------------------------------------------------------* + * usb_intr_find_best_slot + * + * Return value: + * The best Transaction Translation slot for an interrupt endpoint. + *------------------------------------------------------------------------*/ +static uint8_t +usb_intr_find_best_slot(usb_size_t *ptr, uint8_t start, + uint8_t end, uint8_t mask) +{ + usb_size_t min = 0 - 1; + usb_size_t sum; + uint8_t x; + uint8_t y; + uint8_t z; + + y = 0; + + /* find the last slot with lesser used bandwidth */ + + for (x = start; x < end; x++) { + + sum = 0; + + /* compute sum of bandwidth */ + for (z = x; z < end; z++) { + if (mask & (1U << (z - x))) + sum += ptr[z]; + } + + /* check if the current multi-slot is more optimal */ + if (min >= sum) { + min = sum; + y = x; + } + + /* check if the mask is about to be shifted out */ + if (mask & (1U << (end - 1 - x))) + break; + } + return (y); +} + +/*------------------------------------------------------------------------* + * usb_hs_bandwidth_adjust + * + * This function will update the bandwith usage for the microframe + * having index "slot" by "len" bytes. "len" can be negative. If the + * "slot" argument is greater or equal to "USB_HS_MICRO_FRAMES_MAX" + * the "slot" argument will be replaced by the slot having least used + * bandwidth. The "mask" argument is used for multi-slot allocations. + * + * Returns: + * The slot in which the bandwidth update was done: 0..7 + *------------------------------------------------------------------------*/ +static uint8_t +usb_hs_bandwidth_adjust(struct usb_device *udev, int16_t len, + uint8_t slot, uint8_t mask) +{ + struct usb_bus *bus = udev->bus; + struct usb_hub *hub; + enum usb_dev_speed speed; + uint8_t x; + + USB_BUS_LOCK_ASSERT(bus, MA_OWNED); + + speed = usbd_get_speed(udev); + + switch (speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + if (speed == USB_SPEED_LOW) { + len *= 8; + } + /* + * The Host Controller Driver should have + * performed checks so that the lookup + * below does not result in a NULL pointer + * access. + */ + + hub = udev->parent_hs_hub->hub; + if (slot >= USB_HS_MICRO_FRAMES_MAX) { + slot = usb_intr_find_best_slot(hub->uframe_usage, + USB_FS_ISOC_UFRAME_MAX, 6, mask); + } + for (x = slot; x < 8; x++) { + if (mask & (1U << (x - slot))) { + hub->uframe_usage[x] += len; + bus->uframe_usage[x] += len; + } + } + break; + default: + if (slot >= USB_HS_MICRO_FRAMES_MAX) { + slot = usb_intr_find_best_slot(bus->uframe_usage, 0, + USB_HS_MICRO_FRAMES_MAX, mask); + } + for (x = slot; x < 8; x++) { + if (mask & (1U << (x - slot))) { + bus->uframe_usage[x] += len; + } + } + break; + } + return (slot); +} + +/*------------------------------------------------------------------------* + * usb_hs_bandwidth_alloc + * + * This function is a wrapper function for "usb_hs_bandwidth_adjust()". + *------------------------------------------------------------------------*/ +void +usb_hs_bandwidth_alloc(struct usb_xfer *xfer) +{ + struct usb_device *udev; + uint8_t slot; + uint8_t mask; + uint8_t speed; + + udev = xfer->xroot->udev; + + if (udev->flags.usb_mode != USB_MODE_HOST) + return; /* not supported */ + + xfer->endpoint->refcount_bw++; + if (xfer->endpoint->refcount_bw != 1) + return; /* already allocated */ + + speed = usbd_get_speed(udev); + + switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + /* allocate a microframe slot */ + + mask = 0x01; + slot = usb_hs_bandwidth_adjust(udev, + xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX, mask); + + xfer->endpoint->usb_uframe = slot; + xfer->endpoint->usb_smask = mask << slot; + + if ((speed != USB_SPEED_FULL) && + (speed != USB_SPEED_LOW)) { + xfer->endpoint->usb_cmask = 0x00 ; + } else { + xfer->endpoint->usb_cmask = (-(0x04 << slot)) & 0xFE; + } + break; + + case UE_ISOCHRONOUS: + switch (usbd_xfer_get_fps_shift(xfer)) { + case 0: + mask = 0xFF; + break; + case 1: + mask = 0x55; + break; + case 2: + mask = 0x11; + break; + default: + mask = 0x01; + break; + } + + /* allocate a microframe multi-slot */ + + slot = usb_hs_bandwidth_adjust(udev, + xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX, mask); + + xfer->endpoint->usb_uframe = slot; + xfer->endpoint->usb_cmask = 0; + xfer->endpoint->usb_smask = mask << slot; + break; + + default: + xfer->endpoint->usb_uframe = 0; + xfer->endpoint->usb_cmask = 0; + xfer->endpoint->usb_smask = 0; + break; + } + + DPRINTFN(11, "slot=%d, mask=0x%02x\n", + xfer->endpoint->usb_uframe, + xfer->endpoint->usb_smask >> xfer->endpoint->usb_uframe); +} + +/*------------------------------------------------------------------------* + * usb_hs_bandwidth_free + * + * This function is a wrapper function for "usb_hs_bandwidth_adjust()". + *------------------------------------------------------------------------*/ +void +usb_hs_bandwidth_free(struct usb_xfer *xfer) +{ + struct usb_device *udev; + uint8_t slot; + uint8_t mask; + + udev = xfer->xroot->udev; + + if (udev->flags.usb_mode != USB_MODE_HOST) + return; /* not supported */ + + xfer->endpoint->refcount_bw--; + if (xfer->endpoint->refcount_bw != 0) + return; /* still allocated */ + + switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + case UE_ISOCHRONOUS: + + slot = xfer->endpoint->usb_uframe; + mask = xfer->endpoint->usb_smask; + + /* free microframe slot(s): */ + usb_hs_bandwidth_adjust(udev, + -xfer->max_frame_size, slot, mask >> slot); + + DPRINTFN(11, "slot=%d, mask=0x%02x\n", + slot, mask >> slot); + + xfer->endpoint->usb_uframe = 0; + xfer->endpoint->usb_cmask = 0; + xfer->endpoint->usb_smask = 0; + break; + + default: + break; + } +} + +/*------------------------------------------------------------------------* + * usbd_fs_isoc_schedule_init_sub + * + * This function initialises an USB FULL speed isochronous schedule + * entry. + *------------------------------------------------------------------------*/ +#if USB_HAVE_TT_SUPPORT +static void +usbd_fs_isoc_schedule_init_sub(struct usb_fs_isoc_schedule *fss) +{ + fss->total_bytes = (USB_FS_ISOC_UFRAME_MAX * + USB_FS_BYTES_PER_HS_UFRAME); + fss->frame_bytes = (USB_FS_BYTES_PER_HS_UFRAME); + fss->frame_slot = 0; +} +#endif + +/*------------------------------------------------------------------------* + * usbd_fs_isoc_schedule_init_all + * + * This function will reset the complete USB FULL speed isochronous + * bandwidth schedule. + *------------------------------------------------------------------------*/ +#if USB_HAVE_TT_SUPPORT +void +usbd_fs_isoc_schedule_init_all(struct usb_fs_isoc_schedule *fss) +{ + struct usb_fs_isoc_schedule *fss_end = fss + USB_ISOC_TIME_MAX; + + while (fss != fss_end) { + usbd_fs_isoc_schedule_init_sub(fss); + fss++; + } +} +#endif + +/*------------------------------------------------------------------------* + * usb_isoc_time_expand + * + * This function will expand the time counter from 7-bit to 16-bit. + * + * Returns: + * 16-bit isochronous time counter. + *------------------------------------------------------------------------*/ +uint16_t +usb_isoc_time_expand(struct usb_bus *bus, uint16_t isoc_time_curr) +{ + uint16_t rem; + + USB_BUS_LOCK_ASSERT(bus, MA_OWNED); + + rem = bus->isoc_time_last & (USB_ISOC_TIME_MAX - 1); + + isoc_time_curr &= (USB_ISOC_TIME_MAX - 1); + + if (isoc_time_curr < rem) { + /* the time counter wrapped around */ + bus->isoc_time_last += USB_ISOC_TIME_MAX; + } + /* update the remainder */ + + bus->isoc_time_last &= ~(USB_ISOC_TIME_MAX - 1); + bus->isoc_time_last |= isoc_time_curr; + + return (bus->isoc_time_last); +} + +/*------------------------------------------------------------------------* + * usbd_fs_isoc_schedule_isoc_time_expand + * + * This function does multiple things. First of all it will expand the + * passed isochronous time, which is the return value. Then it will + * store where the current FULL speed isochronous schedule is + * positioned in time and where the end is. See "pp_start" and + * "pp_end" arguments. + * + * Returns: + * Expanded version of "isoc_time". + * + * NOTE: This function depends on being called regularly with + * intervals less than "USB_ISOC_TIME_MAX". + *------------------------------------------------------------------------*/ +#if USB_HAVE_TT_SUPPORT +uint16_t +usbd_fs_isoc_schedule_isoc_time_expand(struct usb_device *udev, + struct usb_fs_isoc_schedule **pp_start, + struct usb_fs_isoc_schedule **pp_end, + uint16_t isoc_time) +{ + struct usb_fs_isoc_schedule *fss_end; + struct usb_fs_isoc_schedule *fss_a; + struct usb_fs_isoc_schedule *fss_b; + struct usb_hub *hs_hub; + + isoc_time = usb_isoc_time_expand(udev->bus, isoc_time); + + hs_hub = udev->parent_hs_hub->hub; + + if (hs_hub != NULL) { + + fss_a = hs_hub->fs_isoc_schedule + + (hs_hub->isoc_last_time % USB_ISOC_TIME_MAX); + + hs_hub->isoc_last_time = isoc_time; + + fss_b = hs_hub->fs_isoc_schedule + + (isoc_time % USB_ISOC_TIME_MAX); + + fss_end = hs_hub->fs_isoc_schedule + USB_ISOC_TIME_MAX; + + *pp_start = hs_hub->fs_isoc_schedule; + *pp_end = fss_end; + + while (fss_a != fss_b) { + if (fss_a == fss_end) { + fss_a = hs_hub->fs_isoc_schedule; + continue; + } + usbd_fs_isoc_schedule_init_sub(fss_a); + fss_a++; + } + + } else { + + *pp_start = NULL; + *pp_end = NULL; + } + return (isoc_time); +} +#endif + +/*------------------------------------------------------------------------* + * usbd_fs_isoc_schedule_alloc + * + * This function will allocate bandwidth for an isochronous FULL speed + * transaction in the FULL speed schedule. The microframe slot where + * the transaction should be started is stored in the byte pointed to + * by "pstart". The "len" argument specifies the length of the + * transaction in bytes. + * + * Returns: + * 0: Success + * Else: Error + *------------------------------------------------------------------------*/ +#if USB_HAVE_TT_SUPPORT +uint8_t +usbd_fs_isoc_schedule_alloc(struct usb_fs_isoc_schedule *fss, + uint8_t *pstart, uint16_t len) +{ + uint8_t slot = fss->frame_slot; + + /* Compute overhead and bit-stuffing */ + + len += 8; + + len *= 7; + len /= 6; + + if (len > fss->total_bytes) { + *pstart = 0; /* set some dummy value */ + return (1); /* error */ + } + if (len > 0) { + + fss->total_bytes -= len; + + while (len >= fss->frame_bytes) { + len -= fss->frame_bytes; + fss->frame_bytes = USB_FS_BYTES_PER_HS_UFRAME; + fss->frame_slot++; + } + + fss->frame_bytes -= len; + } + *pstart = slot; + return (0); /* success */ +} +#endif + +/*------------------------------------------------------------------------* + * usb_bus_port_get_device + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +struct usb_device * +usb_bus_port_get_device(struct usb_bus *bus, struct usb_port *up) +{ + if ((bus == NULL) || (up == NULL)) { + /* be NULL safe */ + return (NULL); + } + if (up->device_index == 0) { + /* nothing to do */ + return (NULL); + } + return (bus->devices[up->device_index]); +} + +/*------------------------------------------------------------------------* + * usb_bus_port_set_device + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb_bus_port_set_device(struct usb_bus *bus, struct usb_port *up, + struct usb_device *udev, uint8_t device_index) +{ + if (bus == NULL) { + /* be NULL safe */ + return; + } + /* + * There is only one case where we don't + * have an USB port, and that is the Root Hub! + */ + if (up) { + if (udev) { + up->device_index = device_index; + } else { + device_index = up->device_index; + up->device_index = 0; + } + } + /* + * Make relationships to our new device + */ + if (device_index != 0) { +#if USB_HAVE_UGEN + mtx_lock(&usb_ref_lock); +#endif + bus->devices[device_index] = udev; +#if USB_HAVE_UGEN + mtx_unlock(&usb_ref_lock); +#endif + } + /* + * Debug print + */ + DPRINTFN(2, "bus %p devices[%u] = %p\n", bus, device_index, udev); +} + +/*------------------------------------------------------------------------* + * usb_needs_explore + * + * This functions is called when the USB event thread needs to run. + *------------------------------------------------------------------------*/ +void +usb_needs_explore(struct usb_bus *bus, uint8_t do_probe) +{ + uint8_t do_unlock; + + DPRINTF("\n"); + + if (bus == NULL) { + DPRINTF("No bus pointer!\n"); + return; + } + if ((bus->devices == NULL) || + (bus->devices[USB_ROOT_HUB_ADDR] == NULL)) { + DPRINTF("No root HUB\n"); + return; + } + if (mtx_owned(&bus->bus_mtx)) { + do_unlock = 0; + } else { + USB_BUS_LOCK(bus); + do_unlock = 1; + } + if (do_probe) { + bus->do_probe = 1; + } + if (usb_proc_msignal(&bus->explore_proc, + &bus->explore_msg[0], &bus->explore_msg[1])) { + /* ignore */ + } + if (do_unlock) { + USB_BUS_UNLOCK(bus); + } +} + +/*------------------------------------------------------------------------* + * usb_needs_explore_all + * + * This function is called whenever a new driver is loaded and will + * cause that all USB busses are re-explored. + *------------------------------------------------------------------------*/ +void +usb_needs_explore_all(void) +{ + struct usb_bus *bus; + devclass_t dc; + device_t dev; + int max; + + DPRINTFN(3, "\n"); + + dc = usb_devclass_ptr; + if (dc == NULL) { + DPRINTFN(0, "no devclass\n"); + return; + } + /* + * Explore all USB busses in parallell. + */ + max = devclass_get_maxunit(dc); + while (max >= 0) { + dev = devclass_get_device(dc, max); + if (dev) { + bus = device_get_softc(dev); + if (bus) { + usb_needs_explore(bus, 1); + } + } + max--; + } +} + +/*------------------------------------------------------------------------* + * usb_bus_power_update + * + * This function will ensure that all USB devices on the given bus are + * properly suspended or resumed according to the device transfer + * state. + *------------------------------------------------------------------------*/ +#if USB_HAVE_POWERD +void +usb_bus_power_update(struct usb_bus *bus) +{ + usb_needs_explore(bus, 0 /* no probe */ ); +} +#endif + +/*------------------------------------------------------------------------* + * usbd_transfer_power_ref + * + * This function will modify the power save reference counts and + * wakeup the USB device associated with the given USB transfer, if + * needed. + *------------------------------------------------------------------------*/ +#if USB_HAVE_POWERD +void +usbd_transfer_power_ref(struct usb_xfer *xfer, int val) +{ + static const usb_power_mask_t power_mask[4] = { + [UE_CONTROL] = USB_HW_POWER_CONTROL, + [UE_BULK] = USB_HW_POWER_BULK, + [UE_INTERRUPT] = USB_HW_POWER_INTERRUPT, + [UE_ISOCHRONOUS] = USB_HW_POWER_ISOC, + }; + struct usb_device *udev; + uint8_t needs_explore; + uint8_t needs_hw_power; + uint8_t xfer_type; + + udev = xfer->xroot->udev; + + if (udev->device_index == USB_ROOT_HUB_ADDR) { + /* no power save for root HUB */ + return; + } + USB_BUS_LOCK(udev->bus); + + xfer_type = xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE; + + udev->pwr_save.last_xfer_time = ticks; + udev->pwr_save.type_refs[xfer_type] += val; + + if (xfer->flags_int.control_xfr) { + udev->pwr_save.read_refs += val; + if (xfer->flags_int.usb_mode == USB_MODE_HOST) { + /* + * It is not allowed to suspend during a + * control transfer: + */ + udev->pwr_save.write_refs += val; + } + } else if (USB_GET_DATA_ISREAD(xfer)) { + udev->pwr_save.read_refs += val; + } else { + udev->pwr_save.write_refs += val; + } + + if (val > 0) { + if (udev->flags.self_suspended) + needs_explore = usb_peer_should_wakeup(udev); + else + needs_explore = 0; + + if (!(udev->bus->hw_power_state & power_mask[xfer_type])) { + DPRINTF("Adding type %u to power state\n", xfer_type); + udev->bus->hw_power_state |= power_mask[xfer_type]; + needs_hw_power = 1; + } else { + needs_hw_power = 0; + } + } else { + needs_explore = 0; + needs_hw_power = 0; + } + + USB_BUS_UNLOCK(udev->bus); + + if (needs_explore) { + DPRINTF("update\n"); + usb_bus_power_update(udev->bus); + } else if (needs_hw_power) { + DPRINTF("needs power\n"); + if (udev->bus->methods->set_hw_power != NULL) { + (udev->bus->methods->set_hw_power) (udev->bus); + } + } +} +#endif + +/*------------------------------------------------------------------------* + * usb_peer_should_wakeup + * + * This function returns non-zero if the current device should wake up. + *------------------------------------------------------------------------*/ +static uint8_t +usb_peer_should_wakeup(struct usb_device *udev) +{ + return ((udev->power_mode == USB_POWER_MODE_ON) || + (udev->driver_added_refcount != udev->bus->driver_added_refcount) || + (udev->re_enumerate_wait != 0) || + (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0) || + (udev->pwr_save.write_refs != 0) || + ((udev->pwr_save.read_refs != 0) && + (udev->flags.usb_mode == USB_MODE_HOST) && + (usb_peer_can_wakeup(udev) == 0))); +} + +/*------------------------------------------------------------------------* + * usb_bus_powerd + * + * This function implements the USB power daemon and is called + * regularly from the USB explore thread. + *------------------------------------------------------------------------*/ +#if USB_HAVE_POWERD +void +usb_bus_powerd(struct usb_bus *bus) +{ + struct usb_device *udev; + usb_ticks_t temp; + usb_ticks_t limit; + usb_ticks_t mintime; + usb_size_t type_refs[5]; + uint8_t x; + + limit = usb_power_timeout; + if (limit == 0) + limit = hz; + else if (limit > 255) + limit = 255 * hz; + else + limit = limit * hz; + + DPRINTF("bus=%p\n", bus); + + USB_BUS_LOCK(bus); + + /* + * The root HUB device is never suspended + * and we simply skip it. + */ + for (x = USB_ROOT_HUB_ADDR + 1; + x != bus->devices_max; x++) { + + udev = bus->devices[x]; + if (udev == NULL) + continue; + + temp = ticks - udev->pwr_save.last_xfer_time; + + if (usb_peer_should_wakeup(udev)) { + /* check if we are suspended */ + if (udev->flags.self_suspended != 0) { + USB_BUS_UNLOCK(bus); + usb_dev_resume_peer(udev); + USB_BUS_LOCK(bus); + } + } else if ((temp >= limit) && + (udev->flags.usb_mode == USB_MODE_HOST) && + (udev->flags.self_suspended == 0)) { + /* try to do suspend */ + + USB_BUS_UNLOCK(bus); + usb_dev_suspend_peer(udev); + USB_BUS_LOCK(bus); + } + } + + /* reset counters */ + + mintime = 0 - 1; + type_refs[0] = 0; + type_refs[1] = 0; + type_refs[2] = 0; + type_refs[3] = 0; + type_refs[4] = 0; + + /* Re-loop all the devices to get the actual state */ + + for (x = USB_ROOT_HUB_ADDR + 1; + x != bus->devices_max; x++) { + + udev = bus->devices[x]; + if (udev == NULL) + continue; + + /* we found a non-Root-Hub USB device */ + type_refs[4] += 1; + + /* "last_xfer_time" can be updated by a resume */ + temp = ticks - udev->pwr_save.last_xfer_time; + + /* + * Compute minimum time since last transfer for the complete + * bus: + */ + if (temp < mintime) + mintime = temp; + + if (udev->flags.self_suspended == 0) { + type_refs[0] += udev->pwr_save.type_refs[0]; + type_refs[1] += udev->pwr_save.type_refs[1]; + type_refs[2] += udev->pwr_save.type_refs[2]; + type_refs[3] += udev->pwr_save.type_refs[3]; + } + } + + if (mintime >= (1 * hz)) { + /* recompute power masks */ + DPRINTF("Recomputing power masks\n"); + bus->hw_power_state = 0; + if (type_refs[UE_CONTROL] != 0) + bus->hw_power_state |= USB_HW_POWER_CONTROL; + if (type_refs[UE_BULK] != 0) + bus->hw_power_state |= USB_HW_POWER_BULK; + if (type_refs[UE_INTERRUPT] != 0) + bus->hw_power_state |= USB_HW_POWER_INTERRUPT; + if (type_refs[UE_ISOCHRONOUS] != 0) + bus->hw_power_state |= USB_HW_POWER_ISOC; + if (type_refs[4] != 0) + bus->hw_power_state |= USB_HW_POWER_NON_ROOT_HUB; + } + USB_BUS_UNLOCK(bus); + + if (bus->methods->set_hw_power != NULL) { + /* always update hardware power! */ + (bus->methods->set_hw_power) (bus); + } + return; +} +#endif + +/*------------------------------------------------------------------------* + * usb_dev_resume_peer + * + * This function will resume an USB peer and do the required USB + * signalling to get an USB device out of the suspended state. + *------------------------------------------------------------------------*/ +static void +usb_dev_resume_peer(struct usb_device *udev) +{ + struct usb_bus *bus; + int err; + + /* be NULL safe */ + if (udev == NULL) + return; + + /* check if already resumed */ + if (udev->flags.self_suspended == 0) + return; + + /* we need a parent HUB to do resume */ + if (udev->parent_hub == NULL) + return; + + DPRINTF("udev=%p\n", udev); + + if ((udev->flags.usb_mode == USB_MODE_DEVICE) && + (udev->flags.remote_wakeup == 0)) { + /* + * If the host did not set the remote wakeup feature, we can + * not wake it up either! + */ + DPRINTF("remote wakeup is not set!\n"); + return; + } + /* get bus pointer */ + bus = udev->bus; + + /* resume parent hub first */ + usb_dev_resume_peer(udev->parent_hub); + + /* reduce chance of instant resume failure by waiting a little bit */ + usb_pause_mtx(NULL, USB_MS_TO_TICKS(20)); + + if (usb_device_20_compatible(udev)) { + /* resume current port (Valid in Host and Device Mode) */ + err = usbd_req_clear_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_SUSPEND); + if (err) { + DPRINTFN(0, "Resuming port failed\n"); + return; + } + } else { + /* resume current port (Valid in Host and Device Mode) */ + err = usbd_req_set_port_link_state(udev->parent_hub, + NULL, udev->port_no, UPS_PORT_LS_U0); + if (err) { + DPRINTFN(0, "Resuming port failed\n"); + return; + } + } + + /* resume settle time */ + usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_PORT_RESUME_DELAY)); + + if (bus->methods->device_resume != NULL) { + /* resume USB device on the USB controller */ + (bus->methods->device_resume) (udev); + } + USB_BUS_LOCK(bus); + /* set that this device is now resumed */ + udev->flags.self_suspended = 0; +#if USB_HAVE_POWERD + /* make sure that we don't go into suspend right away */ + udev->pwr_save.last_xfer_time = ticks; + + /* make sure the needed power masks are on */ + if (udev->pwr_save.type_refs[UE_CONTROL] != 0) + bus->hw_power_state |= USB_HW_POWER_CONTROL; + if (udev->pwr_save.type_refs[UE_BULK] != 0) + bus->hw_power_state |= USB_HW_POWER_BULK; + if (udev->pwr_save.type_refs[UE_INTERRUPT] != 0) + bus->hw_power_state |= USB_HW_POWER_INTERRUPT; + if (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0) + bus->hw_power_state |= USB_HW_POWER_ISOC; +#endif + USB_BUS_UNLOCK(bus); + + if (bus->methods->set_hw_power != NULL) { + /* always update hardware power! */ + (bus->methods->set_hw_power) (bus); + } + + usbd_sr_lock(udev); + + /* notify all sub-devices about resume */ + err = usb_suspend_resume(udev, 0); + + usbd_sr_unlock(udev); + + /* check if peer has wakeup capability */ + if (usb_peer_can_wakeup(udev)) { + /* clear remote wakeup */ + err = usbd_req_clear_device_feature(udev, + NULL, UF_DEVICE_REMOTE_WAKEUP); + if (err) { + DPRINTFN(0, "Clearing device " + "remote wakeup failed: %s\n", + usbd_errstr(err)); + } + } +} + +/*------------------------------------------------------------------------* + * usb_dev_suspend_peer + * + * This function will suspend an USB peer and do the required USB + * signalling to get an USB device into the suspended state. + *------------------------------------------------------------------------*/ +static void +usb_dev_suspend_peer(struct usb_device *udev) +{ + struct usb_device *child; + int err; + uint8_t x; + uint8_t nports; + +repeat: + /* be NULL safe */ + if (udev == NULL) + return; + + /* check if already suspended */ + if (udev->flags.self_suspended) + return; + + /* we need a parent HUB to do suspend */ + if (udev->parent_hub == NULL) + return; + + DPRINTF("udev=%p\n", udev); + + /* check if the current device is a HUB */ + if (udev->hub != NULL) { + nports = udev->hub->nports; + + /* check if all devices on the HUB are suspended */ + for (x = 0; x != nports; x++) { + child = usb_bus_port_get_device(udev->bus, + udev->hub->ports + x); + + if (child == NULL) + continue; + + if (child->flags.self_suspended) + continue; + + DPRINTFN(1, "Port %u is busy on the HUB!\n", x + 1); + return; + } + } + + if (usb_peer_can_wakeup(udev)) { + /* + * This request needs to be done before we set + * "udev->flags.self_suspended": + */ + + /* allow device to do remote wakeup */ + err = usbd_req_set_device_feature(udev, + NULL, UF_DEVICE_REMOTE_WAKEUP); + if (err) { + DPRINTFN(0, "Setting device " + "remote wakeup failed\n"); + } + } + + USB_BUS_LOCK(udev->bus); + /* + * Checking for suspend condition and setting suspended bit + * must be atomic! + */ + err = usb_peer_should_wakeup(udev); + if (err == 0) { + /* + * Set that this device is suspended. This variable + * must be set before calling USB controller suspend + * callbacks. + */ + udev->flags.self_suspended = 1; + } + USB_BUS_UNLOCK(udev->bus); + + if (err != 0) { + if (usb_peer_can_wakeup(udev)) { + /* allow device to do remote wakeup */ + err = usbd_req_clear_device_feature(udev, + NULL, UF_DEVICE_REMOTE_WAKEUP); + if (err) { + DPRINTFN(0, "Setting device " + "remote wakeup failed\n"); + } + } + + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + /* resume parent HUB first */ + usb_dev_resume_peer(udev->parent_hub); + + /* reduce chance of instant resume failure by waiting a little bit */ + usb_pause_mtx(NULL, USB_MS_TO_TICKS(20)); + + /* resume current port (Valid in Host and Device Mode) */ + err = usbd_req_clear_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_SUSPEND); + + /* resume settle time */ + usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_PORT_RESUME_DELAY)); + } + DPRINTF("Suspend was cancelled!\n"); + return; + } + + usbd_sr_lock(udev); + + /* notify all sub-devices about suspend */ + err = usb_suspend_resume(udev, 1); + + usbd_sr_unlock(udev); + + if (udev->bus->methods->device_suspend != NULL) { + usb_timeout_t temp; + + /* suspend device on the USB controller */ + (udev->bus->methods->device_suspend) (udev); + + /* do DMA delay */ + temp = usbd_get_dma_delay(udev); + if (temp != 0) + usb_pause_mtx(NULL, USB_MS_TO_TICKS(temp)); + + } + + if (usb_device_20_compatible(udev)) { + /* suspend current port */ + err = usbd_req_set_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_SUSPEND); + if (err) { + DPRINTFN(0, "Suspending port failed\n"); + return; + } + } else { + /* suspend current port */ + err = usbd_req_set_port_link_state(udev->parent_hub, + NULL, udev->port_no, UPS_PORT_LS_U3); + if (err) { + DPRINTFN(0, "Suspending port failed\n"); + return; + } + } + + udev = udev->parent_hub; + goto repeat; +} + +/*------------------------------------------------------------------------* + * usbd_set_power_mode + * + * This function will set the power mode, see USB_POWER_MODE_XXX for a + * USB device. + *------------------------------------------------------------------------*/ +void +usbd_set_power_mode(struct usb_device *udev, uint8_t power_mode) +{ + /* filter input argument */ + if ((power_mode != USB_POWER_MODE_ON) && + (power_mode != USB_POWER_MODE_OFF)) + power_mode = USB_POWER_MODE_SAVE; + + power_mode = usbd_filter_power_mode(udev, power_mode); + + udev->power_mode = power_mode; /* update copy of power mode */ + +#if USB_HAVE_POWERD + usb_bus_power_update(udev->bus); +#endif +} + +/*------------------------------------------------------------------------* + * usbd_filter_power_mode + * + * This function filters the power mode based on hardware requirements. + *------------------------------------------------------------------------*/ +uint8_t +usbd_filter_power_mode(struct usb_device *udev, uint8_t power_mode) +{ + struct usb_bus_methods *mtod; + int8_t temp; + + mtod = udev->bus->methods; + temp = -1; + + if (mtod->get_power_mode != NULL) + (mtod->get_power_mode) (udev, &temp); + + /* check if we should not filter */ + if (temp < 0) + return (power_mode); + + /* use fixed power mode given by hardware driver */ + return (temp); +} + +/*------------------------------------------------------------------------* + * usbd_start_re_enumerate + * + * This function starts re-enumeration of the given USB device. This + * function does not need to be called BUS-locked. This function does + * not wait until the re-enumeration is completed. + *------------------------------------------------------------------------*/ +void +usbd_start_re_enumerate(struct usb_device *udev) +{ + if (udev->re_enumerate_wait == 0) { + udev->re_enumerate_wait = 1; + usb_needs_explore(udev->bus, 0); + } +} diff --git a/sys/bus/u4b/usb_hub.h b/sys/bus/u4b/usb_hub.h new file mode 100644 index 0000000000..0f59599770 --- /dev/null +++ b/sys/bus/u4b/usb_hub.h @@ -0,0 +1,83 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_HUB_H_ +#define _USB_HUB_H_ + +/* + * The following structure defines an USB port. + */ +struct usb_port { + uint8_t restartcnt; +#define USB_RESTART_MAX 5 + uint8_t device_index; /* zero means not valid */ + enum usb_hc_mode usb_mode; /* host or device mode */ +}; + +/* + * The following structure defines how many bytes are + * left in an 1ms USB time slot. + */ +struct usb_fs_isoc_schedule { + uint16_t total_bytes; + uint8_t frame_bytes; + uint8_t frame_slot; +}; + +/* + * The following structure defines an USB HUB. + */ +struct usb_hub { +#if USB_HAVE_TT_SUPPORT + struct usb_fs_isoc_schedule fs_isoc_schedule[USB_ISOC_TIME_MAX]; +#endif + struct usb_device *hubudev; /* the HUB device */ + usb_error_t (*explore) (struct usb_device *hub); + void *hubsoftc; + usb_size_t uframe_usage[USB_HS_MICRO_FRAMES_MAX]; + uint16_t portpower; /* mA per USB port */ + uint8_t isoc_last_time; + uint8_t nports; + struct usb_port ports[0]; +}; + +/* function prototypes */ + +void usb_hs_bandwidth_alloc(struct usb_xfer *xfer); +void usb_hs_bandwidth_free(struct usb_xfer *xfer); +void usbd_fs_isoc_schedule_init_all(struct usb_fs_isoc_schedule *fss); +void usb_bus_port_set_device(struct usb_bus *bus, struct usb_port *up, + struct usb_device *udev, uint8_t device_index); +struct usb_device *usb_bus_port_get_device(struct usb_bus *bus, + struct usb_port *up); +void usb_needs_explore(struct usb_bus *bus, uint8_t do_probe); +void usb_needs_explore_all(void); +void usb_bus_power_update(struct usb_bus *bus); +void usb_bus_powerd(struct usb_bus *bus); +void uhub_root_intr(struct usb_bus *, const uint8_t *, uint8_t); +usb_error_t uhub_query_info(struct usb_device *, uint8_t *, uint8_t *); + +#endif /* _USB_HUB_H_ */ diff --git a/sys/bus/u4b/usb_if.m b/sys/bus/u4b/usb_if.m new file mode 100644 index 0000000000..b24fc2b386 --- /dev/null +++ b/sys/bus/u4b/usb_if.m @@ -0,0 +1,66 @@ +#- +# Copyright (c) 2008 Hans Petter Selasky. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer, +# without modification, immediately at the beginning of the file. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# $FreeBSD$ +# + +# USB interface description +# + +#include + +INTERFACE usb; + +# The device received a control request +# +# The value pointed to by "pstate" can be updated to +# "USB_HR_COMPLETE_OK" to indicate that the control +# read transfer is complete, in case of short USB +# control transfers. +# +# Return values: +# 0: Success +# ENOTTY: Transaction stalled +# Else: Use builtin request handler +# +METHOD int handle_request { + device_t dev; + const void *req; /* pointer to the device request */ + void **pptr; /* data pointer */ + uint16_t *plen; /* maximum transfer length */ + uint16_t offset; /* data offset */ + uint8_t *pstate; /* set if transfer is complete, see USB_HR_XXX */ +}; + +# Take controller from BIOS +# +# Return values: +# 0: Success +# Else: Failure +# +METHOD int take_controller { + device_t dev; +}; diff --git a/sys/bus/u4b/usb_ioctl.h b/sys/bus/u4b/usb_ioctl.h new file mode 100644 index 0000000000..9af6ee5c7a --- /dev/null +++ b/sys/bus/u4b/usb_ioctl.h @@ -0,0 +1,312 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_IOCTL_H_ +#define _USB_IOCTL_H_ + +#include + +/* Building "kdump" depends on these includes */ + +#include +#include + +#define USB_DEVICE_NAME "usbctl" +#define USB_DEVICE_DIR "usb" +#define USB_GENERIC_NAME "ugen" +#define USB_TEMPLATE_SYSCTL "hw.usb.template" /* integer type */ + +/* Definition of valid template sysctl values */ + +enum { + USB_TEMP_MSC, /* USB Mass Storage */ + USB_TEMP_CDCE, /* USB CDC Ethernet */ + USB_TEMP_MTP, /* Message Transfer Protocol */ + USB_TEMP_MODEM, /* USB CDC Modem */ + USB_TEMP_AUDIO, /* USB Audio */ + USB_TEMP_KBD, /* USB Keyboard */ + USB_TEMP_MOUSE, /* USB Mouse */ + USB_TEMP_MAX, +}; + +struct usb_read_dir { +#ifdef COMPAT_32BIT + uint64_t urd_data; +#else + void *urd_data; +#endif + uint32_t urd_startentry; + uint32_t urd_maxlen; +}; + +struct usb_ctl_request { +#ifdef COMPAT_32BIT + uint64_t ucr_data; +#else + void *ucr_data; +#endif + uint16_t ucr_flags; + uint16_t ucr_actlen; /* actual length transferred */ + uint8_t ucr_addr; /* zero - currently not used */ + struct usb_device_request ucr_request; +}; + +struct usb_alt_interface { + uint8_t uai_interface_index; + uint8_t uai_alt_index; +}; + +struct usb_gen_descriptor { +#ifdef COMPAT_32BIT + uint64_t ugd_data; +#else + void *ugd_data; +#endif + uint16_t ugd_lang_id; + uint16_t ugd_maxlen; + uint16_t ugd_actlen; + uint16_t ugd_offset; + uint8_t ugd_config_index; + uint8_t ugd_string_index; + uint8_t ugd_iface_index; + uint8_t ugd_altif_index; + uint8_t ugd_endpt_index; + uint8_t ugd_report_type; + uint8_t reserved[8]; +}; + +struct usb_device_info { + uint16_t udi_productNo; + uint16_t udi_vendorNo; + uint16_t udi_releaseNo; + uint16_t udi_power; /* power consumption in mA, 0 if + * selfpowered */ + uint8_t udi_bus; + uint8_t udi_addr; /* device address */ + uint8_t udi_index; /* device index */ + uint8_t udi_class; + uint8_t udi_subclass; + uint8_t udi_protocol; + uint8_t udi_config_no; /* current config number */ + uint8_t udi_config_index; /* current config index */ + uint8_t udi_speed; /* see "USB_SPEED_XXX" */ + uint8_t udi_mode; /* see "USB_MODE_XXX" */ + uint8_t udi_nports; + uint8_t udi_hubaddr; /* parent HUB address */ + uint8_t udi_hubindex; /* parent HUB device index */ + uint8_t udi_hubport; /* parent HUB port */ + uint8_t udi_power_mode; /* see "USB_POWER_MODE_XXX" */ + uint8_t udi_suspended; /* set if device is suspended */ + uint8_t udi_reserved[16]; /* leave space for the future */ + char udi_product[128]; + char udi_vendor[128]; + char udi_serial[64]; + char udi_release[8]; +}; + +struct usb_device_stats { + uint32_t uds_requests_ok[4]; /* Indexed by transfer type UE_XXX */ + uint32_t uds_requests_fail[4]; /* Indexed by transfer type UE_XXX */ +}; + +struct usb_fs_start { + uint8_t ep_index; +}; + +struct usb_fs_stop { + uint8_t ep_index; +}; + +struct usb_fs_complete { + uint8_t ep_index; +}; + +/* This structure is used for all endpoint types */ +struct usb_fs_endpoint { + /* + * NOTE: isochronous USB transfer only use one buffer, but can have + * multiple frame lengths ! + */ +#ifdef COMPAT_32BIT + uint64_t ppBuffer; + uint64_t pLength; +#else + void **ppBuffer; /* pointer to userland buffers */ + uint32_t *pLength; /* pointer to frame lengths, updated + * to actual length */ +#endif + uint32_t nFrames; /* number of frames */ + uint32_t aFrames; /* actual number of frames */ + uint16_t flags; + /* a single short frame will terminate */ +#define USB_FS_FLAG_SINGLE_SHORT_OK 0x0001 + /* multiple short frames are allowed */ +#define USB_FS_FLAG_MULTI_SHORT_OK 0x0002 + /* all frame(s) transmitted are short terminated */ +#define USB_FS_FLAG_FORCE_SHORT 0x0004 + /* will do a clear-stall before xfer */ +#define USB_FS_FLAG_CLEAR_STALL 0x0008 + uint16_t timeout; /* in milliseconds */ + /* isocronous completion time in milliseconds - used for echo cancel */ + uint16_t isoc_time_complete; + /* timeout value for no timeout */ +#define USB_FS_TIMEOUT_NONE 0 + int status; /* see USB_ERR_XXX */ +}; + +struct usb_fs_init { + /* userland pointer to endpoints structure */ +#ifdef COMPAT_32BIT + uint64_t pEndpoints; +#else + struct usb_fs_endpoint *pEndpoints; +#endif + /* maximum number of endpoints */ + uint8_t ep_index_max; +}; + +struct usb_fs_uninit { + uint8_t dummy; /* zero */ +}; + +struct usb_fs_open { +#define USB_FS_MAX_BUFSIZE (1 << 18) + uint32_t max_bufsize; +#define USB_FS_MAX_FRAMES (1U << 12) +#define USB_FS_MAX_FRAMES_PRE_SCALE (1U << 31) /* for ISOCHRONOUS transfers */ + uint32_t max_frames; /* read and write */ + uint16_t max_packet_length; /* read only */ + uint8_t dev_index; /* currently unused */ + uint8_t ep_index; + uint8_t ep_no; /* bEndpointNumber */ +}; + +struct usb_fs_close { + uint8_t ep_index; +}; + +struct usb_fs_clear_stall_sync { + uint8_t ep_index; +}; + +struct usb_gen_quirk { + uint16_t index; /* Quirk Index */ + uint16_t vid; /* Vendor ID */ + uint16_t pid; /* Product ID */ + uint16_t bcdDeviceLow; /* Low Device Revision */ + uint16_t bcdDeviceHigh; /* High Device Revision */ + uint16_t reserved[2]; + /* + * String version of quirk including terminating zero. See UQ_XXX in + * "usb_quirk.h". + */ + char quirkname[64 - 14]; +}; + +/* USB controller */ +#define USB_REQUEST _IOWR('U', 1, struct usb_ctl_request) +#define USB_SETDEBUG _IOW ('U', 2, int) +#define USB_DISCOVER _IO ('U', 3) +#define USB_DEVICEINFO _IOWR('U', 4, struct usb_device_info) +#define USB_DEVICESTATS _IOR ('U', 5, struct usb_device_stats) +#define USB_DEVICEENUMERATE _IOW ('U', 6, int) + +/* Generic HID device */ +#define USB_GET_REPORT_DESC _IOWR('U', 21, struct usb_gen_descriptor) +#define USB_SET_IMMED _IOW ('U', 22, int) +#define USB_GET_REPORT _IOWR('U', 23, struct usb_gen_descriptor) +#define USB_SET_REPORT _IOW ('U', 24, struct usb_gen_descriptor) +#define USB_GET_REPORT_ID _IOR ('U', 25, int) + +/* Generic USB device */ +#define USB_GET_CONFIG _IOR ('U', 100, int) +#define USB_SET_CONFIG _IOW ('U', 101, int) +#define USB_GET_ALTINTERFACE _IOWR('U', 102, struct usb_alt_interface) +#define USB_SET_ALTINTERFACE _IOWR('U', 103, struct usb_alt_interface) +#define USB_GET_DEVICE_DESC _IOR ('U', 105, struct usb_device_descriptor) +#define USB_GET_CONFIG_DESC _IOR ('U', 106, struct usb_config_descriptor) +#define USB_GET_RX_INTERFACE_DESC _IOR ('U', 107, struct usb_interface_descriptor) +#define USB_GET_RX_ENDPOINT_DESC _IOR ('U', 108, struct usb_endpoint_descriptor) +#define USB_GET_FULL_DESC _IOWR('U', 109, struct usb_gen_descriptor) +#define USB_GET_STRING_DESC _IOWR('U', 110, struct usb_gen_descriptor) +#define USB_DO_REQUEST _IOWR('U', 111, struct usb_ctl_request) +#define USB_GET_DEVICEINFO _IOR ('U', 112, struct usb_device_info) +#define USB_SET_RX_SHORT_XFER _IOW ('U', 113, int) +#define USB_SET_RX_TIMEOUT _IOW ('U', 114, int) +#define USB_GET_RX_FRAME_SIZE _IOR ('U', 115, int) +#define USB_GET_RX_BUFFER_SIZE _IOR ('U', 117, int) +#define USB_SET_RX_BUFFER_SIZE _IOW ('U', 118, int) +#define USB_SET_RX_STALL_FLAG _IOW ('U', 119, int) +#define USB_SET_TX_STALL_FLAG _IOW ('U', 120, int) +#define USB_GET_IFACE_DRIVER _IOWR('U', 121, struct usb_gen_descriptor) +#define USB_CLAIM_INTERFACE _IOW ('U', 122, int) +#define USB_RELEASE_INTERFACE _IOW ('U', 123, int) +#define USB_IFACE_DRIVER_ACTIVE _IOW ('U', 124, int) +#define USB_IFACE_DRIVER_DETACH _IOW ('U', 125, int) +#define USB_GET_PLUGTIME _IOR ('U', 126, uint32_t) +#define USB_READ_DIR _IOW ('U', 127, struct usb_read_dir) +/* 128 - 135 unused */ +#define USB_SET_TX_FORCE_SHORT _IOW ('U', 136, int) +#define USB_SET_TX_TIMEOUT _IOW ('U', 137, int) +#define USB_GET_TX_FRAME_SIZE _IOR ('U', 138, int) +#define USB_GET_TX_BUFFER_SIZE _IOR ('U', 139, int) +#define USB_SET_TX_BUFFER_SIZE _IOW ('U', 140, int) +#define USB_GET_TX_INTERFACE_DESC _IOR ('U', 141, struct usb_interface_descriptor) +#define USB_GET_TX_ENDPOINT_DESC _IOR ('U', 142, struct usb_endpoint_descriptor) +#define USB_SET_PORT_ENABLE _IOW ('U', 143, int) +#define USB_SET_PORT_DISABLE _IOW ('U', 144, int) +#define USB_SET_POWER_MODE _IOW ('U', 145, int) +#define USB_GET_POWER_MODE _IOR ('U', 146, int) +#define USB_SET_TEMPLATE _IOW ('U', 147, int) +#define USB_GET_TEMPLATE _IOR ('U', 148, int) + +/* Modem device */ +#define USB_GET_CM_OVER_DATA _IOR ('U', 180, int) +#define USB_SET_CM_OVER_DATA _IOW ('U', 181, int) + +/* GPIO control */ +#define USB_GET_GPIO _IOR ('U', 182, int) +#define USB_SET_GPIO _IOW ('U', 183, int) + +/* USB file system interface */ +#define USB_FS_START _IOW ('U', 192, struct usb_fs_start) +#define USB_FS_STOP _IOW ('U', 193, struct usb_fs_stop) +#define USB_FS_COMPLETE _IOR ('U', 194, struct usb_fs_complete) +#define USB_FS_INIT _IOW ('U', 195, struct usb_fs_init) +#define USB_FS_UNINIT _IOW ('U', 196, struct usb_fs_uninit) +#define USB_FS_OPEN _IOWR('U', 197, struct usb_fs_open) +#define USB_FS_CLOSE _IOW ('U', 198, struct usb_fs_close) +#define USB_FS_CLEAR_STALL_SYNC _IOW ('U', 199, struct usb_fs_clear_stall_sync) + +/* USB quirk system interface */ +#define USB_DEV_QUIRK_GET _IOWR('Q', 0, struct usb_gen_quirk) +#define USB_QUIRK_NAME_GET _IOWR('Q', 1, struct usb_gen_quirk) +#define USB_DEV_QUIRK_ADD _IOW ('Q', 2, struct usb_gen_quirk) +#define USB_DEV_QUIRK_REMOVE _IOW ('Q', 3, struct usb_gen_quirk) + +#endif /* _USB_IOCTL_H_ */ diff --git a/sys/bus/u4b/usb_lookup.c b/sys/bus/u4b/usb_lookup.c new file mode 100644 index 0000000000..e03f9b60b2 --- /dev/null +++ b/sys/bus/u4b/usb_lookup.c @@ -0,0 +1,253 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/*------------------------------------------------------------------------* + * usbd_lookup_id_by_info + * + * This functions takes an array of "struct usb_device_id" and tries + * to match the entries with the information in "struct usbd_lookup_info". + * + * NOTE: The "sizeof_id" parameter must be a multiple of the + * usb_device_id structure size. Else the behaviour of this function + * is undefined. + * + * Return values: + * NULL: No match found. + * Else: Pointer to matching entry. + *------------------------------------------------------------------------*/ +const struct usb_device_id * +usbd_lookup_id_by_info(const struct usb_device_id *id, usb_size_t sizeof_id, + const struct usbd_lookup_info *info) +{ + const struct usb_device_id *id_end; + + if (id == NULL) { + goto done; + } + id_end = (const void *)(((const uint8_t *)id) + sizeof_id); + + /* + * Keep on matching array entries until we find a match or + * until we reach the end of the matching array: + */ + for (; id != id_end; id++) { + + if ((id->match_flag_vendor) && + (id->idVendor != info->idVendor)) { + continue; + } + if ((id->match_flag_product) && + (id->idProduct != info->idProduct)) { + continue; + } + if ((id->match_flag_dev_lo) && + (id->bcdDevice_lo > info->bcdDevice)) { + continue; + } + if ((id->match_flag_dev_hi) && + (id->bcdDevice_hi < info->bcdDevice)) { + continue; + } + if ((id->match_flag_dev_class) && + (id->bDeviceClass != info->bDeviceClass)) { + continue; + } + if ((id->match_flag_dev_subclass) && + (id->bDeviceSubClass != info->bDeviceSubClass)) { + continue; + } + if ((id->match_flag_dev_protocol) && + (id->bDeviceProtocol != info->bDeviceProtocol)) { + continue; + } + if ((id->match_flag_int_class) && + (id->bInterfaceClass != info->bInterfaceClass)) { + continue; + } + if ((id->match_flag_int_subclass) && + (id->bInterfaceSubClass != info->bInterfaceSubClass)) { + continue; + } + if ((id->match_flag_int_protocol) && + (id->bInterfaceProtocol != info->bInterfaceProtocol)) { + continue; + } + /* We found a match! */ + return (id); + } + +done: + return (NULL); +} + +/*------------------------------------------------------------------------* + * usbd_lookup_id_by_uaa - factored out code + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +int +usbd_lookup_id_by_uaa(const struct usb_device_id *id, usb_size_t sizeof_id, + struct usb_attach_arg *uaa) +{ + id = usbd_lookup_id_by_info(id, sizeof_id, &uaa->info); + if (id) { + /* copy driver info */ + uaa->driver_info = id->driver_info; + return (0); + } + return (ENXIO); +} + +/*------------------------------------------------------------------------* + * Export the USB device ID format we use to userspace tools. + *------------------------------------------------------------------------*/ +#if BYTE_ORDER == BIG_ENDIAN +#define U16_XOR "8" +#define U32_XOR "12" +#define U64_XOR "56" +#define U8_BITFIELD_XOR "7" +#define U16_BITFIELD_XOR "15" +#define U32_BITFIELD_XOR "31" +#define U64_BITFIELD_XOR "63" +#else +#define U16_XOR "0" +#define U32_XOR "0" +#define U64_XOR "0" +#define U8_BITFIELD_XOR "0" +#define U16_BITFIELD_XOR "0" +#define U32_BITFIELD_XOR "0" +#define U64_BITFIELD_XOR "0" +#endif + +#if USB_HAVE_COMPAT_LINUX +#define MFL_SIZE "1" +#else +#define MFL_SIZE "0" +#endif + +#ifdef KLD_MODULE +static const char __section("bus_autoconf_format") __used usb_id_format[] = { + + /* Declare that three different sections use the same format */ + + "usb_host_id{256,:}" + "usb_device_id{256,:}" + "usb_dual_id{256,:}" + + /* List size of fields in the usb_device_id structure */ + +#if ULONG_MAX >= 0xFFFFFFFFUL + "unused{0,8}" + "unused{0,8}" + "unused{0,8}" + "unused{0,8}" +#if ULONG_MAX >= 0xFFFFFFFFFFFFFFFFULL + "unused{0,8}" + "unused{0,8}" + "unused{0,8}" + "unused{0,8}" +#endif +#else +#error "Please update code." +#endif + + "idVendor[0]{" U16_XOR ",8}" + "idVendor[1]{" U16_XOR ",8}" + "idProduct[0]{" U16_XOR ",8}" + "idProduct[1]{" U16_XOR ",8}" + "bcdDevice_lo[0]{" U16_XOR ",8}" + "bcdDevice_lo[1]{" U16_XOR ",8}" + "bcdDevice_hi[0]{" U16_XOR ",8}" + "bcdDevice_hi[1]{" U16_XOR ",8}" + + "bDeviceClass{0,8}" + "bDeviceSubClass{0,8}" + "bDeviceProtocol{0,8}" + "bInterfaceClass{0,8}" + "bInterfaceSubClass{0,8}" + "bInterfaceProtocol{0,8}" + + "mf_vendor{" U8_BITFIELD_XOR ",1}" + "mf_product{" U8_BITFIELD_XOR ",1}" + "mf_dev_lo{" U8_BITFIELD_XOR ",1}" + "mf_dev_hi{" U8_BITFIELD_XOR ",1}" + + "mf_dev_class{" U8_BITFIELD_XOR ",1}" + "mf_dev_subclass{" U8_BITFIELD_XOR ",1}" + "mf_dev_protocol{" U8_BITFIELD_XOR ",1}" + "mf_int_class{" U8_BITFIELD_XOR ",1}" + + "mf_int_subclass{" U8_BITFIELD_XOR ",1}" + "mf_int_protocol{" U8_BITFIELD_XOR ",1}" + "unused{" U8_BITFIELD_XOR ",6}" + + "mfl_vendor{" U16_XOR "," MFL_SIZE "}" + "mfl_product{" U16_XOR "," MFL_SIZE "}" + "mfl_dev_lo{" U16_XOR "," MFL_SIZE "}" + "mfl_dev_hi{" U16_XOR "," MFL_SIZE "}" + + "mfl_dev_class{" U16_XOR "," MFL_SIZE "}" + "mfl_dev_subclass{" U16_XOR "," MFL_SIZE "}" + "mfl_dev_protocol{" U16_XOR "," MFL_SIZE "}" + "mfl_int_class{" U16_XOR "," MFL_SIZE "}" + + "mfl_int_subclass{" U16_XOR "," MFL_SIZE "}" + "mfl_int_protocol{" U16_XOR "," MFL_SIZE "}" + "unused{" U16_XOR "," MFL_SIZE "}" + "unused{" U16_XOR "," MFL_SIZE "}" + + "unused{" U16_XOR "," MFL_SIZE "}" + "unused{" U16_XOR "," MFL_SIZE "}" + "unused{" U16_XOR "," MFL_SIZE "}" + "unused{" U16_XOR "," MFL_SIZE "}" +}; +#endif diff --git a/sys/bus/u4b/usb_mbuf.c b/sys/bus/u4b/usb_mbuf.c new file mode 100644 index 0000000000..65b2a91f95 --- /dev/null +++ b/sys/bus/u4b/usb_mbuf.c @@ -0,0 +1,98 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/*------------------------------------------------------------------------* + * usb_alloc_mbufs - allocate mbufs to an usbd interface queue + * + * Returns: + * A pointer that should be passed to "free()" when the buffer(s) + * should be released. + *------------------------------------------------------------------------*/ +void * +usb_alloc_mbufs(struct malloc_type *type, struct usb_ifqueue *ifq, + usb_size_t block_size, uint16_t nblocks) +{ + struct usb_mbuf *m_ptr; + uint8_t *data_ptr; + void *free_ptr = NULL; + usb_size_t alloc_size; + + /* align data */ + block_size += ((-block_size) & (USB_HOST_ALIGN - 1)); + + if (nblocks && block_size) { + + alloc_size = (block_size + sizeof(struct usb_mbuf)) * nblocks; + + free_ptr = malloc(alloc_size, type, M_WAITOK | M_ZERO); + + if (free_ptr == NULL) { + goto done; + } + m_ptr = free_ptr; + data_ptr = (void *)(m_ptr + nblocks); + + while (nblocks--) { + + m_ptr->cur_data_ptr = + m_ptr->min_data_ptr = data_ptr; + + m_ptr->cur_data_len = + m_ptr->max_data_len = block_size; + + USB_IF_ENQUEUE(ifq, m_ptr); + + m_ptr++; + data_ptr += block_size; + } + } +done: + return (free_ptr); +} diff --git a/sys/bus/u4b/usb_mbuf.h b/sys/bus/u4b/usb_mbuf.h new file mode 100644 index 0000000000..3b331ffce6 --- /dev/null +++ b/sys/bus/u4b/usb_mbuf.h @@ -0,0 +1,90 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_MBUF_H_ +#define _USB_MBUF_H_ + +/* + * The following structure defines a minimum re-implementation of the + * mbuf system in the kernel. + */ +struct usb_mbuf { + uint8_t *cur_data_ptr; + uint8_t *min_data_ptr; + struct usb_mbuf *usb_nextpkt; + struct usb_mbuf *usb_next; + + usb_size_t cur_data_len; + usb_size_t max_data_len; + uint8_t last_packet:1; + uint8_t unused:7; +}; + +#define USB_IF_ENQUEUE(ifq, m) do { \ + (m)->usb_nextpkt = NULL; \ + if ((ifq)->ifq_tail == NULL) \ + (ifq)->ifq_head = (m); \ + else \ + (ifq)->ifq_tail->usb_nextpkt = (m); \ + (ifq)->ifq_tail = (m); \ + (ifq)->ifq_len++; \ + } while (0) + +#define USB_IF_DEQUEUE(ifq, m) do { \ + (m) = (ifq)->ifq_head; \ + if (m) { \ + if (((ifq)->ifq_head = (m)->usb_nextpkt) == NULL) { \ + (ifq)->ifq_tail = NULL; \ + } \ + (m)->usb_nextpkt = NULL; \ + (ifq)->ifq_len--; \ + } \ + } while (0) + +#define USB_IF_PREPEND(ifq, m) do { \ + (m)->usb_nextpkt = (ifq)->ifq_head; \ + if ((ifq)->ifq_tail == NULL) { \ + (ifq)->ifq_tail = (m); \ + } \ + (ifq)->ifq_head = (m); \ + (ifq)->ifq_len++; \ + } while (0) + +#define USB_IF_QFULL(ifq) ((ifq)->ifq_len >= (ifq)->ifq_maxlen) +#define USB_IF_QLEN(ifq) ((ifq)->ifq_len) +#define USB_IF_POLL(ifq, m) ((m) = (ifq)->ifq_head) + +#define USB_MBUF_RESET(m) do { \ + (m)->cur_data_ptr = (m)->min_data_ptr; \ + (m)->cur_data_len = (m)->max_data_len; \ + (m)->last_packet = 0; \ + } while (0) + +/* prototypes */ +void *usb_alloc_mbufs(struct malloc_type *type, struct usb_ifqueue *ifq, + usb_size_t block_size, uint16_t nblocks); + +#endif /* _USB_MBUF_H_ */ diff --git a/sys/bus/u4b/usb_msctest.c b/sys/bus/u4b/usb_msctest.c new file mode 100644 index 0000000000..a6b01b81ac --- /dev/null +++ b/sys/bus/u4b/usb_msctest.c @@ -0,0 +1,800 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008,2011 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * The following file contains code that will detect USB autoinstall + * disks. + * + * TODO: Potentially we could add code to automatically detect USB + * mass storage quirks for not supported SCSI commands! + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define USB_DEBUG_VAR usb_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + ST_COMMAND, + ST_DATA_RD, + ST_DATA_RD_CS, + ST_DATA_WR, + ST_DATA_WR_CS, + ST_STATUS, + ST_MAX, +}; + +enum { + DIR_IN, + DIR_OUT, + DIR_NONE, +}; + +#define SCSI_MAX_LEN 0x100 +#define SCSI_INQ_LEN 0x24 +#define SCSI_SENSE_LEN 0xFF + +static uint8_t scsi_test_unit_ready[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static uint8_t scsi_inquiry[] = { 0x12, 0x00, 0x00, 0x00, SCSI_INQ_LEN, 0x00 }; +static uint8_t scsi_rezero_init[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static uint8_t scsi_start_stop_unit[] = { 0x1b, 0x00, 0x00, 0x00, 0x02, 0x00 }; +static uint8_t scsi_ztestor_eject[] = { 0x85, 0x01, 0x01, 0x01, 0x18, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x00, 0x00 }; +static uint8_t scsi_cmotech_eject[] = { 0xff, 0x52, 0x44, 0x45, 0x56, 0x43, + 0x48, 0x47 }; +static uint8_t scsi_huawei_eject[] = { 0x11, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; +static uint8_t scsi_tct_eject[] = { 0x06, 0xf5, 0x04, 0x02, 0x52, 0x70 }; +static uint8_t scsi_sync_cache[] = { 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; +static uint8_t scsi_request_sense[] = { 0x03, 0x00, 0x00, 0x00, 0x12, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +#define BULK_SIZE 64 /* dummy */ +#define ERR_CSW_FAILED -1 + +/* Command Block Wrapper */ +struct bbb_cbw { + uDWord dCBWSignature; +#define CBWSIGNATURE 0x43425355 + uDWord dCBWTag; + uDWord dCBWDataTransferLength; + uByte bCBWFlags; +#define CBWFLAGS_OUT 0x00 +#define CBWFLAGS_IN 0x80 + uByte bCBWLUN; + uByte bCDBLength; +#define CBWCDBLENGTH 16 + uByte CBWCDB[CBWCDBLENGTH]; +} __packed; + +/* Command Status Wrapper */ +struct bbb_csw { + uDWord dCSWSignature; +#define CSWSIGNATURE 0x53425355 + uDWord dCSWTag; + uDWord dCSWDataResidue; + uByte bCSWStatus; +#define CSWSTATUS_GOOD 0x0 +#define CSWSTATUS_FAILED 0x1 +#define CSWSTATUS_PHASE 0x2 +} __packed; + +struct bbb_transfer { + struct mtx mtx; + struct cv cv; + struct bbb_cbw cbw; + struct bbb_csw csw; + + struct usb_xfer *xfer[ST_MAX]; + + uint8_t *data_ptr; + + usb_size_t data_len; /* bytes */ + usb_size_t data_rem; /* bytes */ + usb_timeout_t data_timeout; /* ms */ + usb_frlength_t actlen; /* bytes */ + + uint8_t cmd_len; /* bytes */ + uint8_t dir; + uint8_t lun; + uint8_t state; + uint8_t status_try; + int error; + + uint8_t buffer[SCSI_MAX_LEN] __aligned(4); +}; + +static usb_callback_t bbb_command_callback; +static usb_callback_t bbb_data_read_callback; +static usb_callback_t bbb_data_rd_cs_callback; +static usb_callback_t bbb_data_write_callback; +static usb_callback_t bbb_data_wr_cs_callback; +static usb_callback_t bbb_status_callback; + +static void bbb_done(struct bbb_transfer *, int); +static void bbb_transfer_start(struct bbb_transfer *, uint8_t); +static void bbb_data_clear_stall_callback(struct usb_xfer *, uint8_t, + uint8_t); +static int bbb_command_start(struct bbb_transfer *, uint8_t, uint8_t, + void *, size_t, void *, size_t, usb_timeout_t); +static struct bbb_transfer *bbb_attach(struct usb_device *, uint8_t); +static void bbb_detach(struct bbb_transfer *); + +static const struct usb_config bbb_config[ST_MAX] = { + + [ST_COMMAND] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = sizeof(struct bbb_cbw), + .flags = {.ext_buffer = 1,}, + .callback = &bbb_command_callback, + .timeout = 4 * USB_MS_HZ, /* 4 seconds */ + }, + + [ST_DATA_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = BULK_SIZE, + .flags = {.ext_buffer = 1,.proxy_buffer = 1,.short_xfer_ok = 1,}, + .callback = &bbb_data_read_callback, + .timeout = 4 * USB_MS_HZ, /* 4 seconds */ + }, + + [ST_DATA_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &bbb_data_rd_cs_callback, + .timeout = 1 * USB_MS_HZ, /* 1 second */ + }, + + [ST_DATA_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = BULK_SIZE, + .flags = {.ext_buffer = 1,.proxy_buffer = 1,}, + .callback = &bbb_data_write_callback, + .timeout = 4 * USB_MS_HZ, /* 4 seconds */ + }, + + [ST_DATA_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &bbb_data_wr_cs_callback, + .timeout = 1 * USB_MS_HZ, /* 1 second */ + }, + + [ST_STATUS] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = sizeof(struct bbb_csw), + .flags = {.ext_buffer = 1,.short_xfer_ok = 1,}, + .callback = &bbb_status_callback, + .timeout = 1 * USB_MS_HZ, /* 1 second */ + }, +}; + +static void +bbb_done(struct bbb_transfer *sc, int error) +{ + + sc->error = error; + sc->state = ST_COMMAND; + sc->status_try = 1; + cv_signal(&sc->cv); +} + +static void +bbb_transfer_start(struct bbb_transfer *sc, uint8_t xfer_index) +{ + sc->state = xfer_index; + usbd_transfer_start(sc->xfer[xfer_index]); +} + +static void +bbb_data_clear_stall_callback(struct usb_xfer *xfer, + uint8_t next_xfer, uint8_t stall_xfer) +{ + struct bbb_transfer *sc = usbd_xfer_softc(xfer); + + if (usbd_clear_stall_callback(xfer, sc->xfer[stall_xfer])) { + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + bbb_transfer_start(sc, next_xfer); + break; + default: + bbb_done(sc, USB_ERR_STALLED); + break; + } + } +} + +static void +bbb_command_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct bbb_transfer *sc = usbd_xfer_softc(xfer); + uint32_t tag; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + bbb_transfer_start + (sc, ((sc->dir == DIR_IN) ? ST_DATA_RD : + (sc->dir == DIR_OUT) ? ST_DATA_WR : + ST_STATUS)); + break; + + case USB_ST_SETUP: + sc->status_try = 0; + tag = UGETDW(sc->cbw.dCBWTag) + 1; + USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE); + USETDW(sc->cbw.dCBWTag, tag); + USETDW(sc->cbw.dCBWDataTransferLength, (uint32_t)sc->data_len); + sc->cbw.bCBWFlags = ((sc->dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT); + sc->cbw.bCBWLUN = sc->lun; + sc->cbw.bCDBLength = sc->cmd_len; + if (sc->cbw.bCDBLength > sizeof(sc->cbw.CBWCDB)) { + sc->cbw.bCDBLength = sizeof(sc->cbw.CBWCDB); + DPRINTFN(0, "Truncating long command\n"); + } + usbd_xfer_set_frame_data(xfer, 0, &sc->cbw, sizeof(sc->cbw)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + bbb_done(sc, error); + break; + } +} + +static void +bbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct bbb_transfer *sc = usbd_xfer_softc(xfer); + usb_frlength_t max_bulk = usbd_xfer_max_len(xfer); + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->data_rem -= actlen; + sc->data_ptr += actlen; + sc->actlen += actlen; + + if (actlen < sumlen) { + /* short transfer */ + sc->data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF("max_bulk=%d, data_rem=%d\n", + max_bulk, sc->data_rem); + + if (sc->data_rem == 0) { + bbb_transfer_start(sc, ST_STATUS); + break; + } + if (max_bulk > sc->data_rem) { + max_bulk = sc->data_rem; + } + usbd_xfer_set_timeout(xfer, sc->data_timeout); + usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) { + bbb_done(sc, error); + } else { + bbb_transfer_start(sc, ST_DATA_RD_CS); + } + break; + } +} + +static void +bbb_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error) +{ + bbb_data_clear_stall_callback(xfer, ST_STATUS, + ST_DATA_RD); +} + +static void +bbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct bbb_transfer *sc = usbd_xfer_softc(xfer); + usb_frlength_t max_bulk = usbd_xfer_max_len(xfer); + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->data_rem -= actlen; + sc->data_ptr += actlen; + sc->actlen += actlen; + + if (actlen < sumlen) { + /* short transfer */ + sc->data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF("max_bulk=%d, data_rem=%d\n", + max_bulk, sc->data_rem); + + if (sc->data_rem == 0) { + bbb_transfer_start(sc, ST_STATUS); + return; + } + if (max_bulk > sc->data_rem) { + max_bulk = sc->data_rem; + } + usbd_xfer_set_timeout(xfer, sc->data_timeout); + usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) { + bbb_done(sc, error); + } else { + bbb_transfer_start(sc, ST_DATA_WR_CS); + } + return; + + } +} + +static void +bbb_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error) +{ + bbb_data_clear_stall_callback(xfer, ST_STATUS, + ST_DATA_WR); +} + +static void +bbb_status_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct bbb_transfer *sc = usbd_xfer_softc(xfer); + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + /* very simple status check */ + + if (actlen < sizeof(sc->csw)) { + bbb_done(sc, USB_ERR_SHORT_XFER); + } else if (sc->csw.bCSWStatus == CSWSTATUS_GOOD) { + bbb_done(sc, 0); /* success */ + } else { + bbb_done(sc, ERR_CSW_FAILED); /* error */ + } + break; + + case USB_ST_SETUP: + usbd_xfer_set_frame_data(xfer, 0, &sc->csw, sizeof(sc->csw)); + usbd_transfer_submit(xfer); + break; + + default: + DPRINTF("Failed to read CSW: %s, try %d\n", + usbd_errstr(error), sc->status_try); + + if (error == USB_ERR_CANCELLED || sc->status_try) { + bbb_done(sc, error); + } else { + sc->status_try = 1; + bbb_transfer_start(sc, ST_DATA_RD_CS); + } + break; + } +} + +/*------------------------------------------------------------------------* + * bbb_command_start - execute a SCSI command synchronously + * + * Return values + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +bbb_command_start(struct bbb_transfer *sc, uint8_t dir, uint8_t lun, + void *data_ptr, size_t data_len, void *cmd_ptr, size_t cmd_len, + usb_timeout_t data_timeout) +{ + sc->lun = lun; + sc->dir = data_len ? dir : DIR_NONE; + sc->data_ptr = data_ptr; + sc->data_len = data_len; + sc->data_rem = data_len; + sc->data_timeout = (data_timeout + USB_MS_HZ); + sc->actlen = 0; + sc->cmd_len = cmd_len; + memset(&sc->cbw.CBWCDB, 0, sizeof(sc->cbw.CBWCDB)); + memcpy(&sc->cbw.CBWCDB, cmd_ptr, cmd_len); + DPRINTFN(1, "SCSI cmd = %*D\n", (int)cmd_len, (char *)sc->cbw.CBWCDB, ":"); + + mtx_lock(&sc->mtx); + usbd_transfer_start(sc->xfer[sc->state]); + + while (usbd_transfer_pending(sc->xfer[sc->state])) { + cv_wait(&sc->cv, &sc->mtx); + } + mtx_unlock(&sc->mtx); + return (sc->error); +} + +static struct bbb_transfer * +bbb_attach(struct usb_device *udev, uint8_t iface_index) +{ + struct usb_interface *iface; + struct usb_interface_descriptor *id; + struct bbb_transfer *sc; + usb_error_t err; + uint8_t do_unlock; + + /* automatic locking */ + if (usbd_enum_is_locked(udev)) { + do_unlock = 0; + } else { + do_unlock = 1; + usbd_enum_lock(udev); + } + + /* + * Make sure any driver which is hooked up to this interface, + * like umass is gone: + */ + usb_detach_device(udev, iface_index, 0); + + if (do_unlock) + usbd_enum_unlock(udev); + + iface = usbd_get_iface(udev, iface_index); + if (iface == NULL) + return (NULL); + + id = iface->idesc; + if (id == NULL || id->bInterfaceClass != UICLASS_MASS) + return (NULL); + + switch (id->bInterfaceSubClass) { + case UISUBCLASS_SCSI: + case UISUBCLASS_UFI: + case UISUBCLASS_SFF8020I: + case UISUBCLASS_SFF8070I: + break; + default: + return (NULL); + } + + switch (id->bInterfaceProtocol) { + case UIPROTO_MASS_BBB_OLD: + case UIPROTO_MASS_BBB: + break; + default: + return (NULL); + } + + sc = malloc(sizeof(*sc), M_USB, M_WAITOK | M_ZERO); + mtx_init(&sc->mtx, "USB autoinstall", NULL, MTX_DEF); + cv_init(&sc->cv, "WBBB"); + + err = usbd_transfer_setup(udev, &iface_index, sc->xfer, bbb_config, + ST_MAX, sc, &sc->mtx); + if (err) { + bbb_detach(sc); + return (NULL); + } + return (sc); +} + +static void +bbb_detach(struct bbb_transfer *sc) +{ + usbd_transfer_unsetup(sc->xfer, ST_MAX); + mtx_destroy(&sc->mtx); + cv_destroy(&sc->cv); + free(sc, M_USB); +} + +/*------------------------------------------------------------------------* + * usb_iface_is_cdrom + * + * Return values: + * 1: This interface is an auto install disk (CD-ROM) + * 0: Not an auto install disk. + *------------------------------------------------------------------------*/ +int +usb_iface_is_cdrom(struct usb_device *udev, uint8_t iface_index) +{ + struct bbb_transfer *sc; + uint8_t timeout; + uint8_t is_cdrom; + uint8_t sid_type; + int err; + + sc = bbb_attach(udev, iface_index); + if (sc == NULL) + return (0); + + is_cdrom = 0; + timeout = 4; /* tries */ + while (--timeout) { + err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, + SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry), + USB_MS_HZ); + + if (err == 0 && sc->actlen > 0) { + sid_type = sc->buffer[0] & 0x1F; + if (sid_type == 0x05) + is_cdrom = 1; + break; + } else if (err != ERR_CSW_FAILED) + break; /* non retryable error */ + usb_pause_mtx(NULL, hz); + } + bbb_detach(sc); + return (is_cdrom); +} + +static uint8_t +usb_msc_get_max_lun(struct usb_device *udev, uint8_t iface_index) +{ + struct usb_device_request req; + usb_error_t err; + uint8_t buf = 0; + + + /* The Get Max Lun command is a class-specific request. */ + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = 0xFE; /* GET_MAX_LUN */ + USETW(req.wValue, 0); + req.wIndex[0] = iface_index; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + + err = usbd_do_request(udev, NULL, &req, &buf); + if (err) + buf = 0; + + return (buf); +} + +usb_error_t +usb_msc_auto_quirk(struct usb_device *udev, uint8_t iface_index) +{ + struct bbb_transfer *sc; + uint8_t timeout; + uint8_t is_no_direct; + uint8_t sid_type; + int err; + + sc = bbb_attach(udev, iface_index); + if (sc == NULL) + return (0); + + /* + * Some devices need a delay after that the configuration + * value is set to function properly: + */ + usb_pause_mtx(NULL, hz); + + if (usb_msc_get_max_lun(udev, iface_index) == 0) { + DPRINTF("Device has only got one LUN.\n"); + usbd_add_dynamic_quirk(udev, UQ_MSC_NO_GETMAXLUN); + } + + is_no_direct = 1; + for (timeout = 4; timeout; timeout--) { + err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, + SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry), + USB_MS_HZ); + + if (err == 0 && sc->actlen > 0) { + sid_type = sc->buffer[0] & 0x1F; + if (sid_type == 0x00) + is_no_direct = 0; + break; + } else if (err != ERR_CSW_FAILED) + break; /* non retryable error */ + usb_pause_mtx(NULL, hz); + } + + if (is_no_direct) { + DPRINTF("Device is not direct access.\n"); + goto done; + } + + err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, + &scsi_test_unit_ready, sizeof(scsi_test_unit_ready), + USB_MS_HZ); + + if (err != 0) { + + if (err != ERR_CSW_FAILED) + goto error; + } + + err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, + &scsi_sync_cache, sizeof(scsi_sync_cache), + USB_MS_HZ); + + if (err != 0) { + + if (err != ERR_CSW_FAILED) + goto error; + + DPRINTF("Device doesn't handle synchronize cache\n"); + + usbd_add_dynamic_quirk(udev, UQ_MSC_NO_SYNC_CACHE); + } + + /* clear sense status of any failed commands on the device */ + + err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, + SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry), + USB_MS_HZ); + + DPRINTF("Inquiry = %d\n", err); + + if (err != 0) { + + if (err != ERR_CSW_FAILED) + goto error; + } + + err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, + SCSI_SENSE_LEN, &scsi_request_sense, + sizeof(scsi_request_sense), USB_MS_HZ); + + DPRINTF("Request sense = %d\n", err); + + if (err != 0) { + + if (err != ERR_CSW_FAILED) + goto error; + } + +done: + bbb_detach(sc); + return (0); + +error: + bbb_detach(sc); + + DPRINTF("Device did not respond, enabling all quirks\n"); + + usbd_add_dynamic_quirk(udev, UQ_MSC_NO_SYNC_CACHE); + usbd_add_dynamic_quirk(udev, UQ_MSC_NO_TEST_UNIT_READY); + + /* Need to re-enumerate the device */ + usbd_req_re_enumerate(udev, NULL); + + return (USB_ERR_STALLED); +} + +usb_error_t +usb_msc_eject(struct usb_device *udev, uint8_t iface_index, int method) +{ + struct bbb_transfer *sc; + usb_error_t err; + + sc = bbb_attach(udev, iface_index); + if (sc == NULL) + return (USB_ERR_INVAL); + + err = 0; + switch (method) { + case MSC_EJECT_STOPUNIT: + err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, + &scsi_test_unit_ready, sizeof(scsi_test_unit_ready), + USB_MS_HZ); + DPRINTF("Test unit ready status: %s\n", usbd_errstr(err)); + err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, + &scsi_start_stop_unit, sizeof(scsi_start_stop_unit), + USB_MS_HZ); + break; + case MSC_EJECT_REZERO: + err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, + &scsi_rezero_init, sizeof(scsi_rezero_init), + USB_MS_HZ); + break; + case MSC_EJECT_ZTESTOR: + err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, + &scsi_ztestor_eject, sizeof(scsi_ztestor_eject), + USB_MS_HZ); + break; + case MSC_EJECT_CMOTECH: + err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, + &scsi_cmotech_eject, sizeof(scsi_cmotech_eject), + USB_MS_HZ); + break; + case MSC_EJECT_HUAWEI: + err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, + &scsi_huawei_eject, sizeof(scsi_huawei_eject), + USB_MS_HZ); + break; + case MSC_EJECT_TCT: + /* + * TCTMobile needs DIR_IN flag. To get it, we + * supply a dummy data with the command. + */ + err = bbb_command_start(sc, DIR_IN, 0, &sc->buffer, + sizeof(sc->buffer), &scsi_tct_eject, + sizeof(scsi_tct_eject), USB_MS_HZ); + break; + default: + printf("usb_msc_eject: unknown eject method (%d)\n", method); + break; + } + DPRINTF("Eject CD command status: %s\n", usbd_errstr(err)); + + bbb_detach(sc); + return (0); +} diff --git a/sys/bus/u4b/usb_msctest.h b/sys/bus/u4b/usb_msctest.h new file mode 100644 index 0000000000..e4a717feb3 --- /dev/null +++ b/sys/bus/u4b/usb_msctest.h @@ -0,0 +1,46 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_MSCTEST_H_ +#define _USB_MSCTEST_H_ + +enum { + MSC_EJECT_STOPUNIT, + MSC_EJECT_REZERO, + MSC_EJECT_ZTESTOR, + MSC_EJECT_CMOTECH, + MSC_EJECT_HUAWEI, + MSC_EJECT_TCT, +}; + +int usb_iface_is_cdrom(struct usb_device *udev, + uint8_t iface_index); +usb_error_t usb_msc_eject(struct usb_device *udev, + uint8_t iface_index, int method); +usb_error_t usb_msc_auto_quirk(struct usb_device *udev, + uint8_t iface_index); + +#endif /* _USB_MSCTEST_H_ */ diff --git a/sys/bus/u4b/usb_parse.c b/sys/bus/u4b/usb_parse.c new file mode 100644 index 0000000000..66ab5fa749 --- /dev/null +++ b/sys/bus/u4b/usb_parse.c @@ -0,0 +1,288 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +/*------------------------------------------------------------------------* + * usb_desc_foreach + * + * This function is the safe way to iterate across the USB config + * descriptor. It contains several checks against invalid + * descriptors. If the "desc" argument passed to this function is + * "NULL" the first descriptor, if any, will be returned. + * + * Return values: + * NULL: End of descriptors + * Else: Next descriptor after "desc" + *------------------------------------------------------------------------*/ +struct usb_descriptor * +usb_desc_foreach(struct usb_config_descriptor *cd, + struct usb_descriptor *_desc) +{ + uint8_t *desc_next; + uint8_t *start; + uint8_t *end; + uint8_t *desc; + + /* be NULL safe */ + if (cd == NULL) + return (NULL); + + /* We assume that the "wTotalLength" has been checked. */ + start = (uint8_t *)cd; + end = start + UGETW(cd->wTotalLength); + desc = (uint8_t *)_desc; + + /* Get start of next USB descriptor. */ + if (desc == NULL) + desc = start; + else + desc = desc + desc[0]; + + /* Check that the next USB descriptor is within the range. */ + if ((desc < start) || (desc >= end)) + return (NULL); /* out of range, or EOD */ + + /* Check that the second next USB descriptor is within range. */ + desc_next = desc + desc[0]; + if ((desc_next < start) || (desc_next > end)) + return (NULL); /* out of range */ + + /* Check minimum descriptor length. */ + if (desc[0] < 3) + return (NULL); /* too short descriptor */ + + /* Return start of next descriptor. */ + return ((struct usb_descriptor *)desc); +} + +/*------------------------------------------------------------------------* + * usb_idesc_foreach + * + * This function will iterate the interface descriptors in the config + * descriptor. The parse state structure should be zeroed before + * calling this function the first time. + * + * Return values: + * NULL: End of descriptors + * Else: A valid interface descriptor + *------------------------------------------------------------------------*/ +struct usb_interface_descriptor * +usb_idesc_foreach(struct usb_config_descriptor *cd, + struct usb_idesc_parse_state *ps) +{ + struct usb_interface_descriptor *id; + uint8_t new_iface; + + /* retrieve current descriptor */ + id = (struct usb_interface_descriptor *)ps->desc; + /* default is to start a new interface */ + new_iface = 1; + + while (1) { + id = (struct usb_interface_descriptor *) + usb_desc_foreach(cd, (struct usb_descriptor *)id); + if (id == NULL) + break; + if ((id->bDescriptorType == UDESC_INTERFACE) && + (id->bLength >= sizeof(*id))) { + if (ps->iface_no_last == id->bInterfaceNumber) + new_iface = 0; + ps->iface_no_last = id->bInterfaceNumber; + break; + } + } + + if (ps->desc == NULL) { + /* first time */ + } else if (new_iface) { + /* new interface */ + ps->iface_index ++; + ps->iface_index_alt = 0; + } else { + /* new alternate interface */ + ps->iface_index_alt ++; + } + + /* store and return current descriptor */ + ps->desc = (struct usb_descriptor *)id; + return (id); +} + +/*------------------------------------------------------------------------* + * usb_edesc_foreach + * + * This function will iterate all the endpoint descriptors within an + * interface descriptor. Starting value for the "ped" argument should + * be a valid interface descriptor. + * + * Return values: + * NULL: End of descriptors + * Else: A valid endpoint descriptor + *------------------------------------------------------------------------*/ +struct usb_endpoint_descriptor * +usb_edesc_foreach(struct usb_config_descriptor *cd, + struct usb_endpoint_descriptor *ped) +{ + struct usb_descriptor *desc; + + desc = ((struct usb_descriptor *)ped); + + while ((desc = usb_desc_foreach(cd, desc))) { + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + if (desc->bDescriptorType == UDESC_ENDPOINT) { + if (desc->bLength < sizeof(*ped)) { + /* endpoint descriptor is invalid */ + break; + } + return ((struct usb_endpoint_descriptor *)desc); + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_ed_comp_foreach + * + * This function will iterate all the endpoint companion descriptors + * within an endpoint descriptor in an interface descriptor. Starting + * value for the "ped" argument should be a valid endpoint companion + * descriptor. + * + * Return values: + * NULL: End of descriptors + * Else: A valid endpoint companion descriptor + *------------------------------------------------------------------------*/ +struct usb_endpoint_ss_comp_descriptor * +usb_ed_comp_foreach(struct usb_config_descriptor *cd, + struct usb_endpoint_ss_comp_descriptor *ped) +{ + struct usb_descriptor *desc; + + desc = ((struct usb_descriptor *)ped); + + while ((desc = usb_desc_foreach(cd, desc))) { + if (desc->bDescriptorType == UDESC_INTERFACE) + break; + if (desc->bDescriptorType == UDESC_ENDPOINT) + break; + if (desc->bDescriptorType == UDESC_ENDPOINT_SS_COMP) { + if (desc->bLength < sizeof(*ped)) { + /* endpoint companion descriptor is invalid */ + break; + } + return ((struct usb_endpoint_ss_comp_descriptor *)desc); + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usbd_get_no_descriptors + * + * This function will count the total number of descriptors in the + * configuration descriptor of type "type". + *------------------------------------------------------------------------*/ +uint8_t +usbd_get_no_descriptors(struct usb_config_descriptor *cd, uint8_t type) +{ + struct usb_descriptor *desc = NULL; + uint8_t count = 0; + + while ((desc = usb_desc_foreach(cd, desc))) { + if (desc->bDescriptorType == type) { + count++; + if (count == 0xFF) + break; /* crazy */ + } + } + return (count); +} + +/*------------------------------------------------------------------------* + * usbd_get_no_alts + * + * Return value: + * Number of alternate settings for the given interface descriptor + * pointer. If the USB descriptor is corrupt, the returned value can + * be greater than the actual number of alternate settings. + *------------------------------------------------------------------------*/ +uint8_t +usbd_get_no_alts(struct usb_config_descriptor *cd, + struct usb_interface_descriptor *id) +{ + struct usb_descriptor *desc; + uint8_t n; + uint8_t ifaceno; + + /* Reset interface count */ + + n = 0; + + /* Get the interface number */ + + ifaceno = id->bInterfaceNumber; + + /* Iterate all the USB descriptors */ + + desc = NULL; + while ((desc = usb_desc_foreach(cd, desc))) { + if ((desc->bDescriptorType == UDESC_INTERFACE) && + (desc->bLength >= sizeof(*id))) { + id = (struct usb_interface_descriptor *)desc; + if (id->bInterfaceNumber == ifaceno) { + n++; + if (n == 0xFF) + break; /* crazy */ + } + } + } + return (n); +} diff --git a/sys/bus/u4b/usb_pci.h b/sys/bus/u4b/usb_pci.h new file mode 100644 index 0000000000..071dfc321a --- /dev/null +++ b/sys/bus/u4b/usb_pci.h @@ -0,0 +1,39 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_PCI_H_ +#define _USB_PCI_H_ + +/* + * We don't want the following files included everywhere, that's why + * they are in a separate file. + */ +#include +#include + +#include + +#endif /* _USB_PCI_H_ */ diff --git a/sys/bus/u4b/usb_pf.c b/sys/bus/u4b/usb_pf.c new file mode 100644 index 0000000000..6d65439366 --- /dev/null +++ b/sys/bus/u4b/usb_pf.c @@ -0,0 +1,392 @@ +/*- + * Copyright (c) 1990, 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from the Stanford/CMU enet packet filter, + * (net/enet.c) distributed as part of 4.3BSD, and code contributed + * to Berkeley by Steven McCanne and Van Jacobson both of Lawrence + * Berkeley Laboratory. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int usb_no_pf; + +SYSCTL_INT(_hw_usb, OID_AUTO, no_pf, CTLFLAG_RW, + &usb_no_pf, 0, "Set to disable USB packet filtering"); + +TUNABLE_INT("hw.usb.no_pf", &usb_no_pf); + +void +usbpf_attach(struct usb_bus *ubus) +{ + struct ifnet *ifp; + + if (usb_no_pf != 0) { + ubus->ifp = NULL; + return; + } + + ifp = ubus->ifp = if_alloc(IFT_USB); + if (ifp == NULL) { + device_printf(ubus->parent, "usbpf: Could not allocate " + "instance\n"); + return; + } + + if_initname(ifp, "usbus", device_get_unit(ubus->bdev)); + ifp->if_flags = IFF_CANTCONFIG; + if_attach(ifp); + if_up(ifp); + + /* + * XXX According to the specification of DLT_USB, it indicates + * packets beginning with USB setup header. But not sure all + * packets would be. + */ + bpfattach(ifp, DLT_USB, USBPF_HDR_LEN); + + if (bootverbose) + device_printf(ubus->parent, "usbpf: Attached\n"); +} + +void +usbpf_detach(struct usb_bus *ubus) +{ + struct ifnet *ifp = ubus->ifp; + + if (ifp != NULL) { + bpfdetach(ifp); + if_down(ifp); + if_detach(ifp); + if_free(ifp); + } + ubus->ifp = NULL; +} + +static uint32_t +usbpf_aggregate_xferflags(struct usb_xfer_flags *flags) +{ + uint32_t val = 0; + + if (flags->force_short_xfer == 1) + val |= USBPF_FLAG_FORCE_SHORT_XFER; + if (flags->short_xfer_ok == 1) + val |= USBPF_FLAG_SHORT_XFER_OK; + if (flags->short_frames_ok == 1) + val |= USBPF_FLAG_SHORT_FRAMES_OK; + if (flags->pipe_bof == 1) + val |= USBPF_FLAG_PIPE_BOF; + if (flags->proxy_buffer == 1) + val |= USBPF_FLAG_PROXY_BUFFER; + if (flags->ext_buffer == 1) + val |= USBPF_FLAG_EXT_BUFFER; + if (flags->manual_status == 1) + val |= USBPF_FLAG_MANUAL_STATUS; + if (flags->no_pipe_ok == 1) + val |= USBPF_FLAG_NO_PIPE_OK; + if (flags->stall_pipe == 1) + val |= USBPF_FLAG_STALL_PIPE; + return (val); +} + +static uint32_t +usbpf_aggregate_status(struct usb_xfer_flags_int *flags) +{ + uint32_t val = 0; + + if (flags->open == 1) + val |= USBPF_STATUS_OPEN; + if (flags->transferring == 1) + val |= USBPF_STATUS_TRANSFERRING; + if (flags->did_dma_delay == 1) + val |= USBPF_STATUS_DID_DMA_DELAY; + if (flags->did_close == 1) + val |= USBPF_STATUS_DID_CLOSE; + if (flags->draining == 1) + val |= USBPF_STATUS_DRAINING; + if (flags->started == 1) + val |= USBPF_STATUS_STARTED; + if (flags->bandwidth_reclaimed == 1) + val |= USBPF_STATUS_BW_RECLAIMED; + if (flags->control_xfr == 1) + val |= USBPF_STATUS_CONTROL_XFR; + if (flags->control_hdr == 1) + val |= USBPF_STATUS_CONTROL_HDR; + if (flags->control_act == 1) + val |= USBPF_STATUS_CONTROL_ACT; + if (flags->control_stall == 1) + val |= USBPF_STATUS_CONTROL_STALL; + if (flags->short_frames_ok == 1) + val |= USBPF_STATUS_SHORT_FRAMES_OK; + if (flags->short_xfer_ok == 1) + val |= USBPF_STATUS_SHORT_XFER_OK; +#if USB_HAVE_BUSDMA + if (flags->bdma_enable == 1) + val |= USBPF_STATUS_BDMA_ENABLE; + if (flags->bdma_no_post_sync == 1) + val |= USBPF_STATUS_BDMA_NO_POST_SYNC; + if (flags->bdma_setup == 1) + val |= USBPF_STATUS_BDMA_SETUP; +#endif + if (flags->isochronous_xfr == 1) + val |= USBPF_STATUS_ISOCHRONOUS_XFR; + if (flags->curr_dma_set == 1) + val |= USBPF_STATUS_CURR_DMA_SET; + if (flags->can_cancel_immed == 1) + val |= USBPF_STATUS_CAN_CANCEL_IMMED; + if (flags->doing_callback == 1) + val |= USBPF_STATUS_DOING_CALLBACK; + + return (val); +} + +static int +usbpf_xfer_frame_is_read(struct usb_xfer *xfer, uint32_t frame) +{ + int isread; + + if ((frame == 0) && (xfer->flags_int.control_xfr != 0) && + (xfer->flags_int.control_hdr != 0)) { + /* special case */ + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + /* The device controller writes to memory */ + isread = 1; + } else { + /* The host controller reads from memory */ + isread = 0; + } + } else { + isread = USB_GET_DATA_ISREAD(xfer); + } + return (isread); +} + +static uint32_t +usbpf_xfer_precompute_size(struct usb_xfer *xfer, int type) +{ + uint32_t totlen; + uint32_t x; + uint32_t nframes; + + if (type == USBPF_XFERTAP_SUBMIT) + nframes = xfer->nframes; + else + nframes = xfer->aframes; + + totlen = USBPF_HDR_LEN + (USBPF_FRAME_HDR_LEN * nframes); + + /* precompute all trace lengths */ + for (x = 0; x != nframes; x++) { + if (usbpf_xfer_frame_is_read(xfer, x)) { + if (type != USBPF_XFERTAP_SUBMIT) { + totlen += USBPF_FRAME_ALIGN( + xfer->frlengths[x]); + } + } else { + if (type == USBPF_XFERTAP_SUBMIT) { + totlen += USBPF_FRAME_ALIGN( + xfer->frlengths[x]); + } + } + } + return (totlen); +} + +void +usbpf_xfertap(struct usb_xfer *xfer, int type) +{ + struct usb_bus *bus; + struct usbpf_pkthdr *up; + struct usbpf_framehdr *uf; + usb_frlength_t offset; + uint32_t totlen; + uint32_t frame; + uint32_t temp; + uint32_t nframes; + uint32_t x; + uint8_t *buf; + uint8_t *ptr; + + bus = xfer->xroot->bus; + + /* sanity checks */ + if (usb_no_pf != 0) + return; + if (bus->ifp == NULL) + return; + if (!bpf_peers_present(bus->ifp->if_bpf)) + return; + + totlen = usbpf_xfer_precompute_size(xfer, type); + + if (type == USBPF_XFERTAP_SUBMIT) + nframes = xfer->nframes; + else + nframes = xfer->aframes; + + /* + * XXX TODO XXX + * + * When BPF supports it we could pass a fragmented array of + * buffers avoiding the data copy operation here. + */ + buf = ptr = malloc(totlen, M_TEMP, M_NOWAIT); + if (buf == NULL) { + device_printf(bus->parent, "usbpf: Out of memory\n"); + return; + } + + up = (struct usbpf_pkthdr *)ptr; + ptr += USBPF_HDR_LEN; + + /* fill out header */ + temp = device_get_unit(bus->bdev); + up->up_totlen = htole32(totlen); + up->up_busunit = htole32(temp); + up->up_address = xfer->xroot->udev->device_index; + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) + up->up_mode = USBPF_MODE_DEVICE; + else + up->up_mode = USBPF_MODE_HOST; + up->up_type = type; + up->up_xfertype = xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE; + temp = usbpf_aggregate_xferflags(&xfer->flags); + up->up_flags = htole32(temp); + temp = usbpf_aggregate_status(&xfer->flags_int); + up->up_status = htole32(temp); + temp = xfer->error; + up->up_error = htole32(temp); + temp = xfer->interval; + up->up_interval = htole32(temp); + up->up_frames = htole32(nframes); + temp = xfer->max_packet_size; + up->up_packet_size = htole32(temp); + temp = xfer->max_packet_count; + up->up_packet_count = htole32(temp); + temp = xfer->endpointno; + up->up_endpoint = htole32(temp); + up->up_speed = xfer->xroot->udev->speed; + + /* clear reserved area */ + memset(up->up_reserved, 0, sizeof(up->up_reserved)); + + /* init offset and frame */ + offset = 0; + frame = 0; + + /* iterate all the USB frames and copy data, if any */ + for (x = 0; x != nframes; x++) { + uint32_t length; + int isread; + + /* get length */ + length = xfer->frlengths[x]; + + /* get frame header pointer */ + uf = (struct usbpf_framehdr *)ptr; + ptr += USBPF_FRAME_HDR_LEN; + + /* fill out packet header */ + uf->length = htole32(length); + uf->flags = 0; + + /* get information about data read/write */ + isread = usbpf_xfer_frame_is_read(xfer, x); + + /* check if we need to copy any data */ + if (isread) { + if (type == USBPF_XFERTAP_SUBMIT) + length = 0; + else { + uf->flags |= htole32( + USBPF_FRAMEFLAG_DATA_FOLLOWS); + } + } else { + if (type != USBPF_XFERTAP_SUBMIT) + length = 0; + else { + uf->flags |= htole32( + USBPF_FRAMEFLAG_DATA_FOLLOWS); + } + } + + /* check if data is read direction */ + if (isread) + uf->flags |= htole32(USBPF_FRAMEFLAG_READ); + + /* copy USB data, if any */ + if (length != 0) { + /* copy data */ + usbd_copy_out(&xfer->frbuffers[frame], + offset, ptr, length); + + /* align length */ + temp = USBPF_FRAME_ALIGN(length); + + /* zero pad */ + if (temp != length) + memset(ptr + length, 0, temp - length); + + ptr += temp; + } + + if (xfer->flags_int.isochronous_xfr) { + offset += usbd_xfer_old_frame_length(xfer, x); + } else { + frame ++; + } + } + + bpf_tap(bus->ifp->if_bpf, buf, totlen); + + free(buf, M_TEMP); +} diff --git a/sys/bus/u4b/usb_pf.h b/sys/bus/u4b/usb_pf.h new file mode 100644 index 0000000000..9d51e98c05 --- /dev/null +++ b/sys/bus/u4b/usb_pf.h @@ -0,0 +1,120 @@ +/*- + * Copyright (c) 1990, 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from the Stanford/CMU enet packet filter, + * (net/enet.c) distributed as part of 4.3BSD, and code contributed + * to Berkeley by Steven McCanne and Van Jacobson both of Lawrence + * Berkeley Laboratory. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _DEV_USB_PF_H +#define _DEV_USB_PF_H + +struct usbpf_pkthdr { + uint32_t up_totlen; /* Total length including all headers */ + uint32_t up_busunit; /* Host controller unit number */ + uint8_t up_address; /* USB device index */ + uint8_t up_mode; /* Mode of transfer */ +#define USBPF_MODE_HOST 0 +#define USBPF_MODE_DEVICE 1 + uint8_t up_type; /* points SUBMIT / DONE */ + uint8_t up_xfertype; /* Transfer type, see USB2.0 spec. */ + uint32_t up_flags; /* Transfer flags */ +#define USBPF_FLAG_FORCE_SHORT_XFER (1 << 0) +#define USBPF_FLAG_SHORT_XFER_OK (1 << 1) +#define USBPF_FLAG_SHORT_FRAMES_OK (1 << 2) +#define USBPF_FLAG_PIPE_BOF (1 << 3) +#define USBPF_FLAG_PROXY_BUFFER (1 << 4) +#define USBPF_FLAG_EXT_BUFFER (1 << 5) +#define USBPF_FLAG_MANUAL_STATUS (1 << 6) +#define USBPF_FLAG_NO_PIPE_OK (1 << 7) +#define USBPF_FLAG_STALL_PIPE (1 << 8) + uint32_t up_status; /* Transfer status */ +#define USBPF_STATUS_OPEN (1 << 0) +#define USBPF_STATUS_TRANSFERRING (1 << 1) +#define USBPF_STATUS_DID_DMA_DELAY (1 << 2) +#define USBPF_STATUS_DID_CLOSE (1 << 3) +#define USBPF_STATUS_DRAINING (1 << 4) +#define USBPF_STATUS_STARTED (1 << 5) +#define USBPF_STATUS_BW_RECLAIMED (1 << 6) +#define USBPF_STATUS_CONTROL_XFR (1 << 7) +#define USBPF_STATUS_CONTROL_HDR (1 << 8) +#define USBPF_STATUS_CONTROL_ACT (1 << 9) +#define USBPF_STATUS_CONTROL_STALL (1 << 10) +#define USBPF_STATUS_SHORT_FRAMES_OK (1 << 11) +#define USBPF_STATUS_SHORT_XFER_OK (1 << 12) +#define USBPF_STATUS_BDMA_ENABLE (1 << 13) +#define USBPF_STATUS_BDMA_NO_POST_SYNC (1 << 14) +#define USBPF_STATUS_BDMA_SETUP (1 << 15) +#define USBPF_STATUS_ISOCHRONOUS_XFR (1 << 16) +#define USBPF_STATUS_CURR_DMA_SET (1 << 17) +#define USBPF_STATUS_CAN_CANCEL_IMMED (1 << 18) +#define USBPF_STATUS_DOING_CALLBACK (1 << 19) + uint32_t up_error; /* USB error, see USB_ERR_XXX */ + uint32_t up_interval; /* For interrupt and isoc (ms) */ + uint32_t up_frames; /* Number of following frames */ + uint32_t up_packet_size; /* Packet size used */ + uint32_t up_packet_count; /* Packet count used */ + uint32_t up_endpoint; /* USB endpoint / stream ID */ + uint8_t up_speed; /* USB speed, see USB_SPEED_XXX */ + /* sizeof(struct usbpf_pkthdr) == 128 bytes */ + uint8_t up_reserved[83]; +}; + +struct usbpf_framehdr { + /* + * The frame length field excludes length of frame header and + * any alignment. + */ + uint32_t length; +#define USBPF_FRAME_ALIGN(x) (((x) + 3) & ~3) + uint32_t flags; +#define USBPF_FRAMEFLAG_READ (1 << 0) +#define USBPF_FRAMEFLAG_DATA_FOLLOWS (1 << 1) +}; + +#define USBPF_HDR_LEN 128 /* bytes */ +#define USBPF_FRAME_HDR_LEN 8 /* bytes */ + +extern uint8_t usbpf_pkthdr_size_ok[ + (sizeof(struct usbpf_pkthdr) == USBPF_HDR_LEN) ? 1 : -1]; +extern uint8_t usbpf_framehdr_size_ok[ + (sizeof(struct usbpf_framehdr) == USBPF_FRAME_HDR_LEN) ? 1 : -1]; + +#define USBPF_XFERTAP_SUBMIT 0 +#define USBPF_XFERTAP_DONE 1 + +#ifdef _KERNEL +void usbpf_attach(struct usb_bus *); +void usbpf_detach(struct usb_bus *); +void usbpf_xfertap(struct usb_xfer *, int); +#endif + +#endif diff --git a/sys/bus/u4b/usb_process.c b/sys/bus/u4b/usb_process.c new file mode 100644 index 0000000000..ab579f2aa1 --- /dev/null +++ b/sys/bus/u4b/usb_process.c @@ -0,0 +1,500 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define USB_DEBUG_VAR usb_proc_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if (__FreeBSD_version < 700000) +#define thread_lock(td) mtx_lock_spin(&sched_lock) +#define thread_unlock(td) mtx_unlock_spin(&sched_lock) +#endif + +#if (__FreeBSD_version >= 800000) +static struct proc *usbproc; +static int usb_pcount; +#define USB_THREAD_CREATE(f, s, p, ...) \ + kproc_kthread_add((f), (s), &usbproc, (p), RFHIGHPID, \ + 0, "usb", __VA_ARGS__) +#if (__FreeBSD_version >= 900000) +#define USB_THREAD_SUSPEND_CHECK() kthread_suspend_check() +#else +#define USB_THREAD_SUSPEND_CHECK() kthread_suspend_check(curthread) +#endif +#define USB_THREAD_SUSPEND(p) kthread_suspend(p,0) +#define USB_THREAD_EXIT(err) kthread_exit() +#else +#define USB_THREAD_CREATE(f, s, p, ...) \ + kthread_create((f), (s), (p), RFHIGHPID, 0, __VA_ARGS__) +#define USB_THREAD_SUSPEND_CHECK() kthread_suspend_check(curproc) +#define USB_THREAD_SUSPEND(p) kthread_suspend(p,0) +#define USB_THREAD_EXIT(err) kthread_exit(err) +#endif + +#ifdef USB_DEBUG +static int usb_proc_debug; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, proc, CTLFLAG_RW, 0, "USB process"); +SYSCTL_INT(_hw_usb_proc, OID_AUTO, debug, CTLFLAG_RW, &usb_proc_debug, 0, + "Debug level"); + +TUNABLE_INT("hw.usb.proc.debug", &usb_proc_debug); +#endif + +/*------------------------------------------------------------------------* + * usb_process + * + * This function is the USB process dispatcher. + *------------------------------------------------------------------------*/ +static void +usb_process(void *arg) +{ + struct usb_process *up = arg; + struct usb_proc_msg *pm; + struct thread *td; + + /* in case of attach error, check for suspended */ + USB_THREAD_SUSPEND_CHECK(); + + /* adjust priority */ + td = curthread; + thread_lock(td); + sched_prio(td, up->up_prio); + thread_unlock(td); + + mtx_lock(up->up_mtx); + + up->up_curtd = td; + + while (1) { + + if (up->up_gone) + break; + + /* + * NOTE to reimplementors: dequeueing a command from the + * "used" queue and executing it must be atomic, with regard + * to the "up_mtx" mutex. That means any attempt to queue a + * command by another thread must be blocked until either: + * + * 1) the command sleeps + * + * 2) the command returns + * + * Here is a practical example that shows how this helps + * solving a problem: + * + * Assume that you want to set the baud rate on a USB serial + * device. During the programming of the device you don't + * want to receive nor transmit any data, because it will be + * garbage most likely anyway. The programming of our USB + * device takes 20 milliseconds and it needs to call + * functions that sleep. + * + * Non-working solution: Before we queue the programming + * command, we stop transmission and reception of data. Then + * we queue a programming command. At the end of the + * programming command we enable transmission and reception + * of data. + * + * Problem: If a second programming command is queued while the + * first one is sleeping, we end up enabling transmission + * and reception of data too early. + * + * Working solution: Before we queue the programming command, + * we stop transmission and reception of data. Then we queue + * a programming command. Then we queue a second command + * that only enables transmission and reception of data. + * + * Why it works: If a second programming command is queued + * while the first one is sleeping, then the queueing of a + * second command to enable the data transfers, will cause + * the previous one, which is still on the queue, to be + * removed from the queue, and re-inserted after the last + * baud rate programming command, which then gives the + * desired result. + */ + pm = TAILQ_FIRST(&up->up_qhead); + + if (pm) { + DPRINTF("Message pm=%p, cb=%p (enter)\n", + pm, pm->pm_callback); + + (pm->pm_callback) (pm); + + if (pm == TAILQ_FIRST(&up->up_qhead)) { + /* nothing changed */ + TAILQ_REMOVE(&up->up_qhead, pm, pm_qentry); + pm->pm_qentry.tqe_prev = NULL; + } + DPRINTF("Message pm=%p (leave)\n", pm); + + continue; + } + /* end if messages - check if anyone is waiting for sync */ + if (up->up_dsleep) { + up->up_dsleep = 0; + cv_broadcast(&up->up_drain); + } + up->up_msleep = 1; + cv_wait(&up->up_cv, up->up_mtx); + } + + up->up_ptr = NULL; + cv_signal(&up->up_cv); + mtx_unlock(up->up_mtx); +#if (__FreeBSD_version >= 800000) + /* Clear the proc pointer if this is the last thread. */ + if (--usb_pcount == 0) + usbproc = NULL; +#endif + + USB_THREAD_EXIT(0); +} + +/*------------------------------------------------------------------------* + * usb_proc_create + * + * This function will create a process using the given "prio" that can + * execute callbacks. The mutex pointed to by "p_mtx" will be applied + * before calling the callbacks and released after that the callback + * has returned. The structure pointed to by "up" is assumed to be + * zeroed before this function is called. + * + * Return values: + * 0: success + * Else: failure + *------------------------------------------------------------------------*/ +int +usb_proc_create(struct usb_process *up, struct mtx *p_mtx, + const char *pmesg, uint8_t prio) +{ + up->up_mtx = p_mtx; + up->up_prio = prio; + + TAILQ_INIT(&up->up_qhead); + + cv_init(&up->up_cv, "-"); + cv_init(&up->up_drain, "usbdrain"); + + if (USB_THREAD_CREATE(&usb_process, up, + &up->up_ptr, "%s", pmesg)) { + DPRINTFN(0, "Unable to create USB process."); + up->up_ptr = NULL; + goto error; + } +#if (__FreeBSD_version >= 800000) + usb_pcount++; +#endif + return (0); + +error: + usb_proc_free(up); + return (ENOMEM); +} + +/*------------------------------------------------------------------------* + * usb_proc_free + * + * NOTE: If the structure pointed to by "up" is all zero, this + * function does nothing. + * + * NOTE: Messages that are pending on the process queue will not be + * removed nor called. + *------------------------------------------------------------------------*/ +void +usb_proc_free(struct usb_process *up) +{ + /* check if not initialised */ + if (up->up_mtx == NULL) + return; + + usb_proc_drain(up); + + cv_destroy(&up->up_cv); + cv_destroy(&up->up_drain); + + /* make sure that we do not enter here again */ + up->up_mtx = NULL; +} + +/*------------------------------------------------------------------------* + * usb_proc_msignal + * + * This function will queue one of the passed USB process messages on + * the USB process queue. The first message that is not already queued + * will get queued. If both messages are already queued the one queued + * last will be removed from the queue and queued in the end. The USB + * process mutex must be locked when calling this function. This + * function exploits the fact that a process can only do one callback + * at a time. The message that was queued is returned. + *------------------------------------------------------------------------*/ +void * +usb_proc_msignal(struct usb_process *up, void *_pm0, void *_pm1) +{ + struct usb_proc_msg *pm0 = _pm0; + struct usb_proc_msg *pm1 = _pm1; + struct usb_proc_msg *pm2; + usb_size_t d; + uint8_t t; + + /* check if gone, return dummy value */ + if (up->up_gone) + return (_pm0); + + mtx_assert(up->up_mtx, MA_OWNED); + + t = 0; + + if (pm0->pm_qentry.tqe_prev) { + t |= 1; + } + if (pm1->pm_qentry.tqe_prev) { + t |= 2; + } + if (t == 0) { + /* + * No entries are queued. Queue "pm0" and use the existing + * message number. + */ + pm2 = pm0; + } else if (t == 1) { + /* Check if we need to increment the message number. */ + if (pm0->pm_num == up->up_msg_num) { + up->up_msg_num++; + } + pm2 = pm1; + } else if (t == 2) { + /* Check if we need to increment the message number. */ + if (pm1->pm_num == up->up_msg_num) { + up->up_msg_num++; + } + pm2 = pm0; + } else if (t == 3) { + /* + * Both entries are queued. Re-queue the entry closest to + * the end. + */ + d = (pm1->pm_num - pm0->pm_num); + + /* Check sign after subtraction */ + if (d & 0x80000000) { + pm2 = pm0; + } else { + pm2 = pm1; + } + + TAILQ_REMOVE(&up->up_qhead, pm2, pm_qentry); + } else { + pm2 = NULL; /* panic - should not happen */ + } + + DPRINTF(" t=%u, num=%u\n", t, up->up_msg_num); + + /* Put message last on queue */ + + pm2->pm_num = up->up_msg_num; + TAILQ_INSERT_TAIL(&up->up_qhead, pm2, pm_qentry); + + /* Check if we need to wakeup the USB process. */ + + if (up->up_msleep) { + up->up_msleep = 0; /* save "cv_signal()" calls */ + cv_signal(&up->up_cv); + } + return (pm2); +} + +/*------------------------------------------------------------------------* + * usb_proc_is_gone + * + * Return values: + * 0: USB process is running + * Else: USB process is tearing down + *------------------------------------------------------------------------*/ +uint8_t +usb_proc_is_gone(struct usb_process *up) +{ + if (up->up_gone) + return (1); + + /* + * Allow calls when up_mtx is NULL, before the USB process + * structure is initialised. + */ + if (up->up_mtx != NULL) + mtx_assert(up->up_mtx, MA_OWNED); + return (0); +} + +/*------------------------------------------------------------------------* + * usb_proc_mwait + * + * This function will return when the USB process message pointed to + * by "pm" is no longer on a queue. This function must be called + * having "up->up_mtx" locked. + *------------------------------------------------------------------------*/ +void +usb_proc_mwait(struct usb_process *up, void *_pm0, void *_pm1) +{ + struct usb_proc_msg *pm0 = _pm0; + struct usb_proc_msg *pm1 = _pm1; + + /* check if gone */ + if (up->up_gone) + return; + + mtx_assert(up->up_mtx, MA_OWNED); + + if (up->up_curtd == curthread) { + /* Just remove the messages from the queue. */ + if (pm0->pm_qentry.tqe_prev) { + TAILQ_REMOVE(&up->up_qhead, pm0, pm_qentry); + pm0->pm_qentry.tqe_prev = NULL; + } + if (pm1->pm_qentry.tqe_prev) { + TAILQ_REMOVE(&up->up_qhead, pm1, pm_qentry); + pm1->pm_qentry.tqe_prev = NULL; + } + } else + while (pm0->pm_qentry.tqe_prev || + pm1->pm_qentry.tqe_prev) { + /* check if config thread is gone */ + if (up->up_gone) + break; + up->up_dsleep = 1; + cv_wait(&up->up_drain, up->up_mtx); + } +} + +/*------------------------------------------------------------------------* + * usb_proc_drain + * + * This function will tear down an USB process, waiting for the + * currently executing command to return. + * + * NOTE: If the structure pointed to by "up" is all zero, + * this function does nothing. + *------------------------------------------------------------------------*/ +void +usb_proc_drain(struct usb_process *up) +{ + /* check if not initialised */ + if (up->up_mtx == NULL) + return; + /* handle special case with Giant */ + if (up->up_mtx != &Giant) + mtx_assert(up->up_mtx, MA_NOTOWNED); + + mtx_lock(up->up_mtx); + + /* Set the gone flag */ + + up->up_gone = 1; + + while (up->up_ptr) { + + /* Check if we need to wakeup the USB process */ + + if (up->up_msleep || up->up_csleep) { + up->up_msleep = 0; + up->up_csleep = 0; + cv_signal(&up->up_cv); + } + /* Check if we are still cold booted */ + + if (cold) { + USB_THREAD_SUSPEND(up->up_ptr); + printf("WARNING: A USB process has " + "been left suspended\n"); + break; + } + cv_wait(&up->up_cv, up->up_mtx); + } + /* Check if someone is waiting - should not happen */ + + if (up->up_dsleep) { + up->up_dsleep = 0; + cv_broadcast(&up->up_drain); + DPRINTF("WARNING: Someone is waiting " + "for USB process drain!\n"); + } + mtx_unlock(up->up_mtx); +} + +/*------------------------------------------------------------------------* + * usb_proc_rewakeup + * + * This function is called to re-wakeup the given USB + * process. This usually happens after that the USB system has been in + * polling mode, like during a panic. This function must be called + * having "up->up_mtx" locked. + *------------------------------------------------------------------------*/ +void +usb_proc_rewakeup(struct usb_process *up) +{ + /* check if not initialised */ + if (up->up_mtx == NULL) + return; + /* check if gone */ + if (up->up_gone) + return; + + mtx_assert(up->up_mtx, MA_OWNED); + + if (up->up_msleep == 0) { + /* re-wakeup */ + cv_signal(&up->up_cv); + } +} diff --git a/sys/bus/u4b/usb_process.h b/sys/bus/u4b/usb_process.h new file mode 100644 index 0000000000..23cf6607c9 --- /dev/null +++ b/sys/bus/u4b/usb_process.h @@ -0,0 +1,82 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_PROCESS_H_ +#define _USB_PROCESS_H_ + +#include +#include +#include + +/* defines */ +#define USB_PRI_HIGH PI_SWI(SWI_NET) +#define USB_PRI_MED PI_SWI(SWI_CAMBIO) + +#define USB_PROC_WAIT_TIMEOUT 2 +#define USB_PROC_WAIT_DRAIN 1 +#define USB_PROC_WAIT_NORMAL 0 + +/* structure prototypes */ + +struct usb_proc_msg; + +/* + * The following structure defines the USB process. + */ +struct usb_process { + TAILQ_HEAD(, usb_proc_msg) up_qhead; + struct cv up_cv; + struct cv up_drain; + +#if (__FreeBSD_version >= 800000) + struct thread *up_ptr; +#else + struct proc *up_ptr; +#endif + struct thread *up_curtd; + struct mtx *up_mtx; + + usb_size_t up_msg_num; + + uint8_t up_prio; + uint8_t up_gone; + uint8_t up_msleep; + uint8_t up_csleep; + uint8_t up_dsleep; +}; + +/* prototypes */ + +uint8_t usb_proc_is_gone(struct usb_process *up); +int usb_proc_create(struct usb_process *up, struct mtx *p_mtx, + const char *pmesg, uint8_t prio); +void usb_proc_drain(struct usb_process *up); +void usb_proc_mwait(struct usb_process *up, void *pm0, void *pm1); +void usb_proc_free(struct usb_process *up); +void *usb_proc_msignal(struct usb_process *up, void *pm0, void *pm1); +void usb_proc_rewakeup(struct usb_process *up); + +#endif /* _USB_PROCESS_H_ */ diff --git a/sys/bus/u4b/usb_request.c b/sys/bus/u4b/usb_request.c new file mode 100644 index 0000000000..c403a703ba --- /dev/null +++ b/sys/bus/u4b/usb_request.c @@ -0,0 +1,2228 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define USB_DEBUG_VAR usb_debug + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static int usb_no_cs_fail; + +SYSCTL_INT(_hw_usb, OID_AUTO, no_cs_fail, CTLFLAG_RW, + &usb_no_cs_fail, 0, "USB clear stall failures are ignored, if set"); + +#ifdef USB_DEBUG +static int usb_pr_poll_delay = USB_PORT_RESET_DELAY; +static int usb_pr_recovery_delay = USB_PORT_RESET_RECOVERY; + +SYSCTL_INT(_hw_usb, OID_AUTO, pr_poll_delay, CTLFLAG_RW, + &usb_pr_poll_delay, 0, "USB port reset poll delay in ms"); +SYSCTL_INT(_hw_usb, OID_AUTO, pr_recovery_delay, CTLFLAG_RW, + &usb_pr_recovery_delay, 0, "USB port reset recovery delay in ms"); + +#ifdef USB_REQ_DEBUG +/* The following structures are used in connection to fault injection. */ +struct usb_ctrl_debug { + int bus_index; /* target bus */ + int dev_index; /* target address */ + int ds_fail; /* fail data stage */ + int ss_fail; /* fail data stage */ + int ds_delay; /* data stage delay in ms */ + int ss_delay; /* status stage delay in ms */ + int bmRequestType_value; + int bRequest_value; +}; + +struct usb_ctrl_debug_bits { + uint16_t ds_delay; + uint16_t ss_delay; + uint8_t ds_fail:1; + uint8_t ss_fail:1; + uint8_t enabled:1; +}; + +/* The default is to disable fault injection. */ + +static struct usb_ctrl_debug usb_ctrl_debug = { + .bus_index = -1, + .dev_index = -1, + .bmRequestType_value = -1, + .bRequest_value = -1, +}; + +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_bus_fail, CTLFLAG_RW, + &usb_ctrl_debug.bus_index, 0, "USB controller index to fail"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_dev_fail, CTLFLAG_RW, + &usb_ctrl_debug.dev_index, 0, "USB device address to fail"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_ds_fail, CTLFLAG_RW, + &usb_ctrl_debug.ds_fail, 0, "USB fail data stage"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_ss_fail, CTLFLAG_RW, + &usb_ctrl_debug.ss_fail, 0, "USB fail status stage"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_ds_delay, CTLFLAG_RW, + &usb_ctrl_debug.ds_delay, 0, "USB data stage delay in ms"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_ss_delay, CTLFLAG_RW, + &usb_ctrl_debug.ss_delay, 0, "USB status stage delay in ms"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_rt_fail, CTLFLAG_RW, + &usb_ctrl_debug.bmRequestType_value, 0, "USB bmRequestType to fail"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_rv_fail, CTLFLAG_RW, + &usb_ctrl_debug.bRequest_value, 0, "USB bRequest to fail"); + +/*------------------------------------------------------------------------* + * usbd_get_debug_bits + * + * This function is only useful in USB host mode. + *------------------------------------------------------------------------*/ +static void +usbd_get_debug_bits(struct usb_device *udev, struct usb_device_request *req, + struct usb_ctrl_debug_bits *dbg) +{ + int temp; + + memset(dbg, 0, sizeof(*dbg)); + + /* Compute data stage delay */ + + temp = usb_ctrl_debug.ds_delay; + if (temp < 0) + temp = 0; + else if (temp > (16*1024)) + temp = (16*1024); + + dbg->ds_delay = temp; + + /* Compute status stage delay */ + + temp = usb_ctrl_debug.ss_delay; + if (temp < 0) + temp = 0; + else if (temp > (16*1024)) + temp = (16*1024); + + dbg->ss_delay = temp; + + /* Check if this control request should be failed */ + + if (usbd_get_bus_index(udev) != usb_ctrl_debug.bus_index) + return; + + if (usbd_get_device_index(udev) != usb_ctrl_debug.dev_index) + return; + + temp = usb_ctrl_debug.bmRequestType_value; + + if ((temp != req->bmRequestType) && (temp >= 0) && (temp <= 255)) + return; + + temp = usb_ctrl_debug.bRequest_value; + + if ((temp != req->bRequest) && (temp >= 0) && (temp <= 255)) + return; + + temp = usb_ctrl_debug.ds_fail; + if (temp) + dbg->ds_fail = 1; + + temp = usb_ctrl_debug.ss_fail; + if (temp) + dbg->ss_fail = 1; + + dbg->enabled = 1; +} +#endif /* USB_REQ_DEBUG */ +#endif /* USB_DEBUG */ + +/*------------------------------------------------------------------------* + * usbd_do_request_callback + * + * This function is the USB callback for generic USB Host control + * transfers. + *------------------------------------------------------------------------*/ +void +usbd_do_request_callback(struct usb_xfer *xfer, usb_error_t error) +{ + ; /* workaround for a bug in "indent" */ + + DPRINTF("st=%u\n", USB_GET_STATE(xfer)); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + usbd_transfer_submit(xfer); + break; + default: + cv_signal(&xfer->xroot->udev->ctrlreq_cv); + break; + } +} + +/*------------------------------------------------------------------------* + * usb_do_clear_stall_callback + * + * This function is the USB callback for generic clear stall requests. + *------------------------------------------------------------------------*/ +void +usb_do_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_device_request req; + struct usb_device *udev; + struct usb_endpoint *ep; + struct usb_endpoint *ep_end; + struct usb_endpoint *ep_first; + uint8_t to; + + udev = xfer->xroot->udev; + + USB_BUS_LOCK(udev->bus); + + /* round robin endpoint clear stall */ + + ep = udev->ep_curr; + ep_end = udev->endpoints + udev->endpoints_max; + ep_first = udev->endpoints; + to = udev->endpoints_max; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + /* reset error counter */ + udev->clear_stall_errors = 0; + + if (ep == NULL) + goto tr_setup; /* device was unconfigured */ + if (ep->edesc && + ep->is_stalled) { + ep->toggle_next = 0; + ep->is_stalled = 0; + /* some hardware needs a callback to clear the data toggle */ + usbd_clear_stall_locked(udev, ep); + /* start up the current or next transfer, if any */ + usb_command_wrapper(&ep->endpoint_q, + ep->endpoint_q.curr); + } + ep++; + + case USB_ST_SETUP: +tr_setup: + if (to == 0) + break; /* no endpoints - nothing to do */ + if ((ep < ep_first) || (ep >= ep_end)) + ep = ep_first; /* endpoint wrapped around */ + if (ep->edesc && + ep->is_stalled) { + + /* setup a clear-stall packet */ + + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + req.wIndex[0] = ep->edesc->bEndpointAddress; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + /* copy in the transfer */ + + usbd_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + + /* set length */ + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + xfer->nframes = 1; + USB_BUS_UNLOCK(udev->bus); + + usbd_transfer_submit(xfer); + + USB_BUS_LOCK(udev->bus); + break; + } + ep++; + to--; + goto tr_setup; + + default: + if (error == USB_ERR_CANCELLED) + break; + + DPRINTF("Clear stall failed.\n"); + + /* + * Some VMs like VirtualBox always return failure on + * clear-stall which we sometimes should just ignore. + */ + if (usb_no_cs_fail) + goto tr_transferred; + if (udev->clear_stall_errors == USB_CS_RESET_LIMIT) + goto tr_setup; + + if (error == USB_ERR_TIMEOUT) { + udev->clear_stall_errors = USB_CS_RESET_LIMIT; + DPRINTF("Trying to re-enumerate.\n"); + usbd_start_re_enumerate(udev); + } else { + udev->clear_stall_errors++; + if (udev->clear_stall_errors == USB_CS_RESET_LIMIT) { + DPRINTF("Trying to re-enumerate.\n"); + usbd_start_re_enumerate(udev); + } + } + goto tr_setup; + } + + /* store current endpoint */ + udev->ep_curr = ep; + USB_BUS_UNLOCK(udev->bus); +} + +static usb_handle_req_t * +usbd_get_hr_func(struct usb_device *udev) +{ + /* figure out if there is a Handle Request function */ + if (udev->flags.usb_mode == USB_MODE_DEVICE) + return (usb_temp_get_desc_p); + else if (udev->parent_hub == NULL) + return (udev->bus->methods->roothub_exec); + else + return (NULL); +} + +/*------------------------------------------------------------------------* + * usbd_do_request_flags and usbd_do_request + * + * Description of arguments passed to these functions: + * + * "udev" - this is the "usb_device" structure pointer on which the + * request should be performed. It is possible to call this function + * in both Host Side mode and Device Side mode. + * + * "mtx" - if this argument is non-NULL the mutex pointed to by it + * will get dropped and picked up during the execution of this + * function, hence this function sometimes needs to sleep. If this + * argument is NULL it has no effect. + * + * "req" - this argument must always be non-NULL and points to an + * 8-byte structure holding the USB request to be done. The USB + * request structure has a bit telling the direction of the USB + * request, if it is a read or a write. + * + * "data" - if the "wLength" part of the structure pointed to by "req" + * is non-zero this argument must point to a valid kernel buffer which + * can hold at least "wLength" bytes. If "wLength" is zero "data" can + * be NULL. + * + * "flags" - here is a list of valid flags: + * + * o USB_SHORT_XFER_OK: allows the data transfer to be shorter than + * specified + * + * o USB_DELAY_STATUS_STAGE: allows the status stage to be performed + * at a later point in time. This is tunable by the "hw.usb.ss_delay" + * sysctl. This flag is mostly useful for debugging. + * + * o USB_USER_DATA_PTR: treat the "data" pointer like a userland + * pointer. + * + * "actlen" - if non-NULL the actual transfer length will be stored in + * the 16-bit unsigned integer pointed to by "actlen". This + * information is mostly useful when the "USB_SHORT_XFER_OK" flag is + * used. + * + * "timeout" - gives the timeout for the control transfer in + * milliseconds. A "timeout" value less than 50 milliseconds is + * treated like a 50 millisecond timeout. A "timeout" value greater + * than 30 seconds is treated like a 30 second timeout. This USB stack + * does not allow control requests without a timeout. + * + * NOTE: This function is thread safe. All calls to + * "usbd_do_request_flags" will be serialised by the use of an + * internal "sx_lock". + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_do_request_flags(struct usb_device *udev, struct mtx *mtx, + struct usb_device_request *req, void *data, uint16_t flags, + uint16_t *actlen, usb_timeout_t timeout) +{ +#ifdef USB_REQ_DEBUG + struct usb_ctrl_debug_bits dbg; +#endif + usb_handle_req_t *hr_func; + struct usb_xfer *xfer; + const void *desc; + int err = 0; + usb_ticks_t start_ticks; + usb_ticks_t delta_ticks; + usb_ticks_t max_ticks; + uint16_t length; + uint16_t temp; + uint16_t acttemp; + uint8_t enum_locked; + + if (timeout < 50) { + /* timeout is too small */ + timeout = 50; + } + if (timeout > 30000) { + /* timeout is too big */ + timeout = 30000; + } + length = UGETW(req->wLength); + + enum_locked = usbd_enum_is_locked(udev); + + DPRINTFN(5, "udev=%p bmRequestType=0x%02x bRequest=0x%02x " + "wValue=0x%02x%02x wIndex=0x%02x%02x wLength=0x%02x%02x\n", + udev, req->bmRequestType, req->bRequest, + req->wValue[1], req->wValue[0], + req->wIndex[1], req->wIndex[0], + req->wLength[1], req->wLength[0]); + + /* Check if the device is still alive */ + if (udev->state < USB_STATE_POWERED) { + DPRINTF("usb device has gone\n"); + return (USB_ERR_NOT_CONFIGURED); + } + + /* + * Set "actlen" to a known value in case the caller does not + * check the return value: + */ + if (actlen) + *actlen = 0; + +#if (USB_HAVE_USER_IO == 0) + if (flags & USB_USER_DATA_PTR) + return (USB_ERR_INVAL); +#endif + if ((mtx != NULL) && (mtx != &Giant)) { + mtx_unlock(mtx); + mtx_assert(mtx, MA_NOTOWNED); + } + + /* + * We need to allow suspend and resume at this point, else the + * control transfer will timeout if the device is suspended! + */ + if (enum_locked) + usbd_sr_unlock(udev); + + /* + * Grab the default sx-lock so that serialisation + * is achieved when multiple threads are involved: + */ + sx_xlock(&udev->ctrl_sx); + + hr_func = usbd_get_hr_func(udev); + + if (hr_func != NULL) { + DPRINTF("Handle Request function is set\n"); + + desc = NULL; + temp = 0; + + if (!(req->bmRequestType & UT_READ)) { + if (length != 0) { + DPRINTFN(1, "The handle request function " + "does not support writing data!\n"); + err = USB_ERR_INVAL; + goto done; + } + } + + /* The root HUB code needs the BUS lock locked */ + + USB_BUS_LOCK(udev->bus); + err = (hr_func) (udev, req, &desc, &temp); + USB_BUS_UNLOCK(udev->bus); + + if (err) + goto done; + + if (length > temp) { + if (!(flags & USB_SHORT_XFER_OK)) { + err = USB_ERR_SHORT_XFER; + goto done; + } + length = temp; + } + if (actlen) + *actlen = length; + + if (length > 0) { +#if USB_HAVE_USER_IO + if (flags & USB_USER_DATA_PTR) { + if (copyout(desc, data, length)) { + err = USB_ERR_INVAL; + goto done; + } + } else +#endif + memcpy(data, desc, length); + } + goto done; /* success */ + } + + /* + * Setup a new USB transfer or use the existing one, if any: + */ + usbd_ctrl_transfer_setup(udev); + + xfer = udev->ctrl_xfer[0]; + if (xfer == NULL) { + /* most likely out of memory */ + err = USB_ERR_NOMEM; + goto done; + } + +#ifdef USB_REQ_DEBUG + /* Get debug bits */ + usbd_get_debug_bits(udev, req, &dbg); + + /* Check for fault injection */ + if (dbg.enabled) + flags |= USB_DELAY_STATUS_STAGE; +#endif + USB_XFER_LOCK(xfer); + + if (flags & USB_DELAY_STATUS_STAGE) + xfer->flags.manual_status = 1; + else + xfer->flags.manual_status = 0; + + if (flags & USB_SHORT_XFER_OK) + xfer->flags.short_xfer_ok = 1; + else + xfer->flags.short_xfer_ok = 0; + + xfer->timeout = timeout; + + start_ticks = ticks; + + max_ticks = USB_MS_TO_TICKS(timeout); + + usbd_copy_in(xfer->frbuffers, 0, req, sizeof(*req)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(*req)); + + while (1) { + temp = length; + if (temp > usbd_xfer_max_len(xfer)) { + temp = usbd_xfer_max_len(xfer); + } +#ifdef USB_REQ_DEBUG + if (xfer->flags.manual_status) { + if (usbd_xfer_frame_len(xfer, 0) != 0) { + /* Execute data stage separately */ + temp = 0; + } else if (temp > 0) { + if (dbg.ds_fail) { + err = USB_ERR_INVAL; + break; + } + if (dbg.ds_delay > 0) { + usb_pause_mtx( + xfer->xroot->xfer_mtx, + USB_MS_TO_TICKS(dbg.ds_delay)); + /* make sure we don't time out */ + start_ticks = ticks; + } + } + } +#endif + usbd_xfer_set_frame_len(xfer, 1, temp); + + if (temp > 0) { + if (!(req->bmRequestType & UT_READ)) { +#if USB_HAVE_USER_IO + if (flags & USB_USER_DATA_PTR) { + USB_XFER_UNLOCK(xfer); + err = usbd_copy_in_user(xfer->frbuffers + 1, + 0, data, temp); + USB_XFER_LOCK(xfer); + if (err) { + err = USB_ERR_INVAL; + break; + } + } else +#endif + usbd_copy_in(xfer->frbuffers + 1, + 0, data, temp); + } + usbd_xfer_set_frames(xfer, 2); + } else { + if (usbd_xfer_frame_len(xfer, 0) == 0) { + if (xfer->flags.manual_status) { +#ifdef USB_REQ_DEBUG + if (dbg.ss_fail) { + err = USB_ERR_INVAL; + break; + } + if (dbg.ss_delay > 0) { + usb_pause_mtx( + xfer->xroot->xfer_mtx, + USB_MS_TO_TICKS(dbg.ss_delay)); + /* make sure we don't time out */ + start_ticks = ticks; + } +#endif + xfer->flags.manual_status = 0; + } else { + break; + } + } + usbd_xfer_set_frames(xfer, 1); + } + + usbd_transfer_start(xfer); + + while (usbd_transfer_pending(xfer)) { + cv_wait(&udev->ctrlreq_cv, + xfer->xroot->xfer_mtx); + } + + err = xfer->error; + + if (err) { + break; + } + + /* get actual length of DATA stage */ + + if (xfer->aframes < 2) { + acttemp = 0; + } else { + acttemp = usbd_xfer_frame_len(xfer, 1); + } + + /* check for short packet */ + + if (temp > acttemp) { + temp = acttemp; + length = temp; + } + if (temp > 0) { + if (req->bmRequestType & UT_READ) { +#if USB_HAVE_USER_IO + if (flags & USB_USER_DATA_PTR) { + USB_XFER_UNLOCK(xfer); + err = usbd_copy_out_user(xfer->frbuffers + 1, + 0, data, temp); + USB_XFER_LOCK(xfer); + if (err) { + err = USB_ERR_INVAL; + break; + } + } else +#endif + usbd_copy_out(xfer->frbuffers + 1, + 0, data, temp); + } + } + /* + * Clear "frlengths[0]" so that we don't send the setup + * packet again: + */ + usbd_xfer_set_frame_len(xfer, 0, 0); + + /* update length and data pointer */ + length -= temp; + data = USB_ADD_BYTES(data, temp); + + if (actlen) { + (*actlen) += temp; + } + /* check for timeout */ + + delta_ticks = ticks - start_ticks; + if (delta_ticks > max_ticks) { + if (!err) { + err = USB_ERR_TIMEOUT; + } + } + if (err) { + break; + } + } + + if (err) { + /* + * Make sure that the control endpoint is no longer + * blocked in case of a non-transfer related error: + */ + usbd_transfer_stop(xfer); + } + USB_XFER_UNLOCK(xfer); + +done: + sx_xunlock(&udev->ctrl_sx); + + if (enum_locked) + usbd_sr_lock(udev); + + if ((mtx != NULL) && (mtx != &Giant)) + mtx_lock(mtx); + + return ((usb_error_t)err); +} + +/*------------------------------------------------------------------------* + * usbd_do_request_proc - factored out code + * + * This function is factored out code. It does basically the same like + * usbd_do_request_flags, except it will check the status of the + * passed process argument before doing the USB request. If the + * process is draining the USB_ERR_IOERROR code will be returned. It + * is assumed that the mutex associated with the process is locked + * when calling this function. + *------------------------------------------------------------------------*/ +usb_error_t +usbd_do_request_proc(struct usb_device *udev, struct usb_process *pproc, + struct usb_device_request *req, void *data, uint16_t flags, + uint16_t *actlen, usb_timeout_t timeout) +{ + usb_error_t err; + uint16_t len; + + /* get request data length */ + len = UGETW(req->wLength); + + /* check if the device is being detached */ + if (usb_proc_is_gone(pproc)) { + err = USB_ERR_IOERROR; + goto done; + } + + /* forward the USB request */ + err = usbd_do_request_flags(udev, pproc->up_mtx, + req, data, flags, actlen, timeout); + +done: + /* on failure we zero the data */ + /* on short packet we zero the unused data */ + if ((len != 0) && (req->bmRequestType & UE_DIR_IN)) { + if (err) + memset(data, 0, len); + else if (actlen && *actlen != len) + memset(((uint8_t *)data) + *actlen, 0, len - *actlen); + } + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_reset_port + * + * This function will instruct a USB HUB to perform a reset sequence + * on the specified port number. + * + * Returns: + * 0: Success. The USB device should now be at address zero. + * Else: Failure. No USB device is present and the USB port should be + * disabled. + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_reset_port(struct usb_device *udev, struct mtx *mtx, uint8_t port) +{ + struct usb_port_status ps; + usb_error_t err; + uint16_t n; + uint16_t status; + uint16_t change; + +#ifdef USB_DEBUG + uint16_t pr_poll_delay; + uint16_t pr_recovery_delay; + +#endif + + DPRINTF("\n"); + + /* clear any leftover port reset changes first */ + usbd_req_clear_port_feature( + udev, mtx, port, UHF_C_PORT_RESET); + + /* assert port reset on the given port */ + err = usbd_req_set_port_feature( + udev, mtx, port, UHF_PORT_RESET); + + /* check for errors */ + if (err) + goto done; +#ifdef USB_DEBUG + /* range check input parameters */ + pr_poll_delay = usb_pr_poll_delay; + if (pr_poll_delay < 1) { + pr_poll_delay = 1; + } else if (pr_poll_delay > 1000) { + pr_poll_delay = 1000; + } + pr_recovery_delay = usb_pr_recovery_delay; + if (pr_recovery_delay > 1000) { + pr_recovery_delay = 1000; + } +#endif + n = 0; + while (1) { +#ifdef USB_DEBUG + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_poll_delay)); + n += pr_poll_delay; +#else + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_DELAY)); + n += USB_PORT_RESET_DELAY; +#endif + err = usbd_req_get_port_status(udev, mtx, &ps, port); + if (err) + goto done; + + status = UGETW(ps.wPortStatus); + change = UGETW(ps.wPortChange); + + /* if the device disappeared, just give up */ + if (!(status & UPS_CURRENT_CONNECT_STATUS)) + goto done; + + /* check if reset is complete */ + if (change & UPS_C_PORT_RESET) + break; + + /* + * Some Virtual Machines like VirtualBox 4.x fail to + * generate a port reset change event. Check if reset + * is no longer asserted. + */ + if (!(status & UPS_RESET)) + break; + + /* check for timeout */ + if (n > 1000) { + n = 0; + break; + } + } + + /* clear port reset first */ + err = usbd_req_clear_port_feature( + udev, mtx, port, UHF_C_PORT_RESET); + if (err) + goto done; + + /* check for timeout */ + if (n == 0) { + err = USB_ERR_TIMEOUT; + goto done; + } +#ifdef USB_DEBUG + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_recovery_delay)); +#else + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_RECOVERY)); +#endif + +done: + DPRINTFN(2, "port %d reset returning error=%s\n", + port, usbd_errstr(err)); + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_warm_reset_port + * + * This function will instruct an USB HUB to perform a warm reset + * sequence on the specified port number. This kind of reset is not + * mandatory for LOW-, FULL- and HIGH-speed USB HUBs and is targeted + * for SUPER-speed USB HUBs. + * + * Returns: + * 0: Success. The USB device should now be available again. + * Else: Failure. No USB device is present and the USB port should be + * disabled. + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_warm_reset_port(struct usb_device *udev, struct mtx *mtx, + uint8_t port) +{ + struct usb_port_status ps; + usb_error_t err; + uint16_t n; + uint16_t status; + uint16_t change; + +#ifdef USB_DEBUG + uint16_t pr_poll_delay; + uint16_t pr_recovery_delay; + +#endif + + DPRINTF("\n"); + + err = usbd_req_get_port_status(udev, mtx, &ps, port); + if (err) + goto done; + + status = UGETW(ps.wPortStatus); + + switch (UPS_PORT_LINK_STATE_GET(status)) { + case UPS_PORT_LS_U3: + case UPS_PORT_LS_COMP_MODE: + case UPS_PORT_LS_LOOPBACK: + case UPS_PORT_LS_SS_INA: + break; + default: + DPRINTF("Wrong state for warm reset\n"); + return (0); + } + + /* clear any leftover warm port reset changes first */ + usbd_req_clear_port_feature(udev, mtx, + port, UHF_C_BH_PORT_RESET); + + /* set warm port reset */ + err = usbd_req_set_port_feature(udev, mtx, + port, UHF_BH_PORT_RESET); + if (err) + goto done; + +#ifdef USB_DEBUG + /* range check input parameters */ + pr_poll_delay = usb_pr_poll_delay; + if (pr_poll_delay < 1) { + pr_poll_delay = 1; + } else if (pr_poll_delay > 1000) { + pr_poll_delay = 1000; + } + pr_recovery_delay = usb_pr_recovery_delay; + if (pr_recovery_delay > 1000) { + pr_recovery_delay = 1000; + } +#endif + n = 0; + while (1) { +#ifdef USB_DEBUG + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_poll_delay)); + n += pr_poll_delay; +#else + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_DELAY)); + n += USB_PORT_RESET_DELAY; +#endif + err = usbd_req_get_port_status(udev, mtx, &ps, port); + if (err) + goto done; + + status = UGETW(ps.wPortStatus); + change = UGETW(ps.wPortChange); + + /* if the device disappeared, just give up */ + if (!(status & UPS_CURRENT_CONNECT_STATUS)) + goto done; + + /* check if reset is complete */ + if (change & UPS_C_BH_PORT_RESET) + break; + + /* check for timeout */ + if (n > 1000) { + n = 0; + break; + } + } + + /* clear port reset first */ + err = usbd_req_clear_port_feature( + udev, mtx, port, UHF_C_BH_PORT_RESET); + if (err) + goto done; + + /* check for timeout */ + if (n == 0) { + err = USB_ERR_TIMEOUT; + goto done; + } +#ifdef USB_DEBUG + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_recovery_delay)); +#else + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_RECOVERY)); +#endif + +done: + DPRINTFN(2, "port %d warm reset returning error=%s\n", + port, usbd_errstr(err)); + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_desc + * + * This function can be used to retrieve USB descriptors. It contains + * some additional logic like zeroing of missing descriptor bytes and + * retrying an USB descriptor in case of failure. The "min_len" + * argument specifies the minimum descriptor length. The "max_len" + * argument specifies the maximum descriptor length. If the real + * descriptor length is less than the minimum length the missing + * byte(s) will be zeroed. The type field, the second byte of the USB + * descriptor, will get forced to the correct type. If the "actlen" + * pointer is non-NULL, the actual length of the transfer will get + * stored in the 16-bit unsigned integer which it is pointing to. The + * first byte of the descriptor will not get updated. If the "actlen" + * pointer is NULL the first byte of the descriptor will get updated + * to reflect the actual length instead. If "min_len" is not equal to + * "max_len" then this function will try to retrive the beginning of + * the descriptor and base the maximum length on the first byte of the + * descriptor. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_desc(struct usb_device *udev, + struct mtx *mtx, uint16_t *actlen, void *desc, + uint16_t min_len, uint16_t max_len, + uint16_t id, uint8_t type, uint8_t index, + uint8_t retries) +{ + struct usb_device_request req; + uint8_t *buf; + usb_error_t err; + + DPRINTFN(4, "id=%d, type=%d, index=%d, max_len=%d\n", + id, type, index, max_len); + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, type, index); + USETW(req.wIndex, id); + + while (1) { + + if ((min_len < 2) || (max_len < 2)) { + err = USB_ERR_INVAL; + goto done; + } + USETW(req.wLength, min_len); + + err = usbd_do_request_flags(udev, mtx, &req, + desc, 0, NULL, 1000); + + if (err) { + if (!retries) { + goto done; + } + retries--; + + usb_pause_mtx(mtx, hz / 5); + + continue; + } + buf = desc; + + if (min_len == max_len) { + + /* enforce correct length */ + if ((buf[0] > min_len) && (actlen == NULL)) + buf[0] = min_len; + + /* enforce correct type */ + buf[1] = type; + + goto done; + } + /* range check */ + + if (max_len > buf[0]) { + max_len = buf[0]; + } + /* zero minimum data */ + + while (min_len > max_len) { + min_len--; + buf[min_len] = 0; + } + + /* set new minimum length */ + + min_len = max_len; + } +done: + if (actlen != NULL) { + if (err) + *actlen = 0; + else + *actlen = min_len; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_string_any + * + * This function will return the string given by "string_index" + * using the first language ID. The maximum length "len" includes + * the terminating zero. The "len" argument should be twice as + * big pluss 2 bytes, compared with the actual maximum string length ! + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_string_any(struct usb_device *udev, struct mtx *mtx, char *buf, + uint16_t len, uint8_t string_index) +{ + char *s; + uint8_t *temp; + uint16_t i; + uint16_t n; + uint16_t c; + uint8_t swap; + usb_error_t err; + + if (len == 0) { + /* should not happen */ + return (USB_ERR_NORMAL_COMPLETION); + } + if (string_index == 0) { + /* this is the language table */ + buf[0] = 0; + return (USB_ERR_INVAL); + } + if (udev->flags.no_strings) { + buf[0] = 0; + return (USB_ERR_STALLED); + } + err = usbd_req_get_string_desc + (udev, mtx, buf, len, udev->langid, string_index); + if (err) { + buf[0] = 0; + return (err); + } + temp = (uint8_t *)buf; + + if (temp[0] < 2) { + /* string length is too short */ + buf[0] = 0; + return (USB_ERR_INVAL); + } + /* reserve one byte for terminating zero */ + len--; + + /* find maximum length */ + s = buf; + n = (temp[0] / 2) - 1; + if (n > len) { + n = len; + } + /* skip descriptor header */ + temp += 2; + + /* reset swap state */ + swap = 3; + + /* convert and filter */ + for (i = 0; (i != n); i++) { + c = UGETW(temp + (2 * i)); + + /* convert from Unicode, handle buggy strings */ + if (((c & 0xff00) == 0) && (swap & 1)) { + /* Little Endian, default */ + *s = c; + swap = 1; + } else if (((c & 0x00ff) == 0) && (swap & 2)) { + /* Big Endian */ + *s = c >> 8; + swap = 2; + } else { + /* silently skip bad character */ + continue; + } + + /* + * Filter by default - We only allow alphanumerical + * and a few more to avoid any problems with scripts + * and daemons. + */ + if (isalpha(*s) || + isdigit(*s) || + *s == '-' || + *s == '+' || + *s == ' ' || + *s == '.' || + *s == ',') { + /* allowed */ + s++; + } + /* silently skip bad character */ + } + *s = 0; /* zero terminate resulting string */ + return (USB_ERR_NORMAL_COMPLETION); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_string_desc + * + * If you don't know the language ID, consider using + * "usbd_req_get_string_any()". + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_string_desc(struct usb_device *udev, struct mtx *mtx, void *sdesc, + uint16_t max_len, uint16_t lang_id, + uint8_t string_index) +{ + return (usbd_req_get_desc(udev, mtx, NULL, sdesc, 2, max_len, lang_id, + UDESC_STRING, string_index, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_config_desc_ptr + * + * This function is used in device side mode to retrieve the pointer + * to the generated config descriptor. This saves allocating space for + * an additional config descriptor when setting the configuration. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_descriptor_ptr(struct usb_device *udev, + struct usb_config_descriptor **ppcd, uint16_t wValue) +{ + struct usb_device_request req; + usb_handle_req_t *hr_func; + const void *ptr; + uint16_t len; + usb_error_t err; + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW(req.wValue, wValue); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + ptr = NULL; + len = 0; + + hr_func = usbd_get_hr_func(udev); + + if (hr_func == NULL) + err = USB_ERR_INVAL; + else { + USB_BUS_LOCK(udev->bus); + err = (hr_func) (udev, &req, &ptr, &len); + USB_BUS_UNLOCK(udev->bus); + } + + if (err) + ptr = NULL; + else if (ptr == NULL) + err = USB_ERR_INVAL; + + *ppcd = __DECONST(struct usb_config_descriptor *, ptr); + + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_config_desc + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_config_desc(struct usb_device *udev, struct mtx *mtx, + struct usb_config_descriptor *d, uint8_t conf_index) +{ + usb_error_t err; + + DPRINTFN(4, "confidx=%d\n", conf_index); + + err = usbd_req_get_desc(udev, mtx, NULL, d, sizeof(*d), + sizeof(*d), 0, UDESC_CONFIG, conf_index, 0); + if (err) { + goto done; + } + /* Extra sanity checking */ + if (UGETW(d->wTotalLength) < sizeof(*d)) { + err = USB_ERR_INVAL; + } +done: + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_config_desc_full + * + * This function gets the complete USB configuration descriptor and + * ensures that "wTotalLength" is correct. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_config_desc_full(struct usb_device *udev, struct mtx *mtx, + struct usb_config_descriptor **ppcd, struct malloc_type *mtype, + uint8_t index) +{ + struct usb_config_descriptor cd; + struct usb_config_descriptor *cdesc; + uint16_t len; + usb_error_t err; + + DPRINTFN(4, "index=%d\n", index); + + *ppcd = NULL; + + err = usbd_req_get_config_desc(udev, mtx, &cd, index); + if (err) { + return (err); + } + /* get full descriptor */ + len = UGETW(cd.wTotalLength); + if (len < sizeof(*cdesc)) { + /* corrupt descriptor */ + return (USB_ERR_INVAL); + } + cdesc = malloc(len, mtype, M_WAITOK); + if (cdesc == NULL) { + return (USB_ERR_NOMEM); + } + err = usbd_req_get_desc(udev, mtx, NULL, cdesc, len, len, 0, + UDESC_CONFIG, index, 3); + if (err) { + free(cdesc, mtype); + return (err); + } + /* make sure that the device is not fooling us: */ + USETW(cdesc->wTotalLength, len); + + *ppcd = cdesc; + + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usbd_req_get_device_desc + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_device_desc(struct usb_device *udev, struct mtx *mtx, + struct usb_device_descriptor *d) +{ + DPRINTFN(4, "\n"); + return (usbd_req_get_desc(udev, mtx, NULL, d, sizeof(*d), + sizeof(*d), 0, UDESC_DEVICE, 0, 3)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_alt_interface_no + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_alt_interface_no(struct usb_device *udev, struct mtx *mtx, + uint8_t *alt_iface_no, uint8_t iface_index) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) + return (USB_ERR_INVAL); + + req.bmRequestType = UT_READ_INTERFACE; + req.bRequest = UR_GET_INTERFACE; + USETW(req.wValue, 0); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + return (usbd_do_request(udev, mtx, &req, alt_iface_no)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_alt_interface_no + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_alt_interface_no(struct usb_device *udev, struct mtx *mtx, + uint8_t iface_index, uint8_t alt_no) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) + return (USB_ERR_INVAL); + + req.bmRequestType = UT_WRITE_INTERFACE; + req.bRequest = UR_SET_INTERFACE; + req.wValue[0] = alt_no; + req.wValue[1] = 0; + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_device_status + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_device_status(struct usb_device *udev, struct mtx *mtx, + struct usb_status *st) +{ + struct usb_device_request req; + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(*st)); + return (usbd_do_request(udev, mtx, &req, st)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_hub_descriptor + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_hub_descriptor(struct usb_device *udev, struct mtx *mtx, + struct usb_hub_descriptor *hd, uint8_t nports) +{ + struct usb_device_request req; + uint16_t len = (nports + 7 + (8 * 8)) / 8; + + req.bmRequestType = UT_READ_CLASS_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, UDESC_HUB, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + return (usbd_do_request(udev, mtx, &req, hd)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_ss_hub_descriptor + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_ss_hub_descriptor(struct usb_device *udev, struct mtx *mtx, + struct usb_hub_ss_descriptor *hd, uint8_t nports) +{ + struct usb_device_request req; + uint16_t len = sizeof(*hd) - 32 + 1 + ((nports + 7) / 8); + + req.bmRequestType = UT_READ_CLASS_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, UDESC_SS_HUB, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + return (usbd_do_request(udev, mtx, &req, hd)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_hub_status + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_hub_status(struct usb_device *udev, struct mtx *mtx, + struct usb_hub_status *st) +{ + struct usb_device_request req; + + req.bmRequestType = UT_READ_CLASS_DEVICE; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(struct usb_hub_status)); + return (usbd_do_request(udev, mtx, &req, st)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_address + * + * This function is used to set the address for an USB device. After + * port reset the USB device will respond at address zero. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_address(struct usb_device *udev, struct mtx *mtx, uint16_t addr) +{ + struct usb_device_request req; + usb_error_t err; + + DPRINTFN(6, "setting device address=%d\n", addr); + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_ADDRESS; + USETW(req.wValue, addr); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + err = USB_ERR_INVAL; + + /* check if USB controller handles set address */ + if (udev->bus->methods->set_address != NULL) + err = (udev->bus->methods->set_address) (udev, mtx, addr); + + if (err != USB_ERR_INVAL) + goto done; + + /* Setting the address should not take more than 1 second ! */ + err = usbd_do_request_flags(udev, mtx, &req, NULL, + USB_DELAY_STATUS_STAGE, NULL, 1000); + +done: + /* allow device time to set new address */ + usb_pause_mtx(mtx, + USB_MS_TO_TICKS(USB_SET_ADDRESS_SETTLE)); + + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_port_status + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_port_status(struct usb_device *udev, struct mtx *mtx, + struct usb_port_status *ps, uint8_t port) +{ + struct usb_device_request req; + + req.bmRequestType = UT_READ_CLASS_OTHER; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, sizeof *ps); + return (usbd_do_request(udev, mtx, &req, ps)); +} + +/*------------------------------------------------------------------------* + * usbd_req_clear_hub_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_clear_hub_feature(struct usb_device *udev, struct mtx *mtx, + uint16_t sel) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_DEVICE; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_hub_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_hub_feature(struct usb_device *udev, struct mtx *mtx, + uint16_t sel) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_DEVICE; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_hub_u1_timeout + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_hub_u1_timeout(struct usb_device *udev, struct mtx *mtx, + uint8_t port, uint8_t timeout) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, UHF_PORT_U1_TIMEOUT); + req.wIndex[0] = port; + req.wIndex[1] = timeout; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_hub_u2_timeout + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_hub_u2_timeout(struct usb_device *udev, struct mtx *mtx, + uint8_t port, uint8_t timeout) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, UHF_PORT_U2_TIMEOUT); + req.wIndex[0] = port; + req.wIndex[1] = timeout; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_hub_depth + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_hub_depth(struct usb_device *udev, struct mtx *mtx, + uint16_t depth) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_DEVICE; + req.bRequest = UR_SET_HUB_DEPTH; + USETW(req.wValue, depth); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_clear_port_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_clear_port_feature(struct usb_device *udev, struct mtx *mtx, + uint8_t port, uint16_t sel) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, sel); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_port_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_port_feature(struct usb_device *udev, struct mtx *mtx, + uint8_t port, uint16_t sel) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, sel); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_protocol + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_protocol(struct usb_device *udev, struct mtx *mtx, + uint8_t iface_index, uint16_t report) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "iface=%p, report=%d, endpt=%d\n", + iface, report, iface->idesc->bInterfaceNumber); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_PROTOCOL; + USETW(req.wValue, report); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_report + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_report(struct usb_device *udev, struct mtx *mtx, void *data, uint16_t len, + uint8_t iface_index, uint8_t type, uint8_t id) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "len=%d\n", len); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, type, id); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, len); + return (usbd_do_request(udev, mtx, &req, data)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_report + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_report(struct usb_device *udev, struct mtx *mtx, void *data, + uint16_t len, uint8_t iface_index, uint8_t type, uint8_t id) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "len=%d\n", len); + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_REPORT; + USETW2(req.wValue, type, id); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, len); + return (usbd_do_request(udev, mtx, &req, data)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_idle + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_idle(struct usb_device *udev, struct mtx *mtx, + uint8_t iface_index, uint8_t duration, uint8_t id) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "%d %d\n", duration, id); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_IDLE; + USETW2(req.wValue, duration, id); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_report_descriptor + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_report_descriptor(struct usb_device *udev, struct mtx *mtx, + void *d, uint16_t size, uint8_t iface_index) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + req.bmRequestType = UT_READ_INTERFACE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, UDESC_REPORT, 0); /* report id should be 0 */ + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, size); + return (usbd_do_request(udev, mtx, &req, d)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_config + * + * This function is used to select the current configuration number in + * both USB device side mode and USB host side mode. When setting the + * configuration the function of the interfaces can change. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_config(struct usb_device *udev, struct mtx *mtx, uint8_t conf) +{ + struct usb_device_request req; + + DPRINTF("setting config %d\n", conf); + + /* do "set configuration" request */ + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_CONFIG; + req.wValue[0] = conf; + req.wValue[1] = 0; + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_config + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_config(struct usb_device *udev, struct mtx *mtx, uint8_t *pconf) +{ + struct usb_device_request req; + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_CONFIG; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + return (usbd_do_request(udev, mtx, &req, pconf)); +} + +/*------------------------------------------------------------------------* + * usbd_setup_device_desc + *------------------------------------------------------------------------*/ +usb_error_t +usbd_setup_device_desc(struct usb_device *udev, struct mtx *mtx) +{ + usb_error_t err; + + /* + * Get the first 8 bytes of the device descriptor ! + * + * NOTE: "usbd_do_request()" will check the device descriptor + * next time we do a request to see if the maximum packet size + * changed! The 8 first bytes of the device descriptor + * contains the maximum packet size to use on control endpoint + * 0. If this value is different from "USB_MAX_IPACKET" a new + * USB control request will be setup! + */ + switch (udev->speed) { + case USB_SPEED_FULL: + case USB_SPEED_LOW: + err = usbd_req_get_desc(udev, mtx, NULL, &udev->ddesc, + USB_MAX_IPACKET, USB_MAX_IPACKET, 0, UDESC_DEVICE, 0, 0); + if (err != 0) { + DPRINTFN(0, "getting device descriptor " + "at addr %d failed, %s\n", udev->address, + usbd_errstr(err)); + return (err); + } + break; + default: + DPRINTF("Minimum MaxPacketSize is large enough " + "to hold the complete device descriptor\n"); + break; + } + + /* get the full device descriptor */ + err = usbd_req_get_device_desc(udev, mtx, &udev->ddesc); + + /* try one more time, if error */ + if (err) + err = usbd_req_get_device_desc(udev, mtx, &udev->ddesc); + + if (err) { + DPRINTF("addr=%d, getting full desc failed\n", + udev->address); + return (err); + } + + DPRINTF("adding unit addr=%d, rev=%02x, class=%d, " + "subclass=%d, protocol=%d, maxpacket=%d, len=%d, speed=%d\n", + udev->address, UGETW(udev->ddesc.bcdUSB), + udev->ddesc.bDeviceClass, + udev->ddesc.bDeviceSubClass, + udev->ddesc.bDeviceProtocol, + udev->ddesc.bMaxPacketSize, + udev->ddesc.bLength, + udev->speed); + + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_re_enumerate + * + * NOTE: After this function returns the hardware is in the + * unconfigured state! The application is responsible for setting a + * new configuration. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_re_enumerate(struct usb_device *udev, struct mtx *mtx) +{ + struct usb_device *parent_hub; + usb_error_t err; + uint8_t old_addr; + uint8_t do_retry = 1; + + if (udev->flags.usb_mode != USB_MODE_HOST) { + return (USB_ERR_INVAL); + } + old_addr = udev->address; + parent_hub = udev->parent_hub; + if (parent_hub == NULL) { + return (USB_ERR_INVAL); + } +retry: + /* + * Try to reset the High Speed parent HUB of a LOW- or FULL- + * speed device, if any. + */ + if (udev->parent_hs_hub != NULL && + udev->speed != USB_SPEED_HIGH) { + DPRINTF("Trying to reset parent High Speed TT.\n"); + err = usbd_req_reset_tt(udev->parent_hs_hub, NULL, + udev->hs_port_no); + if (err) { + DPRINTF("Resetting parent High " + "Speed TT failed (%s).\n", + usbd_errstr(err)); + } + } + + /* Try to warm reset first */ + if (parent_hub->speed == USB_SPEED_SUPER) + usbd_req_warm_reset_port(parent_hub, mtx, udev->port_no); + + /* Try to reset the parent HUB port. */ + err = usbd_req_reset_port(parent_hub, mtx, udev->port_no); + if (err) { + DPRINTFN(0, "addr=%d, port reset failed, %s\n", + old_addr, usbd_errstr(err)); + goto done; + } + + /* + * After that the port has been reset our device should be at + * address zero: + */ + udev->address = USB_START_ADDR; + + /* reset "bMaxPacketSize" */ + udev->ddesc.bMaxPacketSize = USB_MAX_IPACKET; + + /* reset USB state */ + usb_set_device_state(udev, USB_STATE_POWERED); + + /* + * Restore device address: + */ + err = usbd_req_set_address(udev, mtx, old_addr); + if (err) { + /* XXX ignore any errors! */ + DPRINTFN(0, "addr=%d, set address failed! (%s, ignored)\n", + old_addr, usbd_errstr(err)); + } + /* + * Restore device address, if the controller driver did not + * set a new one: + */ + if (udev->address == USB_START_ADDR) + udev->address = old_addr; + + /* setup the device descriptor and the initial "wMaxPacketSize" */ + err = usbd_setup_device_desc(udev, mtx); + +done: + if (err && do_retry) { + /* give the USB firmware some time to load */ + usb_pause_mtx(mtx, hz / 2); + /* no more retries after this retry */ + do_retry = 0; + /* try again */ + goto retry; + } + /* restore address */ + if (udev->address == USB_START_ADDR) + udev->address = old_addr; + /* update state, if successful */ + if (err == 0) + usb_set_device_state(udev, USB_STATE_ADDRESSED); + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_clear_device_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_clear_device_feature(struct usb_device *udev, struct mtx *mtx, + uint16_t sel) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_device_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_device_feature(struct usb_device *udev, struct mtx *mtx, + uint16_t sel) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_reset_tt + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_reset_tt(struct usb_device *udev, struct mtx *mtx, + uint8_t port) +{ + struct usb_device_request req; + + /* For single TT HUBs the port should be 1 */ + + if (udev->ddesc.bDeviceClass == UDCLASS_HUB && + udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBSTT) + port = 1; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_RESET_TT; + USETW(req.wValue, 0); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_clear_tt_buffer + * + * For single TT HUBs the port should be 1. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_clear_tt_buffer(struct usb_device *udev, struct mtx *mtx, + uint8_t port, uint8_t addr, uint8_t type, uint8_t endpoint) +{ + struct usb_device_request req; + uint16_t wValue; + + /* For single TT HUBs the port should be 1 */ + + if (udev->ddesc.bDeviceClass == UDCLASS_HUB && + udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBSTT) + port = 1; + + wValue = (endpoint & 0xF) | ((addr & 0x7F) << 4) | + ((endpoint & 0x80) << 8) | ((type & 3) << 12); + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_CLEAR_TT_BUFFER; + USETW(req.wValue, wValue); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_port_link_state + * + * USB 3.0 specific request + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_port_link_state(struct usb_device *udev, struct mtx *mtx, + uint8_t port, uint8_t link_state) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, UHF_PORT_LINK_STATE); + req.wIndex[0] = port; + req.wIndex[1] = link_state; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} diff --git a/sys/bus/u4b/usb_request.h b/sys/bus/u4b/usb_request.h new file mode 100644 index 0000000000..74823af288 --- /dev/null +++ b/sys/bus/u4b/usb_request.h @@ -0,0 +1,95 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_REQUEST_H_ +#define _USB_REQUEST_H_ + +struct usb_process; + +usb_error_t usbd_req_clear_hub_feature(struct usb_device *udev, + struct mtx *mtx, uint16_t sel); +usb_error_t usbd_req_clear_port_feature(struct usb_device *udev, + struct mtx *mtx, uint8_t port, uint16_t sel); +usb_error_t usbd_req_get_alt_interface_no(struct usb_device *udev, + struct mtx *mtx, uint8_t *alt_iface_no, + uint8_t iface_index); +usb_error_t usbd_req_get_config(struct usb_device *udev, struct mtx *mtx, + uint8_t *pconf); +usb_error_t usbd_req_get_descriptor_ptr(struct usb_device *udev, + struct usb_config_descriptor **ppcd, uint16_t wValue); +usb_error_t usbd_req_get_config_desc(struct usb_device *udev, struct mtx *mtx, + struct usb_config_descriptor *d, uint8_t conf_index); +usb_error_t usbd_req_get_config_desc_full(struct usb_device *udev, + struct mtx *mtx, struct usb_config_descriptor **ppcd, + struct malloc_type *mtype, uint8_t conf_index); +usb_error_t usbd_req_get_desc(struct usb_device *udev, struct mtx *mtx, + uint16_t *actlen, void *desc, uint16_t min_len, + uint16_t max_len, uint16_t id, uint8_t type, + uint8_t index, uint8_t retries); +usb_error_t usbd_req_get_device_desc(struct usb_device *udev, struct mtx *mtx, + struct usb_device_descriptor *d); +usb_error_t usbd_req_get_device_status(struct usb_device *udev, + struct mtx *mtx, struct usb_status *st); +usb_error_t usbd_req_get_hub_descriptor(struct usb_device *udev, + struct mtx *mtx, struct usb_hub_descriptor *hd, + uint8_t nports); +usb_error_t usbd_req_get_ss_hub_descriptor(struct usb_device *udev, + struct mtx *mtx, struct usb_hub_ss_descriptor *hd, + uint8_t nports); +usb_error_t usbd_req_get_hub_status(struct usb_device *udev, struct mtx *mtx, + struct usb_hub_status *st); +usb_error_t usbd_req_get_port_status(struct usb_device *udev, struct mtx *mtx, + struct usb_port_status *ps, uint8_t port); +usb_error_t usbd_req_reset_port(struct usb_device *udev, struct mtx *mtx, + uint8_t port); +usb_error_t usbd_req_warm_reset_port(struct usb_device *udev, + struct mtx *mtx, uint8_t port); +usb_error_t usbd_req_set_address(struct usb_device *udev, struct mtx *mtx, + uint16_t addr); +usb_error_t usbd_req_set_hub_feature(struct usb_device *udev, struct mtx *mtx, + uint16_t sel); +usb_error_t usbd_req_set_port_feature(struct usb_device *udev, + struct mtx *mtx, uint8_t port, uint16_t sel); +usb_error_t usbd_setup_device_desc(struct usb_device *udev, struct mtx *mtx); +usb_error_t usbd_req_re_enumerate(struct usb_device *udev, struct mtx *mtx); +usb_error_t usbd_req_clear_device_feature(struct usb_device *udev, + struct mtx *mtx, uint16_t sel); +usb_error_t usbd_req_set_device_feature(struct usb_device *udev, + struct mtx *mtx, uint16_t sel); +usb_error_t usbd_req_set_hub_u1_timeout(struct usb_device *udev, + struct mtx *mtx, uint8_t port, uint8_t timeout); +usb_error_t usbd_req_set_hub_u2_timeout(struct usb_device *udev, + struct mtx *mtx, uint8_t port, uint8_t timeout); +usb_error_t usbd_req_set_hub_depth(struct usb_device *udev, + struct mtx *mtx, uint16_t depth); +usb_error_t usbd_req_reset_tt(struct usb_device *udev, struct mtx *mtx, + uint8_t port); +usb_error_t usbd_req_clear_tt_buffer(struct usb_device *udev, struct mtx *mtx, + uint8_t port, uint8_t addr, uint8_t type, uint8_t endpoint); +usb_error_t usbd_req_set_port_link_state(struct usb_device *udev, + struct mtx *mtx, uint8_t port, uint8_t link_state); + +#endif /* _USB_REQUEST_H_ */ diff --git a/sys/bus/u4b/usb_transfer.c b/sys/bus/u4b/usb_transfer.c new file mode 100644 index 0000000000..e9482998dd --- /dev/null +++ b/sys/bus/u4b/usb_transfer.c @@ -0,0 +1,3320 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define USB_DEBUG_VAR usb_debug + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +struct usb_std_packet_size { + struct { + uint16_t min; /* inclusive */ + uint16_t max; /* inclusive */ + } range; + + uint16_t fixed[4]; +}; + +static usb_callback_t usb_request_callback; + +static const struct usb_config usb_control_ep_cfg[USB_CTRL_XFER_MAX] = { + + /* This transfer is used for generic control endpoint transfers */ + + [0] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control endpoint */ + .direction = UE_DIR_ANY, + .bufsize = USB_EP0_BUFSIZE, /* bytes */ + .flags = {.proxy_buffer = 1,}, + .callback = &usb_request_callback, + .usb_mode = USB_MODE_DUAL, /* both modes */ + }, + + /* This transfer is used for generic clear stall only */ + + [1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &usb_do_clear_stall_callback, + .timeout = 1000, /* 1 second */ + .interval = 50, /* 50ms */ + .usb_mode = USB_MODE_HOST, + }, +}; + +/* function prototypes */ + +static void usbd_update_max_frame_size(struct usb_xfer *); +static void usbd_transfer_unsetup_sub(struct usb_xfer_root *, uint8_t); +static void usbd_control_transfer_init(struct usb_xfer *); +static int usbd_setup_ctrl_transfer(struct usb_xfer *); +static void usb_callback_proc(struct usb_proc_msg *); +static void usbd_callback_ss_done_defer(struct usb_xfer *); +static void usbd_callback_wrapper(struct usb_xfer_queue *); +static void usbd_transfer_start_cb(void *); +static uint8_t usbd_callback_wrapper_sub(struct usb_xfer *); +static void usbd_get_std_packet_size(struct usb_std_packet_size *ptr, + uint8_t type, enum usb_dev_speed speed); + +/*------------------------------------------------------------------------* + * usb_request_callback + *------------------------------------------------------------------------*/ +static void +usb_request_callback(struct usb_xfer *xfer, usb_error_t error) +{ + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) + usb_handle_request_callback(xfer, error); + else + usbd_do_request_callback(xfer, error); +} + +/*------------------------------------------------------------------------* + * usbd_update_max_frame_size + * + * This function updates the maximum frame size, hence high speed USB + * can transfer multiple consecutive packets. + *------------------------------------------------------------------------*/ +static void +usbd_update_max_frame_size(struct usb_xfer *xfer) +{ + /* compute maximum frame size */ + /* this computation should not overflow 16-bit */ + /* max = 15 * 1024 */ + + xfer->max_frame_size = xfer->max_packet_size * xfer->max_packet_count; +} + +/*------------------------------------------------------------------------* + * usbd_get_dma_delay + * + * The following function is called when we need to + * synchronize with DMA hardware. + * + * Returns: + * 0: no DMA delay required + * Else: milliseconds of DMA delay + *------------------------------------------------------------------------*/ +usb_timeout_t +usbd_get_dma_delay(struct usb_device *udev) +{ + struct usb_bus_methods *mtod; + uint32_t temp; + + mtod = udev->bus->methods; + temp = 0; + + if (mtod->get_dma_delay) { + (mtod->get_dma_delay) (udev, &temp); + /* + * Round up and convert to milliseconds. Note that we use + * 1024 milliseconds per second. to save a division. + */ + temp += 0x3FF; + temp /= 0x400; + } + return (temp); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_setup_sub_malloc + * + * This function will allocate one or more DMA'able memory chunks + * according to "size", "align" and "count" arguments. "ppc" is + * pointed to a linear array of USB page caches afterwards. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +#if USB_HAVE_BUSDMA +uint8_t +usbd_transfer_setup_sub_malloc(struct usb_setup_params *parm, + struct usb_page_cache **ppc, usb_size_t size, usb_size_t align, + usb_size_t count) +{ + struct usb_page_cache *pc; + struct usb_page *pg; + void *buf; + usb_size_t n_dma_pc; + usb_size_t n_obj; + usb_size_t x; + usb_size_t y; + usb_size_t r; + usb_size_t z; + + USB_ASSERT(align > 1, ("Invalid alignment, 0x%08x\n", + align)); + USB_ASSERT(size > 0, ("Invalid size = 0\n")); + + if (count == 0) { + return (0); /* nothing to allocate */ + } + /* + * Make sure that the size is aligned properly. + */ + size = -((-size) & (-align)); + + /* + * Try multi-allocation chunks to reduce the number of DMA + * allocations, hence DMA allocations are slow. + */ + if (size >= PAGE_SIZE) { + n_dma_pc = count; + n_obj = 1; + } else { + /* compute number of objects per page */ + n_obj = (PAGE_SIZE / size); + /* + * Compute number of DMA chunks, rounded up + * to nearest one: + */ + n_dma_pc = ((count + n_obj - 1) / n_obj); + } + + if (parm->buf == NULL) { + /* for the future */ + parm->dma_page_ptr += n_dma_pc; + parm->dma_page_cache_ptr += n_dma_pc; + parm->dma_page_ptr += count; + parm->xfer_page_cache_ptr += count; + return (0); + } + for (x = 0; x != n_dma_pc; x++) { + /* need to initialize the page cache */ + parm->dma_page_cache_ptr[x].tag_parent = + &parm->curr_xfer->xroot->dma_parent_tag; + } + for (x = 0; x != count; x++) { + /* need to initialize the page cache */ + parm->xfer_page_cache_ptr[x].tag_parent = + &parm->curr_xfer->xroot->dma_parent_tag; + } + + if (ppc) { + *ppc = parm->xfer_page_cache_ptr; + } + r = count; /* set remainder count */ + z = n_obj * size; /* set allocation size */ + pc = parm->xfer_page_cache_ptr; + pg = parm->dma_page_ptr; + + for (x = 0; x != n_dma_pc; x++) { + + if (r < n_obj) { + /* compute last remainder */ + z = r * size; + n_obj = r; + } + if (usb_pc_alloc_mem(parm->dma_page_cache_ptr, + pg, z, align)) { + return (1); /* failure */ + } + /* Set beginning of current buffer */ + buf = parm->dma_page_cache_ptr->buffer; + /* Make room for one DMA page cache and one page */ + parm->dma_page_cache_ptr++; + pg++; + + for (y = 0; (y != n_obj); y++, r--, pc++, pg++) { + + /* Load sub-chunk into DMA */ + if (usb_pc_dmamap_create(pc, size)) { + return (1); /* failure */ + } + pc->buffer = USB_ADD_BYTES(buf, y * size); + pc->page_start = pg; + + mtx_lock(pc->tag_parent->mtx); + if (usb_pc_load_mem(pc, size, 1 /* synchronous */ )) { + mtx_unlock(pc->tag_parent->mtx); + return (1); /* failure */ + } + mtx_unlock(pc->tag_parent->mtx); + } + } + + parm->xfer_page_cache_ptr = pc; + parm->dma_page_ptr = pg; + return (0); +} +#endif + +/*------------------------------------------------------------------------* + * usbd_transfer_setup_sub - transfer setup subroutine + * + * This function must be called from the "xfer_setup" callback of the + * USB Host or Device controller driver when setting up an USB + * transfer. This function will setup correct packet sizes, buffer + * sizes, flags and more, that are stored in the "usb_xfer" + * structure. + *------------------------------------------------------------------------*/ +void +usbd_transfer_setup_sub(struct usb_setup_params *parm) +{ + enum { + REQ_SIZE = 8, + MIN_PKT = 8, + }; + struct usb_xfer *xfer = parm->curr_xfer; + const struct usb_config *setup = parm->curr_setup; + struct usb_endpoint_ss_comp_descriptor *ecomp; + struct usb_endpoint_descriptor *edesc; + struct usb_std_packet_size std_size; + usb_frcount_t n_frlengths; + usb_frcount_t n_frbuffers; + usb_frcount_t x; + uint8_t type; + uint8_t zmps; + + /* + * Sanity check. The following parameters must be initialized before + * calling this function. + */ + if ((parm->hc_max_packet_size == 0) || + (parm->hc_max_packet_count == 0) || + (parm->hc_max_frame_size == 0)) { + parm->err = USB_ERR_INVAL; + goto done; + } + edesc = xfer->endpoint->edesc; + ecomp = xfer->endpoint->ecomp; + + type = (edesc->bmAttributes & UE_XFERTYPE); + + xfer->flags = setup->flags; + xfer->nframes = setup->frames; + xfer->timeout = setup->timeout; + xfer->callback = setup->callback; + xfer->interval = setup->interval; + xfer->endpointno = edesc->bEndpointAddress; + xfer->max_packet_size = UGETW(edesc->wMaxPacketSize); + xfer->max_packet_count = 1; + /* make a shadow copy: */ + xfer->flags_int.usb_mode = parm->udev->flags.usb_mode; + + parm->bufsize = setup->bufsize; + + switch (parm->speed) { + case USB_SPEED_HIGH: + switch (type) { + case UE_ISOCHRONOUS: + case UE_INTERRUPT: + xfer->max_packet_count += (xfer->max_packet_size >> 11) & 3; + + /* check for invalid max packet count */ + if (xfer->max_packet_count > 3) + xfer->max_packet_count = 3; + break; + default: + break; + } + xfer->max_packet_size &= 0x7FF; + break; + case USB_SPEED_SUPER: + xfer->max_packet_count += (xfer->max_packet_size >> 11) & 3; + + if (ecomp != NULL) + xfer->max_packet_count += ecomp->bMaxBurst; + + if ((xfer->max_packet_count == 0) || + (xfer->max_packet_count > 16)) + xfer->max_packet_count = 16; + + switch (type) { + case UE_CONTROL: + xfer->max_packet_count = 1; + break; + case UE_ISOCHRONOUS: + if (ecomp != NULL) { + uint8_t mult; + + mult = (ecomp->bmAttributes & 3) + 1; + if (mult > 3) + mult = 3; + + xfer->max_packet_count *= mult; + } + break; + default: + break; + } + xfer->max_packet_size &= 0x7FF; + break; + default: + break; + } + /* range check "max_packet_count" */ + + if (xfer->max_packet_count > parm->hc_max_packet_count) { + xfer->max_packet_count = parm->hc_max_packet_count; + } + /* filter "wMaxPacketSize" according to HC capabilities */ + + if ((xfer->max_packet_size > parm->hc_max_packet_size) || + (xfer->max_packet_size == 0)) { + xfer->max_packet_size = parm->hc_max_packet_size; + } + /* filter "wMaxPacketSize" according to standard sizes */ + + usbd_get_std_packet_size(&std_size, type, parm->speed); + + if (std_size.range.min || std_size.range.max) { + + if (xfer->max_packet_size < std_size.range.min) { + xfer->max_packet_size = std_size.range.min; + } + if (xfer->max_packet_size > std_size.range.max) { + xfer->max_packet_size = std_size.range.max; + } + } else { + + if (xfer->max_packet_size >= std_size.fixed[3]) { + xfer->max_packet_size = std_size.fixed[3]; + } else if (xfer->max_packet_size >= std_size.fixed[2]) { + xfer->max_packet_size = std_size.fixed[2]; + } else if (xfer->max_packet_size >= std_size.fixed[1]) { + xfer->max_packet_size = std_size.fixed[1]; + } else { + /* only one possibility left */ + xfer->max_packet_size = std_size.fixed[0]; + } + } + + /* compute "max_frame_size" */ + + usbd_update_max_frame_size(xfer); + + /* check interrupt interval and transfer pre-delay */ + + if (type == UE_ISOCHRONOUS) { + + uint16_t frame_limit; + + xfer->interval = 0; /* not used, must be zero */ + xfer->flags_int.isochronous_xfr = 1; /* set flag */ + + if (xfer->timeout == 0) { + /* + * set a default timeout in + * case something goes wrong! + */ + xfer->timeout = 1000 / 4; + } + switch (parm->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + frame_limit = USB_MAX_FS_ISOC_FRAMES_PER_XFER; + xfer->fps_shift = 0; + break; + default: + frame_limit = USB_MAX_HS_ISOC_FRAMES_PER_XFER; + xfer->fps_shift = edesc->bInterval; + if (xfer->fps_shift > 0) + xfer->fps_shift--; + if (xfer->fps_shift > 3) + xfer->fps_shift = 3; + if (xfer->flags.pre_scale_frames != 0) + xfer->nframes <<= (3 - xfer->fps_shift); + break; + } + + if (xfer->nframes > frame_limit) { + /* + * this is not going to work + * cross hardware + */ + parm->err = USB_ERR_INVAL; + goto done; + } + if (xfer->nframes == 0) { + /* + * this is not a valid value + */ + parm->err = USB_ERR_ZERO_NFRAMES; + goto done; + } + } else { + + /* + * If a value is specified use that else check the + * endpoint descriptor! + */ + if (type == UE_INTERRUPT) { + + uint32_t temp; + + if (xfer->interval == 0) { + + xfer->interval = edesc->bInterval; + + switch (parm->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + break; + default: + /* 125us -> 1ms */ + if (xfer->interval < 4) + xfer->interval = 1; + else if (xfer->interval > 16) + xfer->interval = (1 << (16 - 4)); + else + xfer->interval = + (1 << (xfer->interval - 4)); + break; + } + } + + if (xfer->interval == 0) { + /* + * One millisecond is the smallest + * interval we support: + */ + xfer->interval = 1; + } + + xfer->fps_shift = 0; + temp = 1; + + while ((temp != 0) && (temp < xfer->interval)) { + xfer->fps_shift++; + temp *= 2; + } + + switch (parm->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + break; + default: + xfer->fps_shift += 3; + break; + } + } + } + + /* + * NOTE: we do not allow "max_packet_size" or "max_frame_size" + * to be equal to zero when setting up USB transfers, hence + * this leads to alot of extra code in the USB kernel. + */ + + if ((xfer->max_frame_size == 0) || + (xfer->max_packet_size == 0)) { + + zmps = 1; + + if ((parm->bufsize <= MIN_PKT) && + (type != UE_CONTROL) && + (type != UE_BULK)) { + + /* workaround */ + xfer->max_packet_size = MIN_PKT; + xfer->max_packet_count = 1; + parm->bufsize = 0; /* automatic setup length */ + usbd_update_max_frame_size(xfer); + + } else { + parm->err = USB_ERR_ZERO_MAXP; + goto done; + } + + } else { + zmps = 0; + } + + /* + * check if we should setup a default + * length: + */ + + if (parm->bufsize == 0) { + + parm->bufsize = xfer->max_frame_size; + + if (type == UE_ISOCHRONOUS) { + parm->bufsize *= xfer->nframes; + } + } + /* + * check if we are about to setup a proxy + * type of buffer: + */ + + if (xfer->flags.proxy_buffer) { + + /* round bufsize up */ + + parm->bufsize += (xfer->max_frame_size - 1); + + if (parm->bufsize < xfer->max_frame_size) { + /* length wrapped around */ + parm->err = USB_ERR_INVAL; + goto done; + } + /* subtract remainder */ + + parm->bufsize -= (parm->bufsize % xfer->max_frame_size); + + /* add length of USB device request structure, if any */ + + if (type == UE_CONTROL) { + parm->bufsize += REQ_SIZE; /* SETUP message */ + } + } + xfer->max_data_length = parm->bufsize; + + /* Setup "n_frlengths" and "n_frbuffers" */ + + if (type == UE_ISOCHRONOUS) { + n_frlengths = xfer->nframes; + n_frbuffers = 1; + } else { + + if (type == UE_CONTROL) { + xfer->flags_int.control_xfr = 1; + if (xfer->nframes == 0) { + if (parm->bufsize <= REQ_SIZE) { + /* + * there will never be any data + * stage + */ + xfer->nframes = 1; + } else { + xfer->nframes = 2; + } + } + } else { + if (xfer->nframes == 0) { + xfer->nframes = 1; + } + } + + n_frlengths = xfer->nframes; + n_frbuffers = xfer->nframes; + } + + /* + * check if we have room for the + * USB device request structure: + */ + + if (type == UE_CONTROL) { + + if (xfer->max_data_length < REQ_SIZE) { + /* length wrapped around or too small bufsize */ + parm->err = USB_ERR_INVAL; + goto done; + } + xfer->max_data_length -= REQ_SIZE; + } + /* + * Setup "frlengths" and shadow "frlengths" for keeping the + * initial frame lengths when a USB transfer is complete. This + * information is useful when computing isochronous offsets. + */ + xfer->frlengths = parm->xfer_length_ptr; + parm->xfer_length_ptr += 2 * n_frlengths; + + /* setup "frbuffers" */ + xfer->frbuffers = parm->xfer_page_cache_ptr; + parm->xfer_page_cache_ptr += n_frbuffers; + + /* initialize max frame count */ + xfer->max_frame_count = xfer->nframes; + + /* + * check if we need to setup + * a local buffer: + */ + + if (!xfer->flags.ext_buffer) { + + /* align data */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + + if (parm->buf) { + + xfer->local_buffer = + USB_ADD_BYTES(parm->buf, parm->size[0]); + + usbd_xfer_set_frame_offset(xfer, 0, 0); + + if ((type == UE_CONTROL) && (n_frbuffers > 1)) { + usbd_xfer_set_frame_offset(xfer, REQ_SIZE, 1); + } + } + parm->size[0] += parm->bufsize; + + /* align data again */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + } + /* + * Compute maximum buffer size + */ + + if (parm->bufsize_max < parm->bufsize) { + parm->bufsize_max = parm->bufsize; + } +#if USB_HAVE_BUSDMA + if (xfer->flags_int.bdma_enable) { + /* + * Setup "dma_page_ptr". + * + * Proof for formula below: + * + * Assume there are three USB frames having length "a", "b" and + * "c". These USB frames will at maximum need "z" + * "usb_page" structures. "z" is given by: + * + * z = ((a / USB_PAGE_SIZE) + 2) + ((b / USB_PAGE_SIZE) + 2) + + * ((c / USB_PAGE_SIZE) + 2); + * + * Constraining "a", "b" and "c" like this: + * + * (a + b + c) <= parm->bufsize + * + * We know that: + * + * z <= ((parm->bufsize / USB_PAGE_SIZE) + (3*2)); + * + * Here is the general formula: + */ + xfer->dma_page_ptr = parm->dma_page_ptr; + parm->dma_page_ptr += (2 * n_frbuffers); + parm->dma_page_ptr += (parm->bufsize / USB_PAGE_SIZE); + } +#endif + if (zmps) { + /* correct maximum data length */ + xfer->max_data_length = 0; + } + /* subtract USB frame remainder from "hc_max_frame_size" */ + + xfer->max_hc_frame_size = + (parm->hc_max_frame_size - + (parm->hc_max_frame_size % xfer->max_frame_size)); + + if (xfer->max_hc_frame_size == 0) { + parm->err = USB_ERR_INVAL; + goto done; + } + + /* initialize frame buffers */ + + if (parm->buf) { + for (x = 0; x != n_frbuffers; x++) { + xfer->frbuffers[x].tag_parent = + &xfer->xroot->dma_parent_tag; +#if USB_HAVE_BUSDMA + if (xfer->flags_int.bdma_enable && + (parm->bufsize_max > 0)) { + + if (usb_pc_dmamap_create( + xfer->frbuffers + x, + parm->bufsize_max)) { + parm->err = USB_ERR_NOMEM; + goto done; + } + } +#endif + } + } +done: + if (parm->err) { + /* + * Set some dummy values so that we avoid division by zero: + */ + xfer->max_hc_frame_size = 1; + xfer->max_frame_size = 1; + xfer->max_packet_size = 1; + xfer->max_data_length = 0; + xfer->nframes = 0; + xfer->max_frame_count = 0; + } +} + +/*------------------------------------------------------------------------* + * usbd_transfer_setup - setup an array of USB transfers + * + * NOTE: You must always call "usbd_transfer_unsetup" after calling + * "usbd_transfer_setup" if success was returned. + * + * The idea is that the USB device driver should pre-allocate all its + * transfers by one call to this function. + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_transfer_setup(struct usb_device *udev, + const uint8_t *ifaces, struct usb_xfer **ppxfer, + const struct usb_config *setup_start, uint16_t n_setup, + void *priv_sc, struct mtx *xfer_mtx) +{ + struct usb_xfer dummy; + struct usb_setup_params parm; + const struct usb_config *setup_end = setup_start + n_setup; + const struct usb_config *setup; + struct usb_endpoint *ep; + struct usb_xfer_root *info; + struct usb_xfer *xfer; + void *buf = NULL; + uint16_t n; + uint16_t refcount; + + parm.err = 0; + refcount = 0; + info = NULL; + + WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, + "usbd_transfer_setup can sleep!"); + + /* do some checking first */ + + if (n_setup == 0) { + DPRINTFN(6, "setup array has zero length!\n"); + return (USB_ERR_INVAL); + } + if (ifaces == 0) { + DPRINTFN(6, "ifaces array is NULL!\n"); + return (USB_ERR_INVAL); + } + if (xfer_mtx == NULL) { + DPRINTFN(6, "using global lock\n"); + xfer_mtx = &Giant; + } + /* sanity checks */ + for (setup = setup_start, n = 0; + setup != setup_end; setup++, n++) { + if (setup->bufsize == (usb_frlength_t)-1) { + parm.err = USB_ERR_BAD_BUFSIZE; + DPRINTF("invalid bufsize\n"); + } + if (setup->callback == NULL) { + parm.err = USB_ERR_NO_CALLBACK; + DPRINTF("no callback\n"); + } + ppxfer[n] = NULL; + } + + if (parm.err) { + goto done; + } + memset(&parm, 0, sizeof(parm)); + + parm.udev = udev; + parm.speed = usbd_get_speed(udev); + parm.hc_max_packet_count = 1; + + if (parm.speed >= USB_SPEED_MAX) { + parm.err = USB_ERR_INVAL; + goto done; + } + /* setup all transfers */ + + while (1) { + + if (buf) { + /* + * Initialize the "usb_xfer_root" structure, + * which is common for all our USB transfers. + */ + info = USB_ADD_BYTES(buf, 0); + + info->memory_base = buf; + info->memory_size = parm.size[0]; + +#if USB_HAVE_BUSDMA + info->dma_page_cache_start = USB_ADD_BYTES(buf, parm.size[4]); + info->dma_page_cache_end = USB_ADD_BYTES(buf, parm.size[5]); +#endif + info->xfer_page_cache_start = USB_ADD_BYTES(buf, parm.size[5]); + info->xfer_page_cache_end = USB_ADD_BYTES(buf, parm.size[2]); + + cv_init(&info->cv_drain, "WDRAIN"); + + info->xfer_mtx = xfer_mtx; +#if USB_HAVE_BUSDMA + usb_dma_tag_setup(&info->dma_parent_tag, + parm.dma_tag_p, udev->bus->dma_parent_tag[0].tag, + xfer_mtx, &usb_bdma_done_event, 32, parm.dma_tag_max); +#endif + + info->bus = udev->bus; + info->udev = udev; + + TAILQ_INIT(&info->done_q.head); + info->done_q.command = &usbd_callback_wrapper; +#if USB_HAVE_BUSDMA + TAILQ_INIT(&info->dma_q.head); + info->dma_q.command = &usb_bdma_work_loop; +#endif + info->done_m[0].hdr.pm_callback = &usb_callback_proc; + info->done_m[0].xroot = info; + info->done_m[1].hdr.pm_callback = &usb_callback_proc; + info->done_m[1].xroot = info; + + /* + * In device side mode control endpoint + * requests need to run from a separate + * context, else there is a chance of + * deadlock! + */ + if (setup_start == usb_control_ep_cfg) + info->done_p = + &udev->bus->control_xfer_proc; + else if (xfer_mtx == &Giant) + info->done_p = + &udev->bus->giant_callback_proc; + else + info->done_p = + &udev->bus->non_giant_callback_proc; + } + /* reset sizes */ + + parm.size[0] = 0; + parm.buf = buf; + parm.size[0] += sizeof(info[0]); + + for (setup = setup_start, n = 0; + setup != setup_end; setup++, n++) { + + /* skip USB transfers without callbacks: */ + if (setup->callback == NULL) { + continue; + } + /* see if there is a matching endpoint */ + ep = usbd_get_endpoint(udev, + ifaces[setup->if_index], setup); + + if ((ep == NULL) || (ep->methods == NULL)) { + if (setup->flags.no_pipe_ok) + continue; + if ((setup->usb_mode != USB_MODE_DUAL) && + (setup->usb_mode != udev->flags.usb_mode)) + continue; + parm.err = USB_ERR_NO_PIPE; + goto done; + } + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store current setup pointer */ + parm.curr_setup = setup; + + if (buf) { + /* + * Common initialization of the + * "usb_xfer" structure. + */ + xfer = USB_ADD_BYTES(buf, parm.size[0]); + xfer->address = udev->address; + xfer->priv_sc = priv_sc; + xfer->xroot = info; + + usb_callout_init_mtx(&xfer->timeout_handle, + &udev->bus->bus_mtx, 0); + } else { + /* + * Setup a dummy xfer, hence we are + * writing to the "usb_xfer" + * structure pointed to by "xfer" + * before we have allocated any + * memory: + */ + xfer = &dummy; + memset(&dummy, 0, sizeof(dummy)); + refcount++; + } + + /* set transfer endpoint pointer */ + xfer->endpoint = ep; + + parm.size[0] += sizeof(xfer[0]); + parm.methods = xfer->endpoint->methods; + parm.curr_xfer = xfer; + + /* + * Call the Host or Device controller transfer + * setup routine: + */ + (udev->bus->methods->xfer_setup) (&parm); + + /* check for error */ + if (parm.err) + goto done; + + if (buf) { + /* + * Increment the endpoint refcount. This + * basically prevents setting a new + * configuration and alternate setting + * when USB transfers are in use on + * the given interface. Search the USB + * code for "endpoint->refcount_alloc" if you + * want more information. + */ + USB_BUS_LOCK(info->bus); + if (xfer->endpoint->refcount_alloc >= USB_EP_REF_MAX) + parm.err = USB_ERR_INVAL; + + xfer->endpoint->refcount_alloc++; + + if (xfer->endpoint->refcount_alloc == 0) + panic("usbd_transfer_setup(): Refcount wrapped to zero\n"); + USB_BUS_UNLOCK(info->bus); + + /* + * Whenever we set ppxfer[] then we + * also need to increment the + * "setup_refcount": + */ + info->setup_refcount++; + + /* + * Transfer is successfully setup and + * can be used: + */ + ppxfer[n] = xfer; + } + + /* check for error */ + if (parm.err) + goto done; + } + + if (buf || parm.err) { + goto done; + } + if (refcount == 0) { + /* no transfers - nothing to do ! */ + goto done; + } + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store offset temporarily */ + parm.size[1] = parm.size[0]; + + /* + * The number of DMA tags required depends on + * the number of endpoints. The current estimate + * for maximum number of DMA tags per endpoint + * is two. + */ + parm.dma_tag_max += 2 * MIN(n_setup, USB_EP_MAX); + + /* + * DMA tags for QH, TD, Data and more. + */ + parm.dma_tag_max += 8; + + parm.dma_tag_p += parm.dma_tag_max; + + parm.size[0] += ((uint8_t *)parm.dma_tag_p) - + ((uint8_t *)0); + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store offset temporarily */ + parm.size[3] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.dma_page_ptr) - + ((uint8_t *)0); + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store offset temporarily */ + parm.size[4] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.dma_page_cache_ptr) - + ((uint8_t *)0); + + /* store end offset temporarily */ + parm.size[5] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.xfer_page_cache_ptr) - + ((uint8_t *)0); + + /* store end offset temporarily */ + + parm.size[2] = parm.size[0]; + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + parm.size[6] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.xfer_length_ptr) - + ((uint8_t *)0); + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* allocate zeroed memory */ + buf = malloc(parm.size[0], M_USB, M_WAITOK | M_ZERO); + + if (buf == NULL) { + parm.err = USB_ERR_NOMEM; + DPRINTFN(0, "cannot allocate memory block for " + "configuration (%d bytes)\n", + parm.size[0]); + goto done; + } + parm.dma_tag_p = USB_ADD_BYTES(buf, parm.size[1]); + parm.dma_page_ptr = USB_ADD_BYTES(buf, parm.size[3]); + parm.dma_page_cache_ptr = USB_ADD_BYTES(buf, parm.size[4]); + parm.xfer_page_cache_ptr = USB_ADD_BYTES(buf, parm.size[5]); + parm.xfer_length_ptr = USB_ADD_BYTES(buf, parm.size[6]); + } + +done: + if (buf) { + if (info->setup_refcount == 0) { + /* + * "usbd_transfer_unsetup_sub" will unlock + * the bus mutex before returning ! + */ + USB_BUS_LOCK(info->bus); + + /* something went wrong */ + usbd_transfer_unsetup_sub(info, 0); + } + } + if (parm.err) { + usbd_transfer_unsetup(ppxfer, n_setup); + } + return (parm.err); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_unsetup_sub - factored out code + *------------------------------------------------------------------------*/ +static void +usbd_transfer_unsetup_sub(struct usb_xfer_root *info, uint8_t needs_delay) +{ +#if USB_HAVE_BUSDMA + struct usb_page_cache *pc; +#endif + + USB_BUS_LOCK_ASSERT(info->bus, MA_OWNED); + + /* wait for any outstanding DMA operations */ + + if (needs_delay) { + usb_timeout_t temp; + temp = usbd_get_dma_delay(info->udev); + if (temp != 0) { + usb_pause_mtx(&info->bus->bus_mtx, + USB_MS_TO_TICKS(temp)); + } + } + + /* make sure that our done messages are not queued anywhere */ + usb_proc_mwait(info->done_p, &info->done_m[0], &info->done_m[1]); + + USB_BUS_UNLOCK(info->bus); + +#if USB_HAVE_BUSDMA + /* free DMA'able memory, if any */ + pc = info->dma_page_cache_start; + while (pc != info->dma_page_cache_end) { + usb_pc_free_mem(pc); + pc++; + } + + /* free DMA maps in all "xfer->frbuffers" */ + pc = info->xfer_page_cache_start; + while (pc != info->xfer_page_cache_end) { + usb_pc_dmamap_destroy(pc); + pc++; + } + + /* free all DMA tags */ + usb_dma_tag_unsetup(&info->dma_parent_tag); +#endif + + cv_destroy(&info->cv_drain); + + /* + * free the "memory_base" last, hence the "info" structure is + * contained within the "memory_base"! + */ + free(info->memory_base, M_USB); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_unsetup - unsetup/free an array of USB transfers + * + * NOTE: All USB transfers in progress will get called back passing + * the error code "USB_ERR_CANCELLED" before this function + * returns. + *------------------------------------------------------------------------*/ +void +usbd_transfer_unsetup(struct usb_xfer **pxfer, uint16_t n_setup) +{ + struct usb_xfer *xfer; + struct usb_xfer_root *info; + uint8_t needs_delay = 0; + + WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, + "usbd_transfer_unsetup can sleep!"); + + while (n_setup--) { + xfer = pxfer[n_setup]; + + if (xfer == NULL) + continue; + + info = xfer->xroot; + + USB_XFER_LOCK(xfer); + USB_BUS_LOCK(info->bus); + + /* + * HINT: when you start/stop a transfer, it might be a + * good idea to directly use the "pxfer[]" structure: + * + * usbd_transfer_start(sc->pxfer[0]); + * usbd_transfer_stop(sc->pxfer[0]); + * + * That way, if your code has many parts that will not + * stop running under the same lock, in other words + * "xfer_mtx", the usbd_transfer_start and + * usbd_transfer_stop functions will simply return + * when they detect a NULL pointer argument. + * + * To avoid any races we clear the "pxfer[]" pointer + * while holding the private mutex of the driver: + */ + pxfer[n_setup] = NULL; + + USB_BUS_UNLOCK(info->bus); + USB_XFER_UNLOCK(xfer); + + usbd_transfer_drain(xfer); + +#if USB_HAVE_BUSDMA + if (xfer->flags_int.bdma_enable) + needs_delay = 1; +#endif + /* + * NOTE: default endpoint does not have an + * interface, even if endpoint->iface_index == 0 + */ + USB_BUS_LOCK(info->bus); + xfer->endpoint->refcount_alloc--; + USB_BUS_UNLOCK(info->bus); + + usb_callout_drain(&xfer->timeout_handle); + + USB_BUS_LOCK(info->bus); + + USB_ASSERT(info->setup_refcount != 0, ("Invalid setup " + "reference count\n")); + + info->setup_refcount--; + + if (info->setup_refcount == 0) { + usbd_transfer_unsetup_sub(info, + needs_delay); + } else { + USB_BUS_UNLOCK(info->bus); + } + } +} + +/*------------------------------------------------------------------------* + * usbd_control_transfer_init - factored out code + * + * In USB Device Mode we have to wait for the SETUP packet which + * containst the "struct usb_device_request" structure, before we can + * transfer any data. In USB Host Mode we already have the SETUP + * packet at the moment the USB transfer is started. This leads us to + * having to setup the USB transfer at two different places in + * time. This function just contains factored out control transfer + * initialisation code, so that we don't duplicate the code. + *------------------------------------------------------------------------*/ +static void +usbd_control_transfer_init(struct usb_xfer *xfer) +{ + struct usb_device_request req; + + /* copy out the USB request header */ + + usbd_copy_out(xfer->frbuffers, 0, &req, sizeof(req)); + + /* setup remainder */ + + xfer->flags_int.control_rem = UGETW(req.wLength); + + /* copy direction to endpoint variable */ + + xfer->endpointno &= ~(UE_DIR_IN | UE_DIR_OUT); + xfer->endpointno |= + (req.bmRequestType & UT_READ) ? UE_DIR_IN : UE_DIR_OUT; +} + +/*------------------------------------------------------------------------* + * usbd_setup_ctrl_transfer + * + * This function handles initialisation of control transfers. Control + * transfers are special in that regard that they can both transmit + * and receive data. + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usbd_setup_ctrl_transfer(struct usb_xfer *xfer) +{ + usb_frlength_t len; + + /* Check for control endpoint stall */ + if (xfer->flags.stall_pipe && xfer->flags_int.control_act) { + /* the control transfer is no longer active */ + xfer->flags_int.control_stall = 1; + xfer->flags_int.control_act = 0; + } else { + /* don't stall control transfer by default */ + xfer->flags_int.control_stall = 0; + } + + /* Check for invalid number of frames */ + if (xfer->nframes > 2) { + /* + * If you need to split a control transfer, you + * have to do one part at a time. Only with + * non-control transfers you can do multiple + * parts a time. + */ + DPRINTFN(0, "Too many frames: %u\n", + (unsigned int)xfer->nframes); + goto error; + } + + /* + * Check if there is a control + * transfer in progress: + */ + if (xfer->flags_int.control_act) { + + if (xfer->flags_int.control_hdr) { + + /* clear send header flag */ + + xfer->flags_int.control_hdr = 0; + + /* setup control transfer */ + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + usbd_control_transfer_init(xfer); + } + } + /* get data length */ + + len = xfer->sumlen; + + } else { + + /* the size of the SETUP structure is hardcoded ! */ + + if (xfer->frlengths[0] != sizeof(struct usb_device_request)) { + DPRINTFN(0, "Wrong framelength %u != %zu\n", + xfer->frlengths[0], sizeof(struct + usb_device_request)); + goto error; + } + /* check USB mode */ + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + + /* check number of frames */ + if (xfer->nframes != 1) { + /* + * We need to receive the setup + * message first so that we know the + * data direction! + */ + DPRINTF("Misconfigured transfer\n"); + goto error; + } + /* + * Set a dummy "control_rem" value. This + * variable will be overwritten later by a + * call to "usbd_control_transfer_init()" ! + */ + xfer->flags_int.control_rem = 0xFFFF; + } else { + + /* setup "endpoint" and "control_rem" */ + + usbd_control_transfer_init(xfer); + } + + /* set transfer-header flag */ + + xfer->flags_int.control_hdr = 1; + + /* get data length */ + + len = (xfer->sumlen - sizeof(struct usb_device_request)); + } + + /* check if there is a length mismatch */ + + if (len > xfer->flags_int.control_rem) { + DPRINTFN(0, "Length (%d) greater than " + "remaining length (%d)\n", len, + xfer->flags_int.control_rem); + goto error; + } + /* check if we are doing a short transfer */ + + if (xfer->flags.force_short_xfer) { + xfer->flags_int.control_rem = 0; + } else { + if ((len != xfer->max_data_length) && + (len != xfer->flags_int.control_rem) && + (xfer->nframes != 1)) { + DPRINTFN(0, "Short control transfer without " + "force_short_xfer set\n"); + goto error; + } + xfer->flags_int.control_rem -= len; + } + + /* the status part is executed when "control_act" is 0 */ + + if ((xfer->flags_int.control_rem > 0) || + (xfer->flags.manual_status)) { + /* don't execute the STATUS stage yet */ + xfer->flags_int.control_act = 1; + + /* sanity check */ + if ((!xfer->flags_int.control_hdr) && + (xfer->nframes == 1)) { + /* + * This is not a valid operation! + */ + DPRINTFN(0, "Invalid parameter " + "combination\n"); + goto error; + } + } else { + /* time to execute the STATUS stage */ + xfer->flags_int.control_act = 0; + } + return (0); /* success */ + +error: + return (1); /* failure */ +} + +/*------------------------------------------------------------------------* + * usbd_transfer_submit - start USB hardware for the given transfer + * + * This function should only be called from the USB callback. + *------------------------------------------------------------------------*/ +void +usbd_transfer_submit(struct usb_xfer *xfer) +{ + struct usb_xfer_root *info; + struct usb_bus *bus; + usb_frcount_t x; + + info = xfer->xroot; + bus = info->bus; + + DPRINTF("xfer=%p, endpoint=%p, nframes=%d, dir=%s\n", + xfer, xfer->endpoint, xfer->nframes, USB_GET_DATA_ISREAD(xfer) ? + "read" : "write"); + +#ifdef USB_DEBUG + if (USB_DEBUG_VAR > 0) { + USB_BUS_LOCK(bus); + + usb_dump_endpoint(xfer->endpoint); + + USB_BUS_UNLOCK(bus); + } +#endif + + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + USB_BUS_LOCK_ASSERT(bus, MA_NOTOWNED); + + /* Only open the USB transfer once! */ + if (!xfer->flags_int.open) { + xfer->flags_int.open = 1; + + DPRINTF("open\n"); + + USB_BUS_LOCK(bus); + (xfer->endpoint->methods->open) (xfer); + USB_BUS_UNLOCK(bus); + } + /* set "transferring" flag */ + xfer->flags_int.transferring = 1; + +#if USB_HAVE_POWERD + /* increment power reference */ + usbd_transfer_power_ref(xfer, 1); +#endif + /* + * Check if the transfer is waiting on a queue, most + * frequently the "done_q": + */ + if (xfer->wait_queue) { + USB_BUS_LOCK(bus); + usbd_transfer_dequeue(xfer); + USB_BUS_UNLOCK(bus); + } + /* clear "did_dma_delay" flag */ + xfer->flags_int.did_dma_delay = 0; + + /* clear "did_close" flag */ + xfer->flags_int.did_close = 0; + +#if USB_HAVE_BUSDMA + /* clear "bdma_setup" flag */ + xfer->flags_int.bdma_setup = 0; +#endif + /* by default we cannot cancel any USB transfer immediately */ + xfer->flags_int.can_cancel_immed = 0; + + /* clear lengths and frame counts by default */ + xfer->sumlen = 0; + xfer->actlen = 0; + xfer->aframes = 0; + + /* clear any previous errors */ + xfer->error = 0; + + /* Check if the device is still alive */ + if (info->udev->state < USB_STATE_POWERED) { + USB_BUS_LOCK(bus); + /* + * Must return cancelled error code else + * device drivers can hang. + */ + usbd_transfer_done(xfer, USB_ERR_CANCELLED); + USB_BUS_UNLOCK(bus); + return; + } + + /* sanity check */ + if (xfer->nframes == 0) { + if (xfer->flags.stall_pipe) { + /* + * Special case - want to stall without transferring + * any data: + */ + DPRINTF("xfer=%p nframes=0: stall " + "or clear stall!\n", xfer); + USB_BUS_LOCK(bus); + xfer->flags_int.can_cancel_immed = 1; + /* start the transfer */ + usb_command_wrapper(&xfer->endpoint->endpoint_q, xfer); + USB_BUS_UNLOCK(bus); + return; + } + USB_BUS_LOCK(bus); + usbd_transfer_done(xfer, USB_ERR_INVAL); + USB_BUS_UNLOCK(bus); + return; + } + /* compute some variables */ + + for (x = 0; x != xfer->nframes; x++) { + /* make a copy of the frlenghts[] */ + xfer->frlengths[x + xfer->max_frame_count] = xfer->frlengths[x]; + /* compute total transfer length */ + xfer->sumlen += xfer->frlengths[x]; + if (xfer->sumlen < xfer->frlengths[x]) { + /* length wrapped around */ + USB_BUS_LOCK(bus); + usbd_transfer_done(xfer, USB_ERR_INVAL); + USB_BUS_UNLOCK(bus); + return; + } + } + + /* clear some internal flags */ + + xfer->flags_int.short_xfer_ok = 0; + xfer->flags_int.short_frames_ok = 0; + + /* check if this is a control transfer */ + + if (xfer->flags_int.control_xfr) { + + if (usbd_setup_ctrl_transfer(xfer)) { + USB_BUS_LOCK(bus); + usbd_transfer_done(xfer, USB_ERR_STALLED); + USB_BUS_UNLOCK(bus); + return; + } + } + /* + * Setup filtered version of some transfer flags, + * in case of data read direction + */ + if (USB_GET_DATA_ISREAD(xfer)) { + + if (xfer->flags.short_frames_ok) { + xfer->flags_int.short_xfer_ok = 1; + xfer->flags_int.short_frames_ok = 1; + } else if (xfer->flags.short_xfer_ok) { + xfer->flags_int.short_xfer_ok = 1; + + /* check for control transfer */ + if (xfer->flags_int.control_xfr) { + /* + * 1) Control transfers do not support + * reception of multiple short USB + * frames in host mode and device side + * mode, with exception of: + * + * 2) Due to sometimes buggy device + * side firmware we need to do a + * STATUS stage in case of short + * control transfers in USB host mode. + * The STATUS stage then becomes the + * "alt_next" to the DATA stage. + */ + xfer->flags_int.short_frames_ok = 1; + } + } + } + /* + * Check if BUS-DMA support is enabled and try to load virtual + * buffers into DMA, if any: + */ +#if USB_HAVE_BUSDMA + if (xfer->flags_int.bdma_enable) { + /* insert the USB transfer last in the BUS-DMA queue */ + usb_command_wrapper(&xfer->xroot->dma_q, xfer); + return; + } +#endif + /* + * Enter the USB transfer into the Host Controller or + * Device Controller schedule: + */ + usbd_pipe_enter(xfer); +} + +/*------------------------------------------------------------------------* + * usbd_pipe_enter - factored out code + *------------------------------------------------------------------------*/ +void +usbd_pipe_enter(struct usb_xfer *xfer) +{ + struct usb_endpoint *ep; + + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + + USB_BUS_LOCK(xfer->xroot->bus); + + ep = xfer->endpoint; + + DPRINTF("enter\n"); + + /* enter the transfer */ + (ep->methods->enter) (xfer); + + xfer->flags_int.can_cancel_immed = 1; + + /* check for transfer error */ + if (xfer->error) { + /* some error has happened */ + usbd_transfer_done(xfer, 0); + USB_BUS_UNLOCK(xfer->xroot->bus); + return; + } + + /* start the transfer */ + usb_command_wrapper(&ep->endpoint_q, xfer); + USB_BUS_UNLOCK(xfer->xroot->bus); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_start - start an USB transfer + * + * NOTE: Calling this function more than one time will only + * result in a single transfer start, until the USB transfer + * completes. + *------------------------------------------------------------------------*/ +void +usbd_transfer_start(struct usb_xfer *xfer) +{ + if (xfer == NULL) { + /* transfer is gone */ + return; + } + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + + /* mark the USB transfer started */ + + if (!xfer->flags_int.started) { + /* lock the BUS lock to avoid races updating flags_int */ + USB_BUS_LOCK(xfer->xroot->bus); + xfer->flags_int.started = 1; + USB_BUS_UNLOCK(xfer->xroot->bus); + } + /* check if the USB transfer callback is already transferring */ + + if (xfer->flags_int.transferring) { + return; + } + USB_BUS_LOCK(xfer->xroot->bus); + /* call the USB transfer callback */ + usbd_callback_ss_done_defer(xfer); + USB_BUS_UNLOCK(xfer->xroot->bus); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_stop - stop an USB transfer + * + * NOTE: Calling this function more than one time will only + * result in a single transfer stop. + * NOTE: When this function returns it is not safe to free nor + * reuse any DMA buffers. See "usbd_transfer_drain()". + *------------------------------------------------------------------------*/ +void +usbd_transfer_stop(struct usb_xfer *xfer) +{ + struct usb_endpoint *ep; + + if (xfer == NULL) { + /* transfer is gone */ + return; + } + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + + /* check if the USB transfer was ever opened */ + + if (!xfer->flags_int.open) { + if (xfer->flags_int.started) { + /* nothing to do except clearing the "started" flag */ + /* lock the BUS lock to avoid races updating flags_int */ + USB_BUS_LOCK(xfer->xroot->bus); + xfer->flags_int.started = 0; + USB_BUS_UNLOCK(xfer->xroot->bus); + } + return; + } + /* try to stop the current USB transfer */ + + USB_BUS_LOCK(xfer->xroot->bus); + /* override any previous error */ + xfer->error = USB_ERR_CANCELLED; + + /* + * Clear "open" and "started" when both private and USB lock + * is locked so that we don't get a race updating "flags_int" + */ + xfer->flags_int.open = 0; + xfer->flags_int.started = 0; + + /* + * Check if we can cancel the USB transfer immediately. + */ + if (xfer->flags_int.transferring) { + if (xfer->flags_int.can_cancel_immed && + (!xfer->flags_int.did_close)) { + DPRINTF("close\n"); + /* + * The following will lead to an USB_ERR_CANCELLED + * error code being passed to the USB callback. + */ + (xfer->endpoint->methods->close) (xfer); + /* only close once */ + xfer->flags_int.did_close = 1; + } else { + /* need to wait for the next done callback */ + } + } else { + DPRINTF("close\n"); + + /* close here and now */ + (xfer->endpoint->methods->close) (xfer); + + /* + * Any additional DMA delay is done by + * "usbd_transfer_unsetup()". + */ + + /* + * Special case. Check if we need to restart a blocked + * endpoint. + */ + ep = xfer->endpoint; + + /* + * If the current USB transfer is completing we need + * to start the next one: + */ + if (ep->endpoint_q.curr == xfer) { + usb_command_wrapper(&ep->endpoint_q, NULL); + } + } + + USB_BUS_UNLOCK(xfer->xroot->bus); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_pending + * + * This function will check if an USB transfer is pending which is a + * little bit complicated! + * Return values: + * 0: Not pending + * 1: Pending: The USB transfer will receive a callback in the future. + *------------------------------------------------------------------------*/ +uint8_t +usbd_transfer_pending(struct usb_xfer *xfer) +{ + struct usb_xfer_root *info; + struct usb_xfer_queue *pq; + + if (xfer == NULL) { + /* transfer is gone */ + return (0); + } + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + + if (xfer->flags_int.transferring) { + /* trivial case */ + return (1); + } + USB_BUS_LOCK(xfer->xroot->bus); + if (xfer->wait_queue) { + /* we are waiting on a queue somewhere */ + USB_BUS_UNLOCK(xfer->xroot->bus); + return (1); + } + info = xfer->xroot; + pq = &info->done_q; + + if (pq->curr == xfer) { + /* we are currently scheduled for callback */ + USB_BUS_UNLOCK(xfer->xroot->bus); + return (1); + } + /* we are not pending */ + USB_BUS_UNLOCK(xfer->xroot->bus); + return (0); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_drain + * + * This function will stop the USB transfer and wait for any + * additional BUS-DMA and HW-DMA operations to complete. Buffers that + * are loaded into DMA can safely be freed or reused after that this + * function has returned. + *------------------------------------------------------------------------*/ +void +usbd_transfer_drain(struct usb_xfer *xfer) +{ + WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, + "usbd_transfer_drain can sleep!"); + + if (xfer == NULL) { + /* transfer is gone */ + return; + } + if (xfer->xroot->xfer_mtx != &Giant) { + USB_XFER_LOCK_ASSERT(xfer, MA_NOTOWNED); + } + USB_XFER_LOCK(xfer); + + usbd_transfer_stop(xfer); + + while (usbd_transfer_pending(xfer) || + xfer->flags_int.doing_callback) { + + /* + * It is allowed that the callback can drop its + * transfer mutex. In that case checking only + * "usbd_transfer_pending()" is not enough to tell if + * the USB transfer is fully drained. We also need to + * check the internal "doing_callback" flag. + */ + xfer->flags_int.draining = 1; + + /* + * Wait until the current outstanding USB + * transfer is complete ! + */ + cv_wait(&xfer->xroot->cv_drain, xfer->xroot->xfer_mtx); + } + USB_XFER_UNLOCK(xfer); +} + +struct usb_page_cache * +usbd_xfer_get_frame(struct usb_xfer *xfer, usb_frcount_t frindex) +{ + KASSERT(frindex < xfer->max_frame_count, ("frame index overflow")); + + return (&xfer->frbuffers[frindex]); +} + +/*------------------------------------------------------------------------* + * usbd_xfer_get_fps_shift + * + * The following function is only useful for isochronous transfers. It + * returns how many times the frame execution rate has been shifted + * down. + * + * Return value: + * Success: 0..3 + * Failure: 0 + *------------------------------------------------------------------------*/ +uint8_t +usbd_xfer_get_fps_shift(struct usb_xfer *xfer) +{ + return (xfer->fps_shift); +} + +usb_frlength_t +usbd_xfer_frame_len(struct usb_xfer *xfer, usb_frcount_t frindex) +{ + KASSERT(frindex < xfer->max_frame_count, ("frame index overflow")); + + return (xfer->frlengths[frindex]); +} + +/*------------------------------------------------------------------------* + * usbd_xfer_set_frame_data + * + * This function sets the pointer of the buffer that should + * loaded directly into DMA for the given USB frame. Passing "ptr" + * equal to NULL while the corresponding "frlength" is greater + * than zero gives undefined results! + *------------------------------------------------------------------------*/ +void +usbd_xfer_set_frame_data(struct usb_xfer *xfer, usb_frcount_t frindex, + void *ptr, usb_frlength_t len) +{ + KASSERT(frindex < xfer->max_frame_count, ("frame index overflow")); + + /* set virtual address to load and length */ + xfer->frbuffers[frindex].buffer = ptr; + usbd_xfer_set_frame_len(xfer, frindex, len); +} + +void +usbd_xfer_frame_data(struct usb_xfer *xfer, usb_frcount_t frindex, + void **ptr, int *len) +{ + KASSERT(frindex < xfer->max_frame_count, ("frame index overflow")); + + if (ptr != NULL) + *ptr = xfer->frbuffers[frindex].buffer; + if (len != NULL) + *len = xfer->frlengths[frindex]; +} + +/*------------------------------------------------------------------------* + * usbd_xfer_old_frame_length + * + * This function returns the framelength of the given frame at the + * time the transfer was submitted. This function can be used to + * compute the starting data pointer of the next isochronous frame + * when an isochronous transfer has completed. + *------------------------------------------------------------------------*/ +usb_frlength_t +usbd_xfer_old_frame_length(struct usb_xfer *xfer, usb_frcount_t frindex) +{ + KASSERT(frindex < xfer->max_frame_count, ("frame index overflow")); + + return (xfer->frlengths[frindex + xfer->max_frame_count]); +} + +void +usbd_xfer_status(struct usb_xfer *xfer, int *actlen, int *sumlen, int *aframes, + int *nframes) +{ + if (actlen != NULL) + *actlen = xfer->actlen; + if (sumlen != NULL) + *sumlen = xfer->sumlen; + if (aframes != NULL) + *aframes = xfer->aframes; + if (nframes != NULL) + *nframes = xfer->nframes; +} + +/*------------------------------------------------------------------------* + * usbd_xfer_set_frame_offset + * + * This function sets the frame data buffer offset relative to the beginning + * of the USB DMA buffer allocated for this USB transfer. + *------------------------------------------------------------------------*/ +void +usbd_xfer_set_frame_offset(struct usb_xfer *xfer, usb_frlength_t offset, + usb_frcount_t frindex) +{ + KASSERT(!xfer->flags.ext_buffer, ("Cannot offset data frame " + "when the USB buffer is external\n")); + KASSERT(frindex < xfer->max_frame_count, ("frame index overflow")); + + /* set virtual address to load */ + xfer->frbuffers[frindex].buffer = + USB_ADD_BYTES(xfer->local_buffer, offset); +} + +void +usbd_xfer_set_interval(struct usb_xfer *xfer, int i) +{ + xfer->interval = i; +} + +void +usbd_xfer_set_timeout(struct usb_xfer *xfer, int t) +{ + xfer->timeout = t; +} + +void +usbd_xfer_set_frames(struct usb_xfer *xfer, usb_frcount_t n) +{ + xfer->nframes = n; +} + +usb_frcount_t +usbd_xfer_max_frames(struct usb_xfer *xfer) +{ + return (xfer->max_frame_count); +} + +usb_frlength_t +usbd_xfer_max_len(struct usb_xfer *xfer) +{ + return (xfer->max_data_length); +} + +usb_frlength_t +usbd_xfer_max_framelen(struct usb_xfer *xfer) +{ + return (xfer->max_frame_size); +} + +void +usbd_xfer_set_frame_len(struct usb_xfer *xfer, usb_frcount_t frindex, + usb_frlength_t len) +{ + KASSERT(frindex < xfer->max_frame_count, ("frame index overflow")); + + xfer->frlengths[frindex] = len; +} + +/*------------------------------------------------------------------------* + * usb_callback_proc - factored out code + * + * This function performs USB callbacks. + *------------------------------------------------------------------------*/ +static void +usb_callback_proc(struct usb_proc_msg *_pm) +{ + struct usb_done_msg *pm = (void *)_pm; + struct usb_xfer_root *info = pm->xroot; + + /* Change locking order */ + USB_BUS_UNLOCK(info->bus); + + /* + * We exploit the fact that the mutex is the same for all + * callbacks that will be called from this thread: + */ + mtx_lock(info->xfer_mtx); + USB_BUS_LOCK(info->bus); + + /* Continue where we lost track */ + usb_command_wrapper(&info->done_q, + info->done_q.curr); + + mtx_unlock(info->xfer_mtx); +} + +/*------------------------------------------------------------------------* + * usbd_callback_ss_done_defer + * + * This function will defer the start, stop and done callback to the + * correct thread. + *------------------------------------------------------------------------*/ +static void +usbd_callback_ss_done_defer(struct usb_xfer *xfer) +{ + struct usb_xfer_root *info = xfer->xroot; + struct usb_xfer_queue *pq = &info->done_q; + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + if (pq->curr != xfer) { + usbd_transfer_enqueue(pq, xfer); + } + if (!pq->recurse_1) { + + /* + * We have to postpone the callback due to the fact we + * will have a Lock Order Reversal, LOR, if we try to + * proceed ! + */ + if (usb_proc_msignal(info->done_p, + &info->done_m[0], &info->done_m[1])) { + /* ignore */ + } + } else { + /* clear second recurse flag */ + pq->recurse_2 = 0; + } + return; + +} + +/*------------------------------------------------------------------------* + * usbd_callback_wrapper + * + * This is a wrapper for USB callbacks. This wrapper does some + * auto-magic things like figuring out if we can call the callback + * directly from the current context or if we need to wakeup the + * interrupt process. + *------------------------------------------------------------------------*/ +static void +usbd_callback_wrapper(struct usb_xfer_queue *pq) +{ + struct usb_xfer *xfer = pq->curr; + struct usb_xfer_root *info = xfer->xroot; + + USB_BUS_LOCK_ASSERT(info->bus, MA_OWNED); + if (!mtx_owned(info->xfer_mtx) && !SCHEDULER_STOPPED()) { + /* + * Cases that end up here: + * + * 5) HW interrupt done callback or other source. + */ + DPRINTFN(3, "case 5\n"); + + /* + * We have to postpone the callback due to the fact we + * will have a Lock Order Reversal, LOR, if we try to + * proceed ! + */ + if (usb_proc_msignal(info->done_p, + &info->done_m[0], &info->done_m[1])) { + /* ignore */ + } + return; + } + /* + * Cases that end up here: + * + * 1) We are starting a transfer + * 2) We are prematurely calling back a transfer + * 3) We are stopping a transfer + * 4) We are doing an ordinary callback + */ + DPRINTFN(3, "case 1-4\n"); + /* get next USB transfer in the queue */ + info->done_q.curr = NULL; + + /* set flag in case of drain */ + xfer->flags_int.doing_callback = 1; + + USB_BUS_UNLOCK(info->bus); + USB_BUS_LOCK_ASSERT(info->bus, MA_NOTOWNED); + + /* set correct USB state for callback */ + if (!xfer->flags_int.transferring) { + xfer->usb_state = USB_ST_SETUP; + if (!xfer->flags_int.started) { + /* we got stopped before we even got started */ + USB_BUS_LOCK(info->bus); + goto done; + } + } else { + + if (usbd_callback_wrapper_sub(xfer)) { + /* the callback has been deferred */ + USB_BUS_LOCK(info->bus); + goto done; + } +#if USB_HAVE_POWERD + /* decrement power reference */ + usbd_transfer_power_ref(xfer, -1); +#endif + xfer->flags_int.transferring = 0; + + if (xfer->error) { + xfer->usb_state = USB_ST_ERROR; + } else { + /* set transferred state */ + xfer->usb_state = USB_ST_TRANSFERRED; +#if USB_HAVE_BUSDMA + /* sync DMA memory, if any */ + if (xfer->flags_int.bdma_enable && + (!xfer->flags_int.bdma_no_post_sync)) { + usb_bdma_post_sync(xfer); + } +#endif + } + } + +#if USB_HAVE_PF + if (xfer->usb_state != USB_ST_SETUP) + usbpf_xfertap(xfer, USBPF_XFERTAP_DONE); +#endif + /* call processing routine */ + (xfer->callback) (xfer, xfer->error); + + /* pickup the USB mutex again */ + USB_BUS_LOCK(info->bus); + + /* + * Check if we got started after that we got cancelled, but + * before we managed to do the callback. + */ + if ((!xfer->flags_int.open) && + (xfer->flags_int.started) && + (xfer->usb_state == USB_ST_ERROR)) { + /* clear flag in case of drain */ + xfer->flags_int.doing_callback = 0; + /* try to loop, but not recursivly */ + usb_command_wrapper(&info->done_q, xfer); + return; + } + +done: + /* clear flag in case of drain */ + xfer->flags_int.doing_callback = 0; + + /* + * Check if we are draining. + */ + if (xfer->flags_int.draining && + (!xfer->flags_int.transferring)) { + /* "usbd_transfer_drain()" is waiting for end of transfer */ + xfer->flags_int.draining = 0; + cv_broadcast(&info->cv_drain); + } + + /* do the next callback, if any */ + usb_command_wrapper(&info->done_q, + info->done_q.curr); +} + +/*------------------------------------------------------------------------* + * usb_dma_delay_done_cb + * + * This function is called when the DMA delay has been exectuded, and + * will make sure that the callback is called to complete the USB + * transfer. This code path is ususally only used when there is an USB + * error like USB_ERR_CANCELLED. + *------------------------------------------------------------------------*/ +void +usb_dma_delay_done_cb(struct usb_xfer *xfer) +{ + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + DPRINTFN(3, "Completed %p\n", xfer); + + /* queue callback for execution, again */ + usbd_transfer_done(xfer, 0); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_dequeue + * + * - This function is used to remove an USB transfer from a USB + * transfer queue. + * + * - This function can be called multiple times in a row. + *------------------------------------------------------------------------*/ +void +usbd_transfer_dequeue(struct usb_xfer *xfer) +{ + struct usb_xfer_queue *pq; + + pq = xfer->wait_queue; + if (pq) { + TAILQ_REMOVE(&pq->head, xfer, wait_entry); + xfer->wait_queue = NULL; + } +} + +/*------------------------------------------------------------------------* + * usbd_transfer_enqueue + * + * - This function is used to insert an USB transfer into a USB * + * transfer queue. + * + * - This function can be called multiple times in a row. + *------------------------------------------------------------------------*/ +void +usbd_transfer_enqueue(struct usb_xfer_queue *pq, struct usb_xfer *xfer) +{ + /* + * Insert the USB transfer into the queue, if it is not + * already on a USB transfer queue: + */ + if (xfer->wait_queue == NULL) { + xfer->wait_queue = pq; + TAILQ_INSERT_TAIL(&pq->head, xfer, wait_entry); + } +} + +/*------------------------------------------------------------------------* + * usbd_transfer_done + * + * - This function is used to remove an USB transfer from the busdma, + * pipe or interrupt queue. + * + * - This function is used to queue the USB transfer on the done + * queue. + * + * - This function is used to stop any USB transfer timeouts. + *------------------------------------------------------------------------*/ +void +usbd_transfer_done(struct usb_xfer *xfer, usb_error_t error) +{ + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + DPRINTF("err=%s\n", usbd_errstr(error)); + + /* + * If we are not transferring then just return. + * This can happen during transfer cancel. + */ + if (!xfer->flags_int.transferring) { + DPRINTF("not transferring\n"); + /* end of control transfer, if any */ + xfer->flags_int.control_act = 0; + return; + } + /* only set transfer error if not already set */ + if (!xfer->error) { + xfer->error = error; + } + /* stop any callouts */ + usb_callout_stop(&xfer->timeout_handle); + + /* + * If we are waiting on a queue, just remove the USB transfer + * from the queue, if any. We should have the required locks + * locked to do the remove when this function is called. + */ + usbd_transfer_dequeue(xfer); + +#if USB_HAVE_BUSDMA + if (mtx_owned(xfer->xroot->xfer_mtx)) { + struct usb_xfer_queue *pq; + + /* + * If the private USB lock is not locked, then we assume + * that the BUS-DMA load stage has been passed: + */ + pq = &xfer->xroot->dma_q; + + if (pq->curr == xfer) { + /* start the next BUS-DMA load, if any */ + usb_command_wrapper(pq, NULL); + } + } +#endif + /* keep some statistics */ + if (xfer->error) { + xfer->xroot->bus->stats_err.uds_requests + [xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE]++; + } else { + xfer->xroot->bus->stats_ok.uds_requests + [xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE]++; + } + + /* call the USB transfer callback */ + usbd_callback_ss_done_defer(xfer); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_start_cb + * + * This function is called to start the USB transfer when + * "xfer->interval" is greater than zero, and and the endpoint type is + * BULK or CONTROL. + *------------------------------------------------------------------------*/ +static void +usbd_transfer_start_cb(void *arg) +{ + struct usb_xfer *xfer = arg; + struct usb_endpoint *ep = xfer->endpoint; + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + DPRINTF("start\n"); + +#if USB_HAVE_PF + usbpf_xfertap(xfer, USBPF_XFERTAP_SUBMIT); +#endif + /* start USB transfer, if no error */ + if (xfer->error == 0) + (ep->methods->start) (xfer); + + xfer->flags_int.can_cancel_immed = 1; + + /* check for error */ + if (xfer->error) { + /* some error has happened */ + usbd_transfer_done(xfer, 0); + } +} + +/*------------------------------------------------------------------------* + * usbd_xfer_set_stall + * + * This function is used to set the stall flag outside the + * callback. This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usbd_xfer_set_stall(struct usb_xfer *xfer) +{ + if (xfer == NULL) { + /* tearing down */ + return; + } + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + + /* avoid any races by locking the USB mutex */ + USB_BUS_LOCK(xfer->xroot->bus); + xfer->flags.stall_pipe = 1; + USB_BUS_UNLOCK(xfer->xroot->bus); +} + +int +usbd_xfer_is_stalled(struct usb_xfer *xfer) +{ + return (xfer->endpoint->is_stalled); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_clear_stall + * + * This function is used to clear the stall flag outside the + * callback. This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usbd_transfer_clear_stall(struct usb_xfer *xfer) +{ + if (xfer == NULL) { + /* tearing down */ + return; + } + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + + /* avoid any races by locking the USB mutex */ + USB_BUS_LOCK(xfer->xroot->bus); + + xfer->flags.stall_pipe = 0; + + USB_BUS_UNLOCK(xfer->xroot->bus); +} + +/*------------------------------------------------------------------------* + * usbd_pipe_start + * + * This function is used to add an USB transfer to the pipe transfer list. + *------------------------------------------------------------------------*/ +void +usbd_pipe_start(struct usb_xfer_queue *pq) +{ + struct usb_endpoint *ep; + struct usb_xfer *xfer; + uint8_t type; + + xfer = pq->curr; + ep = xfer->endpoint; + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* + * If the endpoint is already stalled we do nothing ! + */ + if (ep->is_stalled) { + return; + } + /* + * Check if we are supposed to stall the endpoint: + */ + if (xfer->flags.stall_pipe) { + struct usb_device *udev; + struct usb_xfer_root *info; + + /* clear stall command */ + xfer->flags.stall_pipe = 0; + + /* get pointer to USB device */ + info = xfer->xroot; + udev = info->udev; + + /* + * Only stall BULK and INTERRUPT endpoints. + */ + type = (ep->edesc->bmAttributes & UE_XFERTYPE); + if ((type == UE_BULK) || + (type == UE_INTERRUPT)) { + uint8_t did_stall; + + did_stall = 1; + + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + (udev->bus->methods->set_stall) ( + udev, NULL, ep, &did_stall); + } else if (udev->ctrl_xfer[1]) { + info = udev->ctrl_xfer[1]->xroot; + usb_proc_msignal( + &info->bus->non_giant_callback_proc, + &udev->cs_msg[0], &udev->cs_msg[1]); + } else { + /* should not happen */ + DPRINTFN(0, "No stall handler\n"); + } + /* + * Check if we should stall. Some USB hardware + * handles set- and clear-stall in hardware. + */ + if (did_stall) { + /* + * The transfer will be continued when + * the clear-stall control endpoint + * message is received. + */ + ep->is_stalled = 1; + return; + } + } else if (type == UE_ISOCHRONOUS) { + + /* + * Make sure any FIFO overflow or other FIFO + * error conditions go away by resetting the + * endpoint FIFO through the clear stall + * method. + */ + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + (udev->bus->methods->clear_stall) (udev, ep); + } + } + } + /* Set or clear stall complete - special case */ + if (xfer->nframes == 0) { + /* we are complete */ + xfer->aframes = 0; + usbd_transfer_done(xfer, 0); + return; + } + /* + * Handled cases: + * + * 1) Start the first transfer queued. + * + * 2) Re-start the current USB transfer. + */ + /* + * Check if there should be any + * pre transfer start delay: + */ + if (xfer->interval > 0) { + type = (ep->edesc->bmAttributes & UE_XFERTYPE); + if ((type == UE_BULK) || + (type == UE_CONTROL)) { + usbd_transfer_timeout_ms(xfer, + &usbd_transfer_start_cb, + xfer->interval); + return; + } + } + DPRINTF("start\n"); + +#if USB_HAVE_PF + usbpf_xfertap(xfer, USBPF_XFERTAP_SUBMIT); +#endif + /* start USB transfer, if no error */ + if (xfer->error == 0) + (ep->methods->start) (xfer); + + xfer->flags_int.can_cancel_immed = 1; + + /* check for error */ + if (xfer->error) { + /* some error has happened */ + usbd_transfer_done(xfer, 0); + } +} + +/*------------------------------------------------------------------------* + * usbd_transfer_timeout_ms + * + * This function is used to setup a timeout on the given USB + * transfer. If the timeout has been deferred the callback given by + * "cb" will get called after "ms" milliseconds. + *------------------------------------------------------------------------*/ +void +usbd_transfer_timeout_ms(struct usb_xfer *xfer, + void (*cb) (void *arg), usb_timeout_t ms) +{ + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* defer delay */ + usb_callout_reset(&xfer->timeout_handle, + USB_MS_TO_TICKS(ms), cb, xfer); +} + +/*------------------------------------------------------------------------* + * usbd_callback_wrapper_sub + * + * - This function will update variables in an USB transfer after + * that the USB transfer is complete. + * + * - This function is used to start the next USB transfer on the + * ep transfer queue, if any. + * + * NOTE: In some special cases the USB transfer will not be removed from + * the pipe queue, but remain first. To enforce USB transfer removal call + * this function passing the error code "USB_ERR_CANCELLED". + * + * Return values: + * 0: Success. + * Else: The callback has been deferred. + *------------------------------------------------------------------------*/ +static uint8_t +usbd_callback_wrapper_sub(struct usb_xfer *xfer) +{ + struct usb_endpoint *ep; + struct usb_bus *bus; + usb_frcount_t x; + + bus = xfer->xroot->bus; + + if ((!xfer->flags_int.open) && + (!xfer->flags_int.did_close)) { + DPRINTF("close\n"); + USB_BUS_LOCK(bus); + (xfer->endpoint->methods->close) (xfer); + USB_BUS_UNLOCK(bus); + /* only close once */ + xfer->flags_int.did_close = 1; + return (1); /* wait for new callback */ + } + /* + * If we have a non-hardware induced error we + * need to do the DMA delay! + */ + if (xfer->error != 0 && !xfer->flags_int.did_dma_delay && + (xfer->error == USB_ERR_CANCELLED || + xfer->error == USB_ERR_TIMEOUT || + bus->methods->start_dma_delay != NULL)) { + + usb_timeout_t temp; + + /* only delay once */ + xfer->flags_int.did_dma_delay = 1; + + /* we can not cancel this delay */ + xfer->flags_int.can_cancel_immed = 0; + + temp = usbd_get_dma_delay(xfer->xroot->udev); + + DPRINTFN(3, "DMA delay, %u ms, " + "on %p\n", temp, xfer); + + if (temp != 0) { + USB_BUS_LOCK(bus); + /* + * Some hardware solutions have dedicated + * events when it is safe to free DMA'ed + * memory. For the other hardware platforms we + * use a static delay. + */ + if (bus->methods->start_dma_delay != NULL) { + (bus->methods->start_dma_delay) (xfer); + } else { + usbd_transfer_timeout_ms(xfer, + (void *)&usb_dma_delay_done_cb, temp); + } + USB_BUS_UNLOCK(bus); + return (1); /* wait for new callback */ + } + } + /* check actual number of frames */ + if (xfer->aframes > xfer->nframes) { + if (xfer->error == 0) { + panic("%s: actual number of frames, %d, is " + "greater than initial number of frames, %d\n", + __FUNCTION__, xfer->aframes, xfer->nframes); + } else { + /* just set some valid value */ + xfer->aframes = xfer->nframes; + } + } + /* compute actual length */ + xfer->actlen = 0; + + for (x = 0; x != xfer->aframes; x++) { + xfer->actlen += xfer->frlengths[x]; + } + + /* + * Frames that were not transferred get zero actual length in + * case the USB device driver does not check the actual number + * of frames transferred, "xfer->aframes": + */ + for (; x < xfer->nframes; x++) { + usbd_xfer_set_frame_len(xfer, x, 0); + } + + /* check actual length */ + if (xfer->actlen > xfer->sumlen) { + if (xfer->error == 0) { + panic("%s: actual length, %d, is greater than " + "initial length, %d\n", + __FUNCTION__, xfer->actlen, xfer->sumlen); + } else { + /* just set some valid value */ + xfer->actlen = xfer->sumlen; + } + } + DPRINTFN(1, "xfer=%p endpoint=%p sts=%d alen=%d, slen=%d, afrm=%d, nfrm=%d\n", + xfer, xfer->endpoint, xfer->error, xfer->actlen, xfer->sumlen, + xfer->aframes, xfer->nframes); + + if (xfer->error) { + /* end of control transfer, if any */ + xfer->flags_int.control_act = 0; + + /* check if we should block the execution queue */ + if ((xfer->error != USB_ERR_CANCELLED) && + (xfer->flags.pipe_bof)) { + DPRINTFN(2, "xfer=%p: Block On Failure " + "on endpoint=%p\n", xfer, xfer->endpoint); + goto done; + } + } else { + /* check for short transfers */ + if (xfer->actlen < xfer->sumlen) { + + /* end of control transfer, if any */ + xfer->flags_int.control_act = 0; + + if (!xfer->flags_int.short_xfer_ok) { + xfer->error = USB_ERR_SHORT_XFER; + if (xfer->flags.pipe_bof) { + DPRINTFN(2, "xfer=%p: Block On Failure on " + "Short Transfer on endpoint %p.\n", + xfer, xfer->endpoint); + goto done; + } + } + } else { + /* + * Check if we are in the middle of a + * control transfer: + */ + if (xfer->flags_int.control_act) { + DPRINTFN(5, "xfer=%p: Control transfer " + "active on endpoint=%p\n", xfer, xfer->endpoint); + goto done; + } + } + } + + ep = xfer->endpoint; + + /* + * If the current USB transfer is completing we need to start the + * next one: + */ + USB_BUS_LOCK(bus); + if (ep->endpoint_q.curr == xfer) { + usb_command_wrapper(&ep->endpoint_q, NULL); + + if (ep->endpoint_q.curr || TAILQ_FIRST(&ep->endpoint_q.head)) { + /* there is another USB transfer waiting */ + } else { + /* this is the last USB transfer */ + /* clear isochronous sync flag */ + xfer->endpoint->is_synced = 0; + } + } + USB_BUS_UNLOCK(bus); +done: + return (0); +} + +/*------------------------------------------------------------------------* + * usb_command_wrapper + * + * This function is used to execute commands non-recursivly on an USB + * transfer. + *------------------------------------------------------------------------*/ +void +usb_command_wrapper(struct usb_xfer_queue *pq, struct usb_xfer *xfer) +{ + if (xfer) { + /* + * If the transfer is not already processing, + * queue it! + */ + if (pq->curr != xfer) { + usbd_transfer_enqueue(pq, xfer); + if (pq->curr != NULL) { + /* something is already processing */ + DPRINTFN(6, "busy %p\n", pq->curr); + return; + } + } + } else { + /* Get next element in queue */ + pq->curr = NULL; + } + + if (!pq->recurse_1) { + + do { + + /* set both recurse flags */ + pq->recurse_1 = 1; + pq->recurse_2 = 1; + + if (pq->curr == NULL) { + xfer = TAILQ_FIRST(&pq->head); + if (xfer) { + TAILQ_REMOVE(&pq->head, xfer, + wait_entry); + xfer->wait_queue = NULL; + pq->curr = xfer; + } else { + break; + } + } + DPRINTFN(6, "cb %p (enter)\n", pq->curr); + (pq->command) (pq); + DPRINTFN(6, "cb %p (leave)\n", pq->curr); + + } while (!pq->recurse_2); + + /* clear first recurse flag */ + pq->recurse_1 = 0; + + } else { + /* clear second recurse flag */ + pq->recurse_2 = 0; + } +} + +/*------------------------------------------------------------------------* + * usbd_ctrl_transfer_setup + * + * This function is used to setup the default USB control endpoint + * transfer. + *------------------------------------------------------------------------*/ +void +usbd_ctrl_transfer_setup(struct usb_device *udev) +{ + struct usb_xfer *xfer; + uint8_t no_resetup; + uint8_t iface_index; + + /* check for root HUB */ + if (udev->parent_hub == NULL) + return; +repeat: + + xfer = udev->ctrl_xfer[0]; + if (xfer) { + USB_XFER_LOCK(xfer); + no_resetup = + ((xfer->address == udev->address) && + (udev->ctrl_ep_desc.wMaxPacketSize[0] == + udev->ddesc.bMaxPacketSize)); + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + if (no_resetup) { + /* + * NOTE: checking "xfer->address" and + * starting the USB transfer must be + * atomic! + */ + usbd_transfer_start(xfer); + } + } + USB_XFER_UNLOCK(xfer); + } else { + no_resetup = 0; + } + + if (no_resetup) { + /* + * All parameters are exactly the same like before. + * Just return. + */ + return; + } + /* + * Update wMaxPacketSize for the default control endpoint: + */ + udev->ctrl_ep_desc.wMaxPacketSize[0] = + udev->ddesc.bMaxPacketSize; + + /* + * Unsetup any existing USB transfer: + */ + usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX); + + /* + * Reset clear stall error counter. + */ + udev->clear_stall_errors = 0; + + /* + * Try to setup a new USB transfer for the + * default control endpoint: + */ + iface_index = 0; + if (usbd_transfer_setup(udev, &iface_index, + udev->ctrl_xfer, usb_control_ep_cfg, USB_CTRL_XFER_MAX, NULL, + &udev->device_mtx)) { + DPRINTFN(0, "could not setup default " + "USB transfer\n"); + } else { + goto repeat; + } +} + +/*------------------------------------------------------------------------* + * usbd_clear_data_toggle - factored out code + * + * NOTE: the intention of this function is not to reset the hardware + * data toggle. + *------------------------------------------------------------------------*/ +void +usbd_clear_stall_locked(struct usb_device *udev, struct usb_endpoint *ep) +{ + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + /* check that we have a valid case */ + if (udev->flags.usb_mode == USB_MODE_HOST && + udev->parent_hub != NULL && + udev->bus->methods->clear_stall != NULL && + ep->methods != NULL) { + (udev->bus->methods->clear_stall) (udev, ep); + } +} + +/*------------------------------------------------------------------------* + * usbd_clear_data_toggle - factored out code + * + * NOTE: the intention of this function is not to reset the hardware + * data toggle on the USB device side. + *------------------------------------------------------------------------*/ +void +usbd_clear_data_toggle(struct usb_device *udev, struct usb_endpoint *ep) +{ + DPRINTFN(5, "udev=%p endpoint=%p\n", udev, ep); + + USB_BUS_LOCK(udev->bus); + ep->toggle_next = 0; + /* some hardware needs a callback to clear the data toggle */ + usbd_clear_stall_locked(udev, ep); + USB_BUS_UNLOCK(udev->bus); +} + +/*------------------------------------------------------------------------* + * usbd_clear_stall_callback - factored out clear stall callback + * + * Input parameters: + * xfer1: Clear Stall Control Transfer + * xfer2: Stalled USB Transfer + * + * This function is NULL safe. + * + * Return values: + * 0: In progress + * Else: Finished + * + * Clear stall config example: + * + * static const struct usb_config my_clearstall = { + * .type = UE_CONTROL, + * .endpoint = 0, + * .direction = UE_DIR_ANY, + * .interval = 50, //50 milliseconds + * .bufsize = sizeof(struct usb_device_request), + * .timeout = 1000, //1.000 seconds + * .callback = &my_clear_stall_callback, // ** + * .usb_mode = USB_MODE_HOST, + * }; + * + * ** "my_clear_stall_callback" calls "usbd_clear_stall_callback" + * passing the correct parameters. + *------------------------------------------------------------------------*/ +uint8_t +usbd_clear_stall_callback(struct usb_xfer *xfer1, + struct usb_xfer *xfer2) +{ + struct usb_device_request req; + + if (xfer2 == NULL) { + /* looks like we are tearing down */ + DPRINTF("NULL input parameter\n"); + return (0); + } + USB_XFER_LOCK_ASSERT(xfer1, MA_OWNED); + USB_XFER_LOCK_ASSERT(xfer2, MA_OWNED); + + switch (USB_GET_STATE(xfer1)) { + case USB_ST_SETUP: + + /* + * pre-clear the data toggle to DATA0 ("umass.c" and + * "ata-usb.c" depends on this) + */ + + usbd_clear_data_toggle(xfer2->xroot->udev, xfer2->endpoint); + + /* setup a clear-stall packet */ + + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + req.wIndex[0] = xfer2->endpoint->edesc->bEndpointAddress; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + /* + * "usbd_transfer_setup_sub()" will ensure that + * we have sufficient room in the buffer for + * the request structure! + */ + + /* copy in the transfer */ + + usbd_copy_in(xfer1->frbuffers, 0, &req, sizeof(req)); + + /* set length */ + xfer1->frlengths[0] = sizeof(req); + xfer1->nframes = 1; + + usbd_transfer_submit(xfer1); + return (0); + + case USB_ST_TRANSFERRED: + break; + + default: /* Error */ + if (xfer1->error == USB_ERR_CANCELLED) { + return (0); + } + break; + } + return (1); /* Clear Stall Finished */ +} + +/*------------------------------------------------------------------------* + * usbd_transfer_poll + * + * The following function gets called from the USB keyboard driver and + * UMASS when the system has paniced. + * + * NOTE: It is currently not possible to resume normal operation on + * the USB controller which has been polled, due to clearing of the + * "up_dsleep" and "up_msleep" flags. + *------------------------------------------------------------------------*/ +void +usbd_transfer_poll(struct usb_xfer **ppxfer, uint16_t max) +{ + struct usb_xfer *xfer; + struct usb_xfer_root *xroot; + struct usb_device *udev; + struct usb_proc_msg *pm; + uint16_t n; + uint16_t drop_bus; + uint16_t drop_xfer; + + for (n = 0; n != max; n++) { + /* Extra checks to avoid panic */ + xfer = ppxfer[n]; + if (xfer == NULL) + continue; /* no USB transfer */ + xroot = xfer->xroot; + if (xroot == NULL) + continue; /* no USB root */ + udev = xroot->udev; + if (udev == NULL) + continue; /* no USB device */ + if (udev->bus == NULL) + continue; /* no BUS structure */ + if (udev->bus->methods == NULL) + continue; /* no BUS methods */ + if (udev->bus->methods->xfer_poll == NULL) + continue; /* no poll method */ + + /* make sure that the BUS mutex is not locked */ + drop_bus = 0; + while (mtx_owned(&xroot->udev->bus->bus_mtx) && !SCHEDULER_STOPPED()) { + mtx_unlock(&xroot->udev->bus->bus_mtx); + drop_bus++; + } + + /* make sure that the transfer mutex is not locked */ + drop_xfer = 0; + while (mtx_owned(xroot->xfer_mtx) && !SCHEDULER_STOPPED()) { + mtx_unlock(xroot->xfer_mtx); + drop_xfer++; + } + + /* Make sure cv_signal() and cv_broadcast() is not called */ + udev->bus->control_xfer_proc.up_msleep = 0; + udev->bus->explore_proc.up_msleep = 0; + udev->bus->giant_callback_proc.up_msleep = 0; + udev->bus->non_giant_callback_proc.up_msleep = 0; + + /* poll USB hardware */ + (udev->bus->methods->xfer_poll) (udev->bus); + + USB_BUS_LOCK(xroot->bus); + + /* check for clear stall */ + if (udev->ctrl_xfer[1] != NULL) { + + /* poll clear stall start */ + pm = &udev->cs_msg[0].hdr; + (pm->pm_callback) (pm); + /* poll clear stall done thread */ + pm = &udev->ctrl_xfer[1]-> + xroot->done_m[0].hdr; + (pm->pm_callback) (pm); + } + + /* poll done thread */ + pm = &xroot->done_m[0].hdr; + (pm->pm_callback) (pm); + + USB_BUS_UNLOCK(xroot->bus); + + /* restore transfer mutex */ + while (drop_xfer--) + mtx_lock(xroot->xfer_mtx); + + /* restore BUS mutex */ + while (drop_bus--) + mtx_lock(&xroot->udev->bus->bus_mtx); + } +} + +static void +usbd_get_std_packet_size(struct usb_std_packet_size *ptr, + uint8_t type, enum usb_dev_speed speed) +{ + static const uint16_t intr_range_max[USB_SPEED_MAX] = { + [USB_SPEED_LOW] = 8, + [USB_SPEED_FULL] = 64, + [USB_SPEED_HIGH] = 1024, + [USB_SPEED_VARIABLE] = 1024, + [USB_SPEED_SUPER] = 1024, + }; + + static const uint16_t isoc_range_max[USB_SPEED_MAX] = { + [USB_SPEED_LOW] = 0, /* invalid */ + [USB_SPEED_FULL] = 1023, + [USB_SPEED_HIGH] = 1024, + [USB_SPEED_VARIABLE] = 3584, + [USB_SPEED_SUPER] = 1024, + }; + + static const uint16_t control_min[USB_SPEED_MAX] = { + [USB_SPEED_LOW] = 8, + [USB_SPEED_FULL] = 8, + [USB_SPEED_HIGH] = 64, + [USB_SPEED_VARIABLE] = 512, + [USB_SPEED_SUPER] = 512, + }; + + static const uint16_t bulk_min[USB_SPEED_MAX] = { + [USB_SPEED_LOW] = 8, + [USB_SPEED_FULL] = 8, + [USB_SPEED_HIGH] = 512, + [USB_SPEED_VARIABLE] = 512, + [USB_SPEED_SUPER] = 1024, + }; + + uint16_t temp; + + memset(ptr, 0, sizeof(*ptr)); + + switch (type) { + case UE_INTERRUPT: + ptr->range.max = intr_range_max[speed]; + break; + case UE_ISOCHRONOUS: + ptr->range.max = isoc_range_max[speed]; + break; + default: + if (type == UE_BULK) + temp = bulk_min[speed]; + else /* UE_CONTROL */ + temp = control_min[speed]; + + /* default is fixed */ + ptr->fixed[0] = temp; + ptr->fixed[1] = temp; + ptr->fixed[2] = temp; + ptr->fixed[3] = temp; + + if (speed == USB_SPEED_FULL) { + /* multiple sizes */ + ptr->fixed[1] = 16; + ptr->fixed[2] = 32; + ptr->fixed[3] = 64; + } + if ((speed == USB_SPEED_VARIABLE) && + (type == UE_BULK)) { + /* multiple sizes */ + ptr->fixed[2] = 1024; + ptr->fixed[3] = 1536; + } + break; + } +} + +void * +usbd_xfer_softc(struct usb_xfer *xfer) +{ + return (xfer->priv_sc); +} + +void * +usbd_xfer_get_priv(struct usb_xfer *xfer) +{ + return (xfer->priv_fifo); +} + +void +usbd_xfer_set_priv(struct usb_xfer *xfer, void *ptr) +{ + xfer->priv_fifo = ptr; +} + +uint8_t +usbd_xfer_state(struct usb_xfer *xfer) +{ + return (xfer->usb_state); +} + +void +usbd_xfer_set_flag(struct usb_xfer *xfer, int flag) +{ + switch (flag) { + case USB_FORCE_SHORT_XFER: + xfer->flags.force_short_xfer = 1; + break; + case USB_SHORT_XFER_OK: + xfer->flags.short_xfer_ok = 1; + break; + case USB_MULTI_SHORT_OK: + xfer->flags.short_frames_ok = 1; + break; + case USB_MANUAL_STATUS: + xfer->flags.manual_status = 1; + break; + } +} + +void +usbd_xfer_clr_flag(struct usb_xfer *xfer, int flag) +{ + switch (flag) { + case USB_FORCE_SHORT_XFER: + xfer->flags.force_short_xfer = 0; + break; + case USB_SHORT_XFER_OK: + xfer->flags.short_xfer_ok = 0; + break; + case USB_MULTI_SHORT_OK: + xfer->flags.short_frames_ok = 0; + break; + case USB_MANUAL_STATUS: + xfer->flags.manual_status = 0; + break; + } +} + +/* + * The following function returns in milliseconds when the isochronous + * transfer was completed by the hardware. The returned value wraps + * around 65536 milliseconds. + */ +uint16_t +usbd_xfer_get_timestamp(struct usb_xfer *xfer) +{ + return (xfer->isoc_time_complete); +} diff --git a/sys/bus/u4b/usb_transfer.h b/sys/bus/u4b/usb_transfer.h new file mode 100644 index 0000000000..f035240b2b --- /dev/null +++ b/sys/bus/u4b/usb_transfer.h @@ -0,0 +1,254 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_TRANSFER_H_ +#define _USB_TRANSFER_H_ + +/* + * Definition of internal USB transfer states: + * =========================================== + * + * The main reason there are many USB states is that we are allowed to + * cancel USB transfers, then start the USB transfer again and that + * this state transaction cannot always be done in a single atomic + * operation without blocking the calling thread. One reason for this + * is that the USB hardware sometimes needs to wait for DMA + * controllers to finish which is done asynchronously and grows the + * statemachine. + * + * When extending the following statemachine there are basically two + * things you should think about: Which states should be executed or + * modified in case of USB transfer stop and which states should be + * executed or modified in case of USB transfer start. Also respect + * the "can_cancel_immed" flag which basically tells if you can go + * directly from a wait state to the cancelling states. + */ + +enum { + /* XFER start execute state */ + + /* USB_ST_SETUP = 0 (already defined) */ + + /* XFER transferred execute state */ + + /* USB_ST_TRANSFERRED = 1 (already defined) */ + + /* XFER error execute state */ + + /* USB_ST_ERROR = 2 (already defined) */ + + /* XFER restart after error execute state */ + + USB_ST_RESTART = 8, + + /* XFER transfer idle state */ + + USB_ST_WAIT_SETUP, + + /* Other XFER execute states */ + + USB_ST_PIPE_OPEN = 16, + USB_ST_PIPE_OPEN_ERROR, + USB_ST_PIPE_OPEN_RESTART, + + USB_ST_BDMA_LOAD, + USB_ST_BDMA_LOAD_ERROR, + USB_ST_BDMA_LOAD_RESTART, + + USB_ST_IVAL_DLY, + USB_ST_IVAL_DLY_ERROR, + USB_ST_IVAL_DLY_RESTART, + + USB_ST_PIPE_STALL, + USB_ST_PIPE_STALL_ERROR, + USB_ST_PIPE_STALL_RESTART, + + USB_ST_ENTER, + USB_ST_ENTER_ERROR, + USB_ST_ENTER_RESTART, + + USB_ST_START, + USB_ST_START_ERROR, + USB_ST_START_RESTART, + + USB_ST_PIPE_CLOSE, + USB_ST_PIPE_CLOSE_ERROR, + USB_ST_PIPE_CLOSE_RESTART, + + USB_ST_BDMA_DLY, + USB_ST_BDMA_DLY_ERROR, + USB_ST_BDMA_DLY_RESTART, + + /* XFER transfer wait states */ + + USB_ST_WAIT_PIPE_OPEN = 64, + USB_ST_WAIT_PIPE_OPEN_ERROR, + USB_ST_WAIT_PIPE_OPEN_RESTART, + + USB_ST_WAIT_BDMA_LOAD, + USB_ST_WAIT_BDMA_LOAD_ERROR, + USB_ST_WAIT_BDMA_LOAD_RESTART, + + USB_ST_WAIT_IVAL_DLY, + USB_ST_WAIT_IVAL_DLY_ERROR, + USB_ST_WAIT_IVAL_DLY_RESTART, + + USB_ST_WAIT_PIPE_STALL, + USB_ST_WAIT_PIPE_STALL_ERROR, + USB_ST_WAIT_PIPE_STALL_RESTART, + + USB_ST_WAIT_ENTER, + USB_ST_WAIT_ENTER_ERROR, + USB_ST_WAIT_ENTER_RESTART, + + USB_ST_WAIT_START, + USB_ST_WAIT_START_ERROR, + USB_ST_WAIT_START_RESTART, + + USB_ST_WAIT_PIPE_CLOSE, + USB_ST_WAIT_PIPE_CLOSE_ERROR, + USB_ST_WAIT_PIPE_CLOSE_RESTART, + + USB_ST_WAIT_BDMA_DLY, + USB_ST_WAIT_BDMA_DLY_ERROR, + USB_ST_WAIT_BDMA_DLY_RESTART, + + USB_ST_WAIT_TRANSFERRED, + USB_ST_WAIT_TRANSFERRED_ERROR, + USB_ST_WAIT_TRANSFERRED_RESTART, +}; + +/* + * The following structure defines the messages that is used to signal + * the "done_p" USB process. + */ +struct usb_done_msg { + struct usb_proc_msg hdr; + struct usb_xfer_root *xroot; +}; + +#define USB_DMATAG_TO_XROOT(dpt) \ + ((struct usb_xfer_root *)( \ + ((uint8_t *)(dpt)) - \ + ((uint8_t *)&((struct usb_xfer_root *)0)->dma_parent_tag))) + +/* + * The following structure is used to keep information about memory + * that should be automatically freed at the moment all USB transfers + * have been freed. + */ +struct usb_xfer_root { + struct usb_dma_parent_tag dma_parent_tag; +#if USB_HAVE_BUSDMA + struct usb_xfer_queue dma_q; +#endif + struct usb_xfer_queue done_q; + struct usb_done_msg done_m[2]; + struct cv cv_drain; + + struct usb_process *done_p; /* pointer to callback process */ + void *memory_base; + struct mtx *xfer_mtx; /* cannot be changed during operation */ +#if USB_HAVE_BUSDMA + struct usb_page_cache *dma_page_cache_start; + struct usb_page_cache *dma_page_cache_end; +#endif + struct usb_page_cache *xfer_page_cache_start; + struct usb_page_cache *xfer_page_cache_end; + struct usb_bus *bus; /* pointer to USB bus (cached) */ + struct usb_device *udev; /* pointer to USB device */ + + usb_size_t memory_size; + usb_size_t setup_refcount; +#if USB_HAVE_BUSDMA + usb_frcount_t dma_nframes; /* number of page caches to load */ + usb_frcount_t dma_currframe; /* currect page cache number */ + usb_frlength_t dma_frlength_0; /* length of page cache zero */ + uint8_t dma_error; /* set if virtual memory could not be + * loaded */ +#endif + uint8_t done_sleep; /* set if done thread is sleeping */ +}; + +/* + * The following structure is used when setting up an array of USB + * transfers. + */ +struct usb_setup_params { + struct usb_dma_tag *dma_tag_p; + struct usb_page *dma_page_ptr; + struct usb_page_cache *dma_page_cache_ptr; /* these will be + * auto-freed */ + struct usb_page_cache *xfer_page_cache_ptr; /* these will not be + * auto-freed */ + struct usb_device *udev; + struct usb_xfer *curr_xfer; + const struct usb_config *curr_setup; + const struct usb_pipe_methods *methods; + void *buf; + usb_frlength_t *xfer_length_ptr; + + usb_size_t size[7]; + usb_frlength_t bufsize; + usb_frlength_t bufsize_max; + + uint32_t hc_max_frame_size; + uint16_t hc_max_packet_size; + uint8_t hc_max_packet_count; + enum usb_dev_speed speed; + uint8_t dma_tag_max; + usb_error_t err; +}; + +/* function prototypes */ + +uint8_t usbd_transfer_setup_sub_malloc(struct usb_setup_params *parm, + struct usb_page_cache **ppc, usb_size_t size, usb_size_t align, + usb_size_t count); +void usb_dma_delay_done_cb(struct usb_xfer *); +void usb_command_wrapper(struct usb_xfer_queue *pq, + struct usb_xfer *xfer); +void usbd_pipe_enter(struct usb_xfer *xfer); +void usbd_pipe_start(struct usb_xfer_queue *pq); +void usbd_transfer_dequeue(struct usb_xfer *xfer); +void usbd_transfer_done(struct usb_xfer *xfer, usb_error_t error); +void usbd_transfer_enqueue(struct usb_xfer_queue *pq, + struct usb_xfer *xfer); +void usbd_transfer_setup_sub(struct usb_setup_params *parm); +void usbd_ctrl_transfer_setup(struct usb_device *udev); +void usbd_clear_stall_locked(struct usb_device *udev, + struct usb_endpoint *ep); +void usbd_clear_data_toggle(struct usb_device *udev, + struct usb_endpoint *ep); +usb_callback_t usbd_do_request_callback; +usb_callback_t usb_handle_request_callback; +usb_callback_t usb_do_clear_stall_callback; +void usbd_transfer_timeout_ms(struct usb_xfer *xfer, + void (*cb) (void *arg), usb_timeout_t ms); +usb_timeout_t usbd_get_dma_delay(struct usb_device *udev); +void usbd_transfer_power_ref(struct usb_xfer *xfer, int val); + +#endif /* _USB_TRANSFER_H_ */ diff --git a/sys/bus/u4b/usb_util.c b/sys/bus/u4b/usb_util.c new file mode 100644 index 0000000000..4fe79c5b33 --- /dev/null +++ b/sys/bus/u4b/usb_util.c @@ -0,0 +1,211 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +/*------------------------------------------------------------------------* + * device_set_usb_desc + * + * This function can be called at probe or attach to set the USB + * device supplied textual description for the given device. + *------------------------------------------------------------------------*/ +void +device_set_usb_desc(device_t dev) +{ + struct usb_attach_arg *uaa; + struct usb_device *udev; + struct usb_interface *iface; + char *temp_p; + usb_error_t err; + + if (dev == NULL) { + /* should not happen */ + return; + } + uaa = device_get_ivars(dev); + if (uaa == NULL) { + /* can happen if called at the wrong time */ + return; + } + udev = uaa->device; + iface = uaa->iface; + + if ((iface == NULL) || + (iface->idesc == NULL) || + (iface->idesc->iInterface == 0)) { + err = USB_ERR_INVAL; + } else { + err = 0; + } + + temp_p = (char *)udev->bus->scratch[0].data; + + if (!err) { + /* try to get the interface string ! */ + err = usbd_req_get_string_any + (udev, NULL, temp_p, + sizeof(udev->bus->scratch), iface->idesc->iInterface); + } + if (err) { + /* use default description */ + usb_devinfo(udev, temp_p, + sizeof(udev->bus->scratch)); + } + device_set_desc_copy(dev, temp_p); + device_printf(dev, "<%s> on %s\n", temp_p, + device_get_nameunit(udev->bus->bdev)); +} + +/*------------------------------------------------------------------------* + * usb_pause_mtx - factored out code + * + * This function will delay the code by the passed number of system + * ticks. The passed mutex "mtx" will be dropped while waiting, if + * "mtx" is different from NULL. + *------------------------------------------------------------------------*/ +void +usb_pause_mtx(struct mtx *mtx, int timo) +{ + if (mtx != NULL) + mtx_unlock(mtx); + + /* + * Add one tick to the timeout so that we don't return too + * early! Note that pause() will assert that the passed + * timeout is positive and non-zero! + */ + pause("USBWAIT", timo + 1); + + if (mtx != NULL) + mtx_lock(mtx); +} + +/*------------------------------------------------------------------------* + * usb_printbcd + * + * This function will print the version number "bcd" to the string + * pointed to by "p" having a maximum length of "p_len" bytes + * including the terminating zero. + *------------------------------------------------------------------------*/ +void +usb_printbcd(char *p, uint16_t p_len, uint16_t bcd) +{ + if (snprintf(p, p_len, "%x.%02x", bcd >> 8, bcd & 0xff)) { + /* ignore any errors */ + } +} + +/*------------------------------------------------------------------------* + * usb_trim_spaces + * + * This function removes spaces at the beginning and the end of the string + * pointed to by the "p" argument. + *------------------------------------------------------------------------*/ +void +usb_trim_spaces(char *p) +{ + char *q; + char *e; + + if (p == NULL) + return; + q = e = p; + while (*q == ' ') /* skip leading spaces */ + q++; + while ((*p = *q++)) /* copy string */ + if (*p++ != ' ') /* remember last non-space */ + e = p; + *e = 0; /* kill trailing spaces */ +} + +/*------------------------------------------------------------------------* + * usb_make_str_desc - convert an ASCII string into a UNICODE string + *------------------------------------------------------------------------*/ +uint8_t +usb_make_str_desc(void *ptr, uint16_t max_len, const char *s) +{ + struct usb_string_descriptor *p = ptr; + uint8_t totlen; + int j; + + if (max_len < 2) { + /* invalid length */ + return (0); + } + max_len = ((max_len / 2) - 1); + + j = strlen(s); + + if (j < 0) { + j = 0; + } + if (j > 126) { + j = 126; + } + if (max_len > j) { + max_len = j; + } + totlen = (max_len + 1) * 2; + + p->bLength = totlen; + p->bDescriptorType = UDESC_STRING; + + while (max_len--) { + USETW2(p->bString[max_len], 0, s[max_len]); + } + return (totlen); +} diff --git a/sys/bus/u4b/usb_util.h b/sys/bus/u4b/usb_util.h new file mode 100644 index 0000000000..7e52404f1f --- /dev/null +++ b/sys/bus/u4b/usb_util.h @@ -0,0 +1,34 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_UTIL_H_ +#define _USB_UTIL_H_ + +uint8_t usb_make_str_desc(void *ptr, uint16_t max_len, const char *s); +void usb_printbcd(char *p, uint16_t p_len, uint16_t bcd); +void usb_trim_spaces(char *p); + +#endif /* _USB_UTIL_H_ */ diff --git a/sys/bus/u4b/usbdevs b/sys/bus/u4b/usbdevs new file mode 100644 index 0000000000..c43d5f929c --- /dev/null +++ b/sys/bus/u4b/usbdevs @@ -0,0 +1,3523 @@ +$FreeBSD$ +/* $NetBSD: usbdevs,v 1.392 2004/12/29 08:38:44 imp Exp $ */ + +/*- + * Copyright (c) 1998-2004 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * List of known USB vendors + * + * USB.org publishes a VID list of USB-IF member companies at + * http://www.usb.org/developers/tools + * Note that it does not show companies that have obtained a Vendor ID + * without becoming full members. + * + * Please note that these IDs do not do anything. Adding an ID here and + * regenerating the usbdevs.h and usbdevs_data.h only makes a symbolic name + * available to the source code and does not change any functionality, nor + * does it make your device available to a specific driver. + * It will however make the descriptive string available if a device does not + * provide the string itself. + * + * After adding a vendor ID VNDR and a product ID PRDCT you will have the + * following extra defines: + * #define USB_VENDOR_VNDR 0x???? + * #define USB_PRODUCT_VNDR_PRDCT 0x???? + * + * You may have to add these defines to the respective probe routines to + * make the device recognised by the appropriate device driver. + */ + +vendor UNKNOWN1 0x0053 Unknown vendor +vendor UNKNOWN2 0x0105 Unknown vendor +vendor EGALAX2 0x0123 eGalax, Inc. +vendor CHIPSBANK 0x0204 Chipsbank Microelectronics Co. +vendor HUMAX 0x02ad HUMAX +vendor LTS 0x0386 LTS +vendor BWCT 0x03da Bernd Walter Computer Technology +vendor AOX 0x03e8 AOX +vendor THESYS 0x03e9 Thesys +vendor DATABROADCAST 0x03ea Data Broadcasting +vendor ATMEL 0x03eb Atmel +vendor IWATSU 0x03ec Iwatsu America +vendor MITSUMI 0x03ee Mitsumi +vendor HP 0x03f0 Hewlett Packard +vendor GENOA 0x03f1 Genoa +vendor OAK 0x03f2 Oak +vendor ADAPTEC 0x03f3 Adaptec +vendor DIEBOLD 0x03f4 Diebold +vendor SIEMENSELECTRO 0x03f5 Siemens Electromechanical +vendor EPSONIMAGING 0x03f8 Epson Imaging +vendor KEYTRONIC 0x03f9 KeyTronic +vendor OPTI 0x03fb OPTi +vendor ELITEGROUP 0x03fc Elitegroup +vendor XILINX 0x03fd Xilinx +vendor FARALLON 0x03fe Farallon Communications +vendor NATIONAL 0x0400 National Semiconductor +vendor NATIONALREG 0x0401 National Registry +vendor ACERLABS 0x0402 Acer Labs +vendor FTDI 0x0403 Future Technology Devices +vendor NCR 0x0404 NCR +vendor SYNOPSYS2 0x0405 Synopsys +vendor FUJITSUICL 0x0406 Fujitsu-ICL +vendor FUJITSU2 0x0407 Fujitsu Personal Systems +vendor QUANTA 0x0408 Quanta +vendor NEC 0x0409 NEC +vendor KODAK 0x040a Eastman Kodak +vendor WELTREND 0x040b Weltrend +vendor VIA 0x040d VIA +vendor MCCI 0x040e MCCI +vendor MELCO 0x0411 Melco +vendor LEADTEK 0x0413 Leadtek +vendor WINBOND 0x0416 Winbond +vendor PHOENIX 0x041a Phoenix +vendor CREATIVE 0x041e Creative Labs +vendor NOKIA 0x0421 Nokia +vendor ADI 0x0422 ADI Systems +vendor CATC 0x0423 Computer Access Technology +vendor SMC2 0x0424 Standard Microsystems +vendor MOTOROLA_HK 0x0425 Motorola HK +vendor GRAVIS 0x0428 Advanced Gravis Computer +vendor CIRRUSLOGIC 0x0429 Cirrus Logic +vendor INNOVATIVE 0x042c Innovative Semiconductors +vendor MOLEX 0x042f Molex +vendor SUN 0x0430 Sun Microsystems +vendor UNISYS 0x0432 Unisys +vendor TAUGA 0x0436 Taugagreining HF +vendor AMD 0x0438 Advanced Micro Devices +vendor LEXMARK 0x043d Lexmark International +vendor LG 0x043e LG Electronics +vendor NANAO 0x0440 NANAO +vendor GATEWAY 0x0443 Gateway 2000 +vendor NMB 0x0446 NMB +vendor ALPS 0x044e Alps Electric +vendor THRUST 0x044f Thrustmaster +vendor TI 0x0451 Texas Instruments +vendor ANALOGDEVICES 0x0456 Analog Devices +vendor SIS 0x0457 Silicon Integrated Systems Corp. +vendor KYE 0x0458 KYE Systems +vendor DIAMOND2 0x045a Diamond (Supra) +vendor RENESAS 0x045b Renesas +vendor MICROSOFT 0x045e Microsoft +vendor PRIMAX 0x0461 Primax Electronics +vendor MGE 0x0463 MGE UPS Systems +vendor AMP 0x0464 AMP +vendor CHERRY 0x046a Cherry Mikroschalter +vendor MEGATRENDS 0x046b American Megatrends +vendor LOGITECH 0x046d Logitech +vendor BTC 0x046e Behavior Tech. Computer +vendor PHILIPS 0x0471 Philips +vendor SUN2 0x0472 Sun Microsystems (offical) +vendor SANYO 0x0474 Sanyo Electric +vendor SEAGATE 0x0477 Seagate +vendor CONNECTIX 0x0478 Connectix +vendor SEMTECH 0x047a Semtech +vendor KENSINGTON 0x047d Kensington +vendor LUCENT 0x047e Lucent +vendor PLANTRONICS 0x047f Plantronics +vendor KYOCERA 0x0482 Kyocera Wireless Corp. +vendor STMICRO 0x0483 STMicroelectronics +vendor FOXCONN 0x0489 Foxconn +vendor MEIZU 0x0492 Meizu Electronics +vendor YAMAHA 0x0499 YAMAHA +vendor COMPAQ 0x049f Compaq +vendor HITACHI 0x04a4 Hitachi +vendor ACERP 0x04a5 Acer Peripherals +vendor DAVICOM 0x04a6 Davicom +vendor VISIONEER 0x04a7 Visioneer +vendor CANON 0x04a9 Canon +vendor NIKON 0x04b0 Nikon +vendor PAN 0x04b1 Pan International +vendor IBM 0x04b3 IBM +vendor CYPRESS 0x04b4 Cypress Semiconductor +vendor ROHM 0x04b5 ROHM +vendor COMPAL 0x04b7 Compal +vendor EPSON 0x04b8 Seiko Epson +vendor RAINBOW 0x04b9 Rainbow Technologies +vendor IODATA 0x04bb I-O Data +vendor TDK 0x04bf TDK +vendor 3COMUSR 0x04c1 U.S. Robotics +vendor METHODE 0x04c2 Methode Electronics Far East +vendor MAXISWITCH 0x04c3 Maxi Switch +vendor LOCKHEEDMER 0x04c4 Lockheed Martin Energy Research +vendor FUJITSU 0x04c5 Fujitsu +vendor TOSHIBAAM 0x04c6 Toshiba America +vendor MICROMACRO 0x04c7 Micro Macro Technologies +vendor KONICA 0x04c8 Konica +vendor LITEON 0x04ca Lite-On Technology +vendor FUJIPHOTO 0x04cb Fuji Photo Film +vendor PHILIPSSEMI 0x04cc Philips Semiconductors +vendor TATUNG 0x04cd Tatung Co. Of America +vendor SCANLOGIC 0x04ce ScanLogic +vendor MYSON 0x04cf Myson Technology +vendor DIGI2 0x04d0 Digi +vendor ITTCANON 0x04d1 ITT Canon +vendor ALTEC 0x04d2 Altec Lansing +vendor LSI 0x04d4 LSI +vendor MENTORGRAPHICS 0x04d6 Mentor Graphics +vendor ITUNERNET 0x04d8 I-Tuner Networks +vendor HOLTEK 0x04d9 Holtek Semiconductor, Inc. +vendor PANASONIC 0x04da Panasonic (Matsushita) +vendor HUANHSIN 0x04dc Huan Hsin +vendor SHARP 0x04dd Sharp +vendor IIYAMA 0x04e1 Iiyama +vendor SHUTTLE 0x04e6 Shuttle Technology +vendor ELO 0x04e7 Elo TouchSystems +vendor SAMSUNG 0x04e8 Samsung Electronics +vendor NORTHSTAR 0x04eb Northstar +vendor TOKYOELECTRON 0x04ec Tokyo Electron +vendor ANNABOOKS 0x04ed Annabooks +vendor JVC 0x04f1 JVC +vendor CHICONY 0x04f2 Chicony Electronics +vendor ELAN 0x04f3 Elan +vendor NEWNEX 0x04f7 Newnex +vendor BROTHER 0x04f9 Brother Industries +vendor DALLAS 0x04fa Dallas Semiconductor +vendor AIPTEK2 0x04fc AIPTEK International +vendor PFU 0x04fe PFU +vendor FUJIKURA 0x0501 Fujikura/DDK +vendor ACER 0x0502 Acer +vendor 3COM 0x0506 3Com +vendor HOSIDEN 0x0507 Hosiden Corporation +vendor AZTECH 0x0509 Aztech Systems +vendor BELKIN 0x050d Belkin Components +vendor KAWATSU 0x050f Kawatsu Semiconductor +vendor FCI 0x0514 FCI +vendor LONGWELL 0x0516 Longwell +vendor COMPOSITE 0x0518 Composite +vendor STAR 0x0519 Star Micronics +vendor APC 0x051d American Power Conversion +vendor SCIATLANTA 0x051e Scientific Atlanta +vendor TSM 0x0520 TSM +vendor CONNECTEK 0x0522 Advanced Connectek USA +vendor NETCHIP 0x0525 NetChip Technology +vendor ALTRA 0x0527 ALTRA +vendor ATI 0x0528 ATI Technologies +vendor AKS 0x0529 Aladdin Knowledge Systems +vendor TEKOM 0x052b Tekom +vendor CANONDEV 0x052c Canon +vendor WACOMTECH 0x0531 Wacom +vendor INVENTEC 0x0537 Inventec +vendor SHYHSHIUN 0x0539 Shyh Shiun Terminals +vendor PREHWERKE 0x053a Preh Werke Gmbh & Co. KG +vendor SYNOPSYS 0x053f Synopsys +vendor UNIACCESS 0x0540 Universal Access +vendor VIEWSONIC 0x0543 ViewSonic +vendor XIRLINK 0x0545 Xirlink +vendor ANCHOR 0x0547 Anchor Chips +vendor SONY 0x054c Sony +vendor FUJIXEROX 0x0550 Fuji Xerox +vendor VISION 0x0553 VLSI Vision +vendor ASAHIKASEI 0x0556 Asahi Kasei Microsystems +vendor ATEN 0x0557 ATEN International +vendor SAMSUNG2 0x055d Samsung Electronics +vendor MUSTEK 0x055f Mustek Systems +vendor TELEX 0x0562 Telex Communications +vendor CHINON 0x0564 Chinon +vendor PERACOM 0x0565 Peracom Networks +vendor ALCOR2 0x0566 Alcor Micro +vendor XYRATEX 0x0567 Xyratex +vendor WACOM 0x056a WACOM +vendor ETEK 0x056c e-TEK Labs +vendor EIZO 0x056d EIZO +vendor ELECOM 0x056e Elecom +vendor CONEXANT 0x0572 Conexant +vendor HAUPPAUGE 0x0573 Hauppauge Computer Works +vendor BAFO 0x0576 BAFO/Quality Computer Accessories +vendor YEDATA 0x057b Y-E Data +vendor AVM 0x057c AVM +vendor QUICKSHOT 0x057f Quickshot +vendor ROLAND 0x0582 Roland +vendor ROCKFIRE 0x0583 Rockfire +vendor RATOC 0x0584 RATOC Systems +vendor ZYXEL 0x0586 ZyXEL Communication +vendor INFINEON 0x058b Infineon +vendor MICREL 0x058d Micrel +vendor ALCOR 0x058f Alcor Micro +vendor OMRON 0x0590 OMRON +vendor ZORAN 0x0595 Zoran Microelectronics +vendor NIIGATA 0x0598 Niigata +vendor IOMEGA 0x059b Iomega +vendor ATREND 0x059c A-Trend Technology +vendor AID 0x059d Advanced Input Devices +vendor LACIE 0x059f LaCie +vendor FUJIFILM 0x05a2 Fuji Film +vendor ARC 0x05a3 ARC +vendor ORTEK 0x05a4 Ortek +vendor CISCOLINKSYS3 0x05a6 Cisco-Linksys +vendor BOSE 0x05a7 Bose +vendor OMNIVISION 0x05a9 OmniVision +vendor INSYSTEM 0x05ab In-System Design +vendor APPLE 0x05ac Apple Computer +vendor YCCABLE 0x05ad Y.C. Cable +vendor DIGITALPERSONA 0x05ba DigitalPersona +vendor 3G 0x05bc 3G Green Green Globe +vendor RAFI 0x05bd RAFI +vendor TYCO 0x05be Tyco +vendor KAWASAKI 0x05c1 Kawasaki +vendor DIGI 0x05c5 Digi International +vendor QUALCOMM2 0x05c6 Qualcomm +vendor QTRONIX 0x05c7 Qtronix +vendor FOXLINK 0x05c8 Foxlink +vendor RICOH 0x05ca Ricoh +vendor ELSA 0x05cc ELSA +vendor SCIWORX 0x05ce sci-worx +vendor BRAINBOXES 0x05d1 Brainboxes Limited +vendor ULTIMA 0x05d8 Ultima +vendor AXIOHM 0x05d9 Axiohm Transaction Solutions +vendor MICROTEK 0x05da Microtek +vendor SUNTAC 0x05db SUN Corporation +vendor LEXAR 0x05dc Lexar Media +vendor ADDTRON 0x05dd Addtron +vendor SYMBOL 0x05e0 Symbol Technologies +vendor SYNTEK 0x05e1 Syntek +vendor GENESYS 0x05e3 Genesys Logic +vendor FUJI 0x05e5 Fuji Electric +vendor KEITHLEY 0x05e6 Keithley Instruments +vendor EIZONANAO 0x05e7 EIZO Nanao +vendor KLSI 0x05e9 Kawasaki LSI +vendor FFC 0x05eb FFC +vendor ANKO 0x05ef Anko Electronic +vendor PIENGINEERING 0x05f3 P.I. Engineering +vendor AOC 0x05f6 AOC International +vendor CHIC 0x05fe Chic Technology +vendor BARCO 0x0600 Barco Display Systems +vendor BRIDGE 0x0607 Bridge Information +vendor SOLIDYEAR 0x060b Solid Year +vendor BIORAD 0x0614 Bio-Rad Laboratories +vendor MACALLY 0x0618 Macally +vendor ACTLABS 0x061c Act Labs +vendor ALARIS 0x0620 Alaris +vendor APEX 0x0624 Apex +vendor CREATIVE3 0x062a Creative Labs +vendor MICRON 0x0634 Micron Technology +vendor VIVITAR 0x0636 Vivitar +vendor GUNZE 0x0637 Gunze Electronics USA +vendor AVISION 0x0638 Avision +vendor TEAC 0x0644 TEAC +vendor SGI 0x065e Silicon Graphics +vendor SANWASUPPLY 0x0663 Sanwa Supply +vendor MEGATEC 0x0665 Megatec +vendor LINKSYS 0x066b Linksys +vendor ACERSA 0x066e Acer Semiconductor America +vendor SIGMATEL 0x066f Sigmatel +vendor DRAYTEK 0x0675 DrayTek +vendor AIWA 0x0677 Aiwa +vendor ACARD 0x0678 ACARD Technology +vendor PROLIFIC 0x067b Prolific Technology +vendor SIEMENS 0x067c Siemens +vendor AVANCELOGIC 0x0680 Avance Logic +vendor SIEMENS2 0x0681 Siemens +vendor MINOLTA 0x0686 Minolta +vendor CHPRODUCTS 0x068e CH Products +vendor HAGIWARA 0x0693 Hagiwara Sys-Com +vendor CTX 0x0698 Chuntex +vendor ASKEY 0x069a Askey Computer +vendor SAITEK 0x06a3 Saitek +vendor ALCATELT 0x06b9 Alcatel Telecom +vendor AGFA 0x06bd AGFA-Gevaert +vendor ASIAMD 0x06be Asia Microelectronic Development +vendor BIZLINK 0x06c4 Bizlink International +vendor KEYSPAN 0x06cd Keyspan / InnoSys Inc. +vendor AASHIMA 0x06d6 Aashima Technology +vendor LIEBERT 0x06da Liebert +vendor MULTITECH 0x06e0 MultiTech +vendor ADS 0x06e1 ADS Technologies +vendor ALCATELM 0x06e4 Alcatel Microelectronics +vendor SIRIUS 0x06ea Sirius Technologies +vendor GUILLEMOT 0x06f8 Guillemot +vendor BOSTON 0x06fd Boston Acoustics +vendor SMC 0x0707 Standard Microsystems +vendor PUTERCOM 0x0708 Putercom +vendor MCT 0x0711 MCT +vendor IMATION 0x0718 Imation +vendor TECLAST 0x071b Teclast +vendor SONYERICSSON 0x0731 Sony Ericsson +vendor EICON 0x0734 Eicon Networks +vendor SYNTECH 0x0745 Syntech Information +vendor DIGITALSTREAM 0x074e Digital Stream +vendor AUREAL 0x0755 Aureal Semiconductor +vendor MIDIMAN 0x0763 Midiman +vendor CYBERPOWER 0x0764 Cyber Power Systems, Inc. +vendor SURECOM 0x0769 Surecom Technology +vendor HIDGLOBAL 0x076b HID Global +vendor LINKSYS2 0x077b Linksys +vendor GRIFFIN 0x077d Griffin Technology +vendor SANDISK 0x0781 SanDisk +vendor JENOPTIK 0x0784 Jenoptik +vendor LOGITEC 0x0789 Logitec +vendor NOKIA2 0x078b Nokia +vendor BRIMAX 0x078e Brimax +vendor AXIS 0x0792 Axis Communications +vendor ABL 0x0794 ABL Electronics +vendor SAGEM 0x079b Sagem +vendor SUNCOMM 0x079c Sun Communications, Inc. +vendor ALFADATA 0x079d Alfadata Computer +vendor NATIONALTECH 0x07a2 National Technical Systems +vendor ONNTO 0x07a3 Onnto +vendor BE 0x07a4 Be +vendor ADMTEK 0x07a6 ADMtek +vendor COREGA 0x07aa Corega +vendor FREECOM 0x07ab Freecom +vendor MICROTECH 0x07af Microtech +vendor GENERALINSTMNTS 0x07b2 General Instruments (Motorola) +vendor OLYMPUS 0x07b4 Olympus +vendor ABOCOM 0x07b8 AboCom Systems +vendor KEISOKUGIKEN 0x07c1 Keisokugiken +vendor ONSPEC 0x07c4 OnSpec +vendor APG 0x07c5 APG Cash Drawer +vendor BUG 0x07c8 B.U.G. +vendor ALLIEDTELESYN 0x07c9 Allied Telesyn International +vendor AVERMEDIA 0x07ca AVerMedia Technologies +vendor SIIG 0x07cc SIIG +vendor CASIO 0x07cf CASIO +vendor DLINK2 0x07d1 D-Link +vendor APTIO 0x07d2 Aptio Products +vendor ARASAN 0x07da Arasan Chip Systems +vendor ALLIEDCABLE 0x07e6 Allied Cable +vendor STSN 0x07ef STSN +vendor CENTURY 0x07f7 Century Corp +vendor NEWLINK 0x07ff NEWlink +vendor ZOOM 0x0803 Zoom Telephonics +vendor PCS 0x0810 Personal Communication Systems +vendor ALPHASMART 0x081e AlphaSmart, Inc. +vendor BROADLOGIC 0x0827 BroadLogic +vendor HANDSPRING 0x082d Handspring +vendor PALM 0x0830 Palm Computing +vendor SOURCENEXT 0x0833 SOURCENEXT +vendor ACTIONSTAR 0x0835 Action Star Enterprise +vendor SAMSUNG_TECHWIN 0x0839 Samsung Techwin +vendor ACCTON 0x083a Accton Technology +vendor DIAMOND 0x0841 Diamond +vendor NETGEAR 0x0846 BayNETGEAR +vendor TOPRE 0x0853 Topre Corporation +vendor ACTIVEWIRE 0x0854 ActiveWire +vendor BBELECTRONICS 0x0856 B&B Electronics +vendor PORTGEAR 0x085a PortGear +vendor NETGEAR2 0x0864 Netgear +vendor SYSTEMTALKS 0x086e System Talks +vendor METRICOM 0x0870 Metricom +vendor ADESSOKBTEK 0x087c ADESSO/Kbtek America +vendor JATON 0x087d Jaton +vendor APT 0x0880 APT Technologies +vendor BOCARESEARCH 0x0885 Boca Research +vendor ANDREA 0x08a8 Andrea Electronics +vendor BURRBROWN 0x08bb Burr-Brown Japan +vendor 2WIRE 0x08c8 2Wire +vendor AIPTEK 0x08ca AIPTEK International +vendor SMARTBRIDGES 0x08d1 SmartBridges +vendor FUJITSUSIEMENS 0x08d4 Fujitsu-Siemens +vendor BILLIONTON 0x08dd Billionton Systems +vendor GEMALTO 0x08e6 Gemalto SA +vendor EXTENDED 0x08e9 Extended Systems +vendor MSYSTEMS 0x08ec M-Systems +vendor DIGIANSWER 0x08fd Digianswer +vendor AUTHENTEC 0x08ff AuthenTec +vendor AUDIOTECHNICA 0x0909 Audio-Technica +vendor TRUMPION 0x090a Trumpion Microelectronics +vendor FEIYA 0x090c Feiya +vendor ALATION 0x0910 Alation Systems +vendor GLOBESPAN 0x0915 Globespan +vendor CONCORDCAMERA 0x0919 Concord Camera +vendor GARMIN 0x091e Garmin International +vendor GOHUBS 0x0921 GoHubs +vendor XEROX 0x0924 Xerox +vendor BIOMETRIC 0x0929 American Biometric Company +vendor TOSHIBA 0x0930 Toshiba +vendor PLEXTOR 0x093b Plextor +vendor INTREPIDCS 0x093c Intrepid +vendor YANO 0x094f Yano +vendor KINGSTON 0x0951 Kingston Technology +vendor BLUEWATER 0x0956 BlueWater Systems +vendor AGILENT 0x0957 Agilent Technologies +vendor GUDE 0x0959 Gude ADS +vendor PORTSMITH 0x095a Portsmith +vendor ACERW 0x0967 Acer +vendor ADIRONDACK 0x0976 Adirondack Wire & Cable +vendor BECKHOFF 0x0978 Beckhoff +vendor MINDSATWORK 0x097a Minds At Work +vendor POINTCHIPS 0x09a6 PointChips +vendor INTERSIL 0x09aa Intersil +vendor ALTIUS 0x09b3 Altius Solutions +vendor ARRIS 0x09c1 Arris Interactive +vendor ACTIVCARD 0x09c3 ACTIVCARD +vendor ACTISYS 0x09c4 ACTiSYS +vendor NOVATEL2 0x09d7 Novatel Wireless +vendor AFOURTECH 0x09da A-FOUR TECH +vendor AIMEX 0x09dc AIMEX +vendor ADDONICS 0x09df Addonics Technologies +vendor AKAI 0x09e8 AKAI professional M.I. +vendor ARESCOM 0x09f5 ARESCOM +vendor BAY 0x09f9 Bay Associates +vendor ALTERA 0x09fb Altera +vendor CSR 0x0a12 Cambridge Silicon Radio +vendor TREK 0x0a16 Trek Technology +vendor ASAHIOPTICAL 0x0a17 Asahi Optical +vendor BOCASYSTEMS 0x0a43 Boca Systems +vendor SHANTOU 0x0a46 ShanTou +vendor MEDIAGEAR 0x0a48 MediaGear +vendor BROADCOM 0x0a5c Broadcom +vendor GREENHOUSE 0x0a6b GREENHOUSE +vendor MEDELI 0x0a67 Medeli +vendor GEOCAST 0x0a79 Geocast Network Systems +vendor IDQUANTIQUE 0x0aba id Quantique +vendor ZYDAS 0x0ace Zydas Technology Corporation +vendor NEODIO 0x0aec Neodio +vendor OPTION 0x0af0 Option N.V. +vendor ASUS 0x0b05 ASUSTeK Computer +vendor TODOS 0x0b0c Todos Data System +vendor SIIG2 0x0b39 SIIG +vendor TEKRAM 0x0b3b Tekram Technology +vendor HAL 0x0b41 HAL Corporation +vendor EMS 0x0b43 EMS Production +vendor NEC2 0x0b62 NEC +vendor ADLINK 0x0b63 ADLINK Technoligy, Inc. +vendor ATI2 0x0b6f ATI +vendor ZEEVO 0x0b7a Zeevo, Inc. +vendor KURUSUGAWA 0x0b7e Kurusugawa Electronics, Inc. +vendor SMART 0x0b8c Smart Technologies +vendor ASIX 0x0b95 ASIX Electronics +vendor O2MICRO 0x0b97 O2 Micro, Inc. +vendor USR 0x0baf U.S. Robotics +vendor AMBIT 0x0bb2 Ambit Microsystems +vendor HTC 0x0bb4 HTC +vendor REALTEK 0x0bda Realtek +vendor MEI 0x0bed MEI +vendor ADDONICS2 0x0bf6 Addonics Technology +vendor FSC 0x0bf8 Fujitsu Siemens Computers +vendor AGATE 0x0c08 Agate Technologies +vendor DMI 0x0c0b DMI +vendor CHICONY2 0x0c45 Chicony +vendor REINERSCT 0x0c4b Reiner-SCT +vendor SEALEVEL 0x0c52 Sealevel System +vendor LUWEN 0x0c76 Luwen +vendor KYOCERA2 0x0c88 Kyocera Wireless Corp. +vendor ZCOM 0x0cde Z-Com +vendor ATHEROS2 0x0cf3 Atheros Communications +vendor TANGTOP 0x0d3d Tangtop +vendor SMC3 0x0d5c Standard Microsystems +vendor ADDON 0x0d7d Add-on Technology +vendor ACDC 0x0d7e American Computer & Digital Components +vendor CMEDIA 0x0d8c CMEDIA +vendor CONCEPTRONIC 0x0d8e Conceptronic +vendor SKANHEX 0x0d96 Skanhex Technology, Inc. +vendor MSI 0x0db0 Micro Star International +vendor ELCON 0x0db7 ELCON Systemtechnik +vendor NETAC 0x0dd8 Netac +vendor SITECOMEU 0x0df6 Sitecom Europe +vendor MOBILEACTION 0x0df7 Mobile Action +vendor AMIGO 0x0e0b Amigo Technology +vendor SPEEDDRAGON 0x0e55 Speed Dragon Multimedia +vendor HAWKING 0x0e66 Hawking +vendor FOSSIL 0x0e67 Fossil, Inc +vendor GMATE 0x0e7e G.Mate, Inc +vendor MEDIATEK 0x0e8d MediaTek, Inc. +vendor OTI 0x0ea0 Ours Technology +vendor YISO 0x0eab Yiso Wireless Co. +vendor PILOTECH 0x0eaf Pilotech +vendor NOVATECH 0x0eb0 NovaTech +vendor ITEGNO 0x0eba iTegno +vendor WINMAXGROUP 0x0ed1 WinMaxGroup +vendor TOD 0x0ede TOD +vendor EGALAX 0x0eef eGalax, Inc. +vendor AIRPRIME 0x0f3d AirPrime, Inc. +vendor MICROTUNE 0x0f4d Microtune +vendor VTECH 0x0f88 VTech +vendor FALCOM 0x0f94 Falcom Wireless Communications GmbH +vendor RIM 0x0fca Research In Motion +vendor DYNASTREAM 0x0fcf Dynastream Innovations +vendor KONTRON 0x0fe6 Kontron AG +vendor QUALCOMM 0x1004 Qualcomm +vendor APACER 0x1005 Apacer +vendor MOTOROLA4 0x100d Motorola +vendor AIRPLUS 0x1011 Airplus +vendor DESKNOTE 0x1019 Desknote +vendor GIGABYTE 0x1044 GIGABYTE +vendor WESTERN 0x1058 Western Digital +vendor MOTOROLA 0x1063 Motorola +vendor CCYU 0x1065 CCYU Technology +vendor CURITEL 0x106c Curitel Communications Inc +vendor SILABS2 0x10a6 SILABS2 +vendor USI 0x10ab USI +vendor PLX 0x10b5 PLX +vendor ASANTE 0x10bd Asante +vendor SILABS 0x10c4 Silicon Labs +vendor SILABS3 0x10c5 Silicon Labs +vendor SILABS4 0x10ce Silicon Labs +vendor ACTIONS 0x10d6 Actions +vendor ANALOG 0x1110 Analog Devices +vendor TENX 0x1130 Ten X Technology, Inc. +vendor ISSC 0x1131 Integrated System Solution Corp. +vendor JRC 0x1145 Japan Radio Company +vendor SPHAIRON 0x114b Sphairon Access Systems GmbH +vendor DELORME 0x1163 DeLorme +vendor SERVERWORKS 0x1166 ServerWorks +vendor DLINK3 0x1186 Dlink +vendor ACERCM 0x1189 Acer Communications & Multimedia +vendor SIERRA 0x1199 Sierra Wireless +vendor SANWA 0x11ad Sanwa Electric Instrument Co., Ltd. +vendor TOPFIELD 0x11db Topfield Co., Ltd +vendor SIEMENS3 0x11f5 Siemens +vendor NETINDEX 0x11f6 NetIndex +vendor ALCATEL 0x11f7 Alcatel +vendor UNKNOWN3 0x1233 Unknown vendor +vendor TSUNAMI 0x1241 Tsunami +vendor PHEENET 0x124a Pheenet +vendor TARGUS 0x1267 Targus +vendor TWINMOS 0x126f TwinMOS +vendor TENDA 0x1286 Tenda +vendor CREATIVE2 0x1292 Creative Labs +vendor BELKIN2 0x1293 Belkin Components +vendor CYBERTAN 0x129b CyberTAN Technology +vendor HUAWEI 0x12d1 Huawei Technologies +vendor ARANEUS 0x12d8 Araneus Information Systems +vendor TAPWAVE 0x12ef Tapwave +vendor AINCOMM 0x12fd Aincomm +vendor MOBILITY 0x1342 Mobility +vendor DICKSMITH 0x1371 Dick Smith Electronics +vendor NETGEAR3 0x1385 Netgear +vendor BALTECH 0x13ad Baltech +vendor CISCOLINKSYS 0x13b1 Cisco-Linksys +vendor SHARK 0x13d2 Shark +vendor AZUREWAVE 0x13d3 AsureWave +vendor INITIO 0x13fd Initio Corporation +vendor EMTEC 0x13fe Emtec +vendor NOVATEL 0x1410 Novatel Wireless +vendor MERLIN 0x1416 Merlin +vendor WISTRONNEWEB 0x1435 Wistron NeWeb +vendor RADIOSHACK 0x1453 Radio Shack +vendor HUAWEI3COM 0x1472 Huawei-3Com +vendor ABOCOM2 0x1482 AboCom Systems +vendor SILICOM 0x1485 Silicom +vendor RALINK 0x148f Ralink Technology +vendor IMAGINATION 0x149a Imagination Technologies +vendor CONCEPTRONIC2 0x14b2 Conceptronic +vendor SUPERTOP 0x14cd Super Top +vendor PLANEX3 0x14ea Planex Communications +vendor SILICONPORTALS 0x1527 Silicon Portals +vendor UBIQUAM 0x1529 UBIQUAM Co., Ltd. +vendor JMICRON 0x152d JMicron +vendor UBLOX 0x1546 U-blox +vendor PNY 0x154b PNY +vendor OWEN 0x1555 Owen +vendor OQO 0x1557 OQO +vendor UMEDIA 0x157e U-MEDIA Communications +vendor FIBERLINE 0x1582 Fiberline +vendor SPARKLAN 0x15a9 SparkLAN +vendor SOUNDGRAPH 0x15c2 Soundgraph, Inc. +vendor AMIT2 0x15c5 AMIT +vendor SOHOWARE 0x15e8 SOHOware +vendor UMAX 0x1606 UMAX Data Systems +vendor INSIDEOUT 0x1608 Inside Out Networks +vendor AMOI 0x1614 Amoi Electronics +vendor GOODWAY 0x1631 Good Way Technology +vendor ENTREGA 0x1645 Entrega +vendor ACTIONTEC 0x1668 Actiontec Electronics +vendor CLIPSAL 0x166a Clipsal +vendor CISCOLINKSYS2 0x167b Cisco-Linksys +vendor ATHEROS 0x168c Atheros Communications +vendor GIGASET 0x1690 Gigaset +vendor GLOBALSUN 0x16ab Global Sun Technology +vendor ANYDATA 0x16d5 AnyDATA Corporation +vendor JABLOTRON 0x16d6 Jablotron +vendor CMOTECH 0x16d8 C-motech +vendor WIENERPLEINBAUS 0x16dc WIENER Plein & Baus GmbH. +vendor AXESSTEL 0x1726 Axesstel Co., Ltd. +vendor LINKSYS4 0x1737 Linksys +vendor SENAO 0x1740 Senao +vendor ASUS2 0x1761 ASUS +vendor SWEEX2 0x177f Sweex +vendor METAGEEK 0x1781 MetaGeek +vendor WAVESENSE 0x17f4 WaveSense +vendor VAISALA 0x1843 Vaisala +vendor AMIT 0x18c5 AMIT +vendor GOOGLE 0x18d1 Google +vendor QCOM 0x18e8 Qcom +vendor ELV 0x18ef ELV +vendor LINKSYS3 0x1915 Linksys +vendor QUALCOMMINC 0x19d2 Qualcomm, Incorporated +vendor WCH2 0x1a86 QinHeng Electronics +vendor STELERA 0x1a8d Stelera Wireless +vendor MATRIXORBITAL 0x1b3d Matrix Orbital +vendor OVISLINK 0x1b75 OvisLink +vendor TCTMOBILE 0x1bbb TCT Mobile +vendor WAGO 0x1be3 WAGO Kontakttechnik GmbH. +vendor TELIT 0x1bc7 Telit +vendor LONGCHEER 0x1c9e Longcheer Holdings, Ltd. +vendor MPMAN 0x1cae MpMan +vendor DRESDENELEKTRONIK 0x1cf1 dresden elektronik +vendor NEOTEL 0x1d09 Neotel +vendor PEGATRON 0x1d4d Pegatron +vendor QISDA 0x1da5 Qisda +vendor METAGEEK2 0x1dd5 MetaGeek +vendor ALINK 0x1e0e Alink +vendor AIRTIES 0x1eda AirTies +vendor VERTEX 0x1fe7 Vertex Wireless Co., Ltd. +vendor DLINK 0x2001 D-Link +vendor PLANEX2 0x2019 Planex Communications +vendor HAUPPAUGE2 0x2040 Hauppauge Computer Works +vendor TLAYTECH 0x20b9 Tlay Tech +vendor ENCORE 0x203d Encore +vendor PARA 0x20b8 PARA Industrial +vendor SIMTEC 0x20df Simtec Electronics +vendor ERICSSON 0x2282 Ericsson +vendor MOTOROLA2 0x22b8 Motorola +vendor TRIPPLITE 0x2478 Tripp-Lite +vendor HIROSE 0x2631 Hirose Electric +vendor NHJ 0x2770 NHJ +vendor PLANEX 0x2c02 Planex Communications +vendor VIDZMEDIA 0x3275 VidzMedia Pte Ltd +vendor AEI 0x3334 AEI +vendor HANK 0x3353 Hank Connection +vendor PQI 0x3538 PQI +vendor DAISY 0x3579 Daisy Technology +vendor NI 0x3923 National Instruments +vendor MICRONET 0x3980 Micronet Communications +vendor IODATA2 0x40bb I-O Data +vendor IRIVER 0x4102 iRiver +vendor DELL 0x413c Dell +vendor WCH 0x4348 QinHeng Electronics +vendor ACEECA 0x4766 Aceeca +vendor AVERATEC 0x50c2 Averatec +vendor SWEEX 0x5173 Sweex +vendor PROLIFIC2 0x5372 Prolific Technologies +vendor ONSPEC2 0x55aa OnSpec Electronic Inc. +vendor ZINWELL 0x5a57 Zinwell +vendor SITECOM 0x6189 Sitecom +vendor ARKMICRO 0x6547 Arkmicro Technologies Inc. +vendor 3COM2 0x6891 3Com +vendor EDIMAX 0x7392 Edimax +vendor INTEL 0x8086 Intel +vendor INTEL2 0x8087 Intel +vendor ALLWIN 0x8516 ALLWIN Tech +vendor SITECOM2 0x9016 Sitecom +vendor MOSCHIP 0x9710 MosChip Semiconductor +vendor MARVELL 0x9e88 Marvell Technology Group Ltd. +vendor 3COM3 0xa727 3Com +vendor DATAAPEX 0xdaae DataApex +vendor HP2 0xf003 Hewlett Packard +vendor USRP 0xfffe GNU Radio USRP + +/* + * List of known products. Grouped by vendor. + */ + +/* 3Com products */ +product 3COM HOMECONN 0x009d HomeConnect Camera +product 3COM 3CREB96 0x00a0 Bluetooth USB Adapter +product 3COM 3C19250 0x03e8 3C19250 Ethernet Adapter +product 3COM 3CRSHEW696 0x0a01 3CRSHEW696 Wireless Adapter +product 3COM 3C460 0x11f8 HomeConnect 3C460 +product 3COM USR56K 0x3021 U.S.Robotics 56000 Voice FaxModem Pro +product 3COM 3C460B 0x4601 HomeConnect 3C460B +product 3COM2 3CRUSB10075 0xa727 3CRUSB10075 +product 3COM3 AR5523_1 0x6893 AR5523 +product 3COM3 AR5523_2 0x6895 AR5523 +product 3COM3 AR5523_3 0x6897 AR5523 + +product 3COMUSR OFFICECONN 0x0082 3Com OfficeConnect Analog Modem +product 3COMUSR USRISDN 0x008f 3Com U.S. Robotics Pro ISDN TA +product 3COMUSR HOMECONN 0x009d 3Com HomeConnect Camera +product 3COMUSR USR56K 0x3021 U.S. Robotics 56000 Voice FaxModem Pro + +/* AboCom products */ +product ABOCOM XX1 0x110c XX1 +product ABOCOM XX2 0x200c XX2 +product ABOCOM RT2770 0x2770 RT2770 +product ABOCOM RT2870 0x2870 RT2870 +product ABOCOM RT3070 0x3070 RT3070 +product ABOCOM RT3071 0x3071 RT3071 +product ABOCOM RT3072 0x3072 RT3072 +product ABOCOM2 RT2870_1 0x3c09 RT2870 +product ABOCOM URE450 0x4000 URE450 Ethernet Adapter +product ABOCOM UFE1000 0x4002 UFE1000 Fast Ethernet Adapter +product ABOCOM DSB650TX_PNA 0x4003 1/10/100 Ethernet Adapter +product ABOCOM XX4 0x4004 XX4 +product ABOCOM XX5 0x4007 XX5 +product ABOCOM XX6 0x400b XX6 +product ABOCOM XX7 0x400c XX7 +product ABOCOM RTL8151 0x401a RTL8151 +product ABOCOM XX8 0x4102 XX8 +product ABOCOM XX9 0x4104 XX9 +product ABOCOM UF200 0x420a UF200 Ethernet +product ABOCOM WL54 0x6001 WL54 +product ABOCOM XX10 0xabc1 XX10 +product ABOCOM BWU613 0xb000 BWU613 +product ABOCOM HWU54DM 0xb21b HWU54DM +product ABOCOM RT2573_2 0xb21c RT2573 +product ABOCOM RT2573_3 0xb21d RT2573 +product ABOCOM RT2573_4 0xb21e RT2573 +product ABOCOM WUG2700 0xb21f WUG2700 + +/* Accton products */ +product ACCTON USB320_EC 0x1046 USB320-EC Ethernet Adapter +product ACCTON 2664W 0x3501 2664W +product ACCTON 111 0x3503 T-Sinus 111 Wireless Adapter +product ACCTON SMCWUSBG_NF 0x4505 SMCWUSB-G (no firmware) +product ACCTON SMCWUSBG 0x4506 SMCWUSB-G +product ACCTON SMCWUSBTG2_NF 0x4507 SMCWUSBT-G2 (no firmware) +product ACCTON SMCWUSBTG2 0x4508 SMCWUSBT-G2 +product ACCTON PRISM_GT 0x4521 PrismGT USB 2.0 WLAN +product ACCTON SS1001 0x5046 SpeedStream Ethernet Adapter +product ACCTON RT2870_2 0x6618 RT2870 +product ACCTON RT3070 0x7511 RT3070 +product ACCTON RT2770 0x7512 RT2770 +product ACCTON RT2870_3 0x7522 RT2870 +product ACCTON RT2870_5 0x8522 RT2870 +product ACCTON RT3070_4 0xa512 RT3070 +product ACCTON RT2870_4 0xa618 RT2870 +product ACCTON RT3070_1 0xa701 RT3070 +product ACCTON RT3070_2 0xa702 RT3070 +product ACCTON RT2870_1 0xb522 RT2870 +product ACCTON RT3070_3 0xc522 RT3070 +product ACCTON RT3070_5 0xd522 RT3070 +product ACCTON ZD1211B 0xe501 ZD1211B + +/* Aceeca products */ +product ACEECA MEZ1000 0x0001 MEZ1000 RDA + +/* Acer Communications & Multimedia (oemd by Surecom) */ +product ACERCM EP1427X2 0x0893 EP-1427X-2 Ethernet Adapter + +/* Acer Labs products */ +product ACERLABS M5632 0x5632 USB 2.0 Data Link + +/* Acer Peripherals, Inc. products */ +product ACERP ACERSCAN_C310U 0x12a6 Acerscan C310U +product ACERP ACERSCAN_320U 0x2022 Acerscan 320U +product ACERP ACERSCAN_640U 0x2040 Acerscan 640U +product ACERP ACERSCAN_620U 0x2060 Acerscan 620U +product ACERP ACERSCAN_4300U 0x20b0 Benq 3300U/4300U +product ACERP ACERSCAN_640BT 0x20be Acerscan 640BT +product ACERP ACERSCAN_1240U 0x20c0 Acerscan 1240U +product ACERP S81 0x4027 BenQ S81 phone +product ACERP H10 0x4068 AWL400 Wireless Adapter +product ACERP ATAPI 0x6003 ATA/ATAPI Adapter +product ACERP AWL300 0x9000 AWL300 Wireless Adapter +product ACERP AWL400 0x9001 AWL400 Wireless Adapter + +/* Acer Warp products */ +product ACERW WARPLINK 0x0204 Warplink + +/* Actions products */ +product ACTIONS MP4 0x1101 Actions MP4 Player + +/* Actiontec, Inc. products */ +product ACTIONTEC PRISM_25 0x0408 Prism2.5 Wireless Adapter +product ACTIONTEC PRISM_25A 0x0421 Prism2.5 Wireless Adapter A +product ACTIONTEC FREELAN 0x6106 ROPEX FreeLan 802.11b +product ACTIONTEC UAT1 0x7605 UAT1 Wireless Ethernet Adapter + +/* ACTiSYS products */ +product ACTISYS IR2000U 0x0011 ACT-IR2000U FIR + +/* ActiveWire, Inc. products */ +product ACTIVEWIRE IOBOARD 0x0100 I/O Board +product ACTIVEWIRE IOBOARD_FW1 0x0101 I/O Board, rev. 1 firmware + +/* Adaptec products */ +product ADAPTEC AWN8020 0x0020 AWN-8020 WLAN + +/* Addtron products */ +product ADDTRON AWU120 0xff31 AWU-120 + +/* ADLINK Texhnology products */ +product ADLINK ND6530 0x6530 ND-6530 USB-Serial + +/* ADMtek products */ +product ADMTEK PEGASUSII_4 0x07c2 AN986A Ethernet +product ADMTEK PEGASUS 0x0986 AN986 Ethernet +product ADMTEK PEGASUSII 0x8511 AN8511 Ethernet +product ADMTEK PEGASUSII_2 0x8513 AN8513 Ethernet +product ADMTEK PEGASUSII_3 0x8515 AN8515 Ethernet + +/* ADDON products */ +/* PNY OEMs these */ +product ADDON ATTACHE 0x1300 USB 2.0 Flash Drive +product ADDON ATTACHE 0x1300 USB 2.0 Flash Drive +product ADDON A256MB 0x1400 Attache 256MB USB 2.0 Flash Drive +product ADDON DISKPRO512 0x1420 USB 2.0 Flash Drive (DANE-ELEC zMate 512MB USB flash drive) + +/* Addonics products */ +product ADDONICS2 CABLE_205 0xa001 Cable 205 + +/* ADS products */ +product ADS UBS10BT 0x0008 UBS-10BT Ethernet +product ADS UBS10BTX 0x0009 UBS-10BT Ethernet + +/* AEI products */ +product AEI FASTETHERNET 0x1701 Fast Ethernet + +/* Agate Technologies products */ +product AGATE QDRIVE 0x0378 Q-Drive + +/* AGFA products */ +product AGFA SNAPSCAN1212U 0x0001 SnapScan 1212U +product AGFA SNAPSCAN1236U 0x0002 SnapScan 1236U +product AGFA SNAPSCANTOUCH 0x0100 SnapScan Touch +product AGFA SNAPSCAN1212U2 0x2061 SnapScan 1212U +product AGFA SNAPSCANE40 0x208d SnapScan e40 +product AGFA SNAPSCANE50 0x208f SnapScan e50 +product AGFA SNAPSCANE20 0x2091 SnapScan e20 +product AGFA SNAPSCANE25 0x2095 SnapScan e25 +product AGFA SNAPSCANE26 0x2097 SnapScan e26 +product AGFA SNAPSCANE52 0x20fd SnapScan e52 + +/* Ain Communication Technology products */ +product AINCOMM AWU2000B 0x1001 AWU2000B Wireless Adapter + +/* AIPTEK products */ +product AIPTEK POCKETCAM3M 0x2011 PocketCAM 3Mega +product AIPTEK2 PENCAM_MEGA_1_3 0x504a PenCam Mega 1.3 +product AIPTEK2 SUNPLUS_TECH 0x0c15 Sunplus Technology Inc. + +/* AirPlis products */ +product AIRPLUS MCD650 0x3198 MCD650 modem + +/* AirPrime products */ +product AIRPRIME PC5220 0x0112 CDMA Wireless PC Card +product AIRPRIME USB308 0x68A3 USB308 HSPA+ USB Modem + +/* AirTies products */ +product AIRTIES RT3070 0x2310 RT3070 + +/* AKS products */ +product AKS USBHASP 0x0001 USB-HASP 0.06 + +/* Alcatel products */ +product ALCATEL OT535 0x02df One Touch 535/735 + +/* Alcor Micro, Inc. products */ +product ALCOR2 KBD_HUB 0x2802 Kbd Hub + +product ALCOR DUMMY 0x0000 Dummy product +product ALCOR SDCR_6335 0x6335 SD/MMC Card Reader +product ALCOR SDCR_6362 0x6362 SD/MMC Card Reader +product ALCOR SDCR_6366 0x6366 SD/MMC Card Reader +product ALCOR TRANSCEND 0x6387 Transcend JetFlash Drive +product ALCOR MA_KBD_HUB 0x9213 MacAlly Kbd Hub +product ALCOR AU9814 0x9215 AU9814 Hub +product ALCOR UMCR_9361 0x9361 USB Multimedia Card Reader +product ALCOR SM_KBD 0x9410 MicroConnectors/StrongMan Keyboard +product ALCOR NEC_KBD_HUB 0x9472 NEC Kbd Hub +product ALCOR AU9720 0x9720 USB2 - RS-232 +product ALCOR AU6390 0x6390 AU6390 USB-IDE converter + +/* Alink products */ +product ALINK DWM652U5 0xce16 DWM-652 +product ALINK 3G 0x9000 3G modem +product ALINK 3GU 0x9200 3G modem + +/* Altec Lansing products */ +product ALTEC ADA70 0x0070 ADA70 Speakers +product ALTEC ASC495 0xff05 ASC495 Speakers + +/* Allied Telesyn International products */ +product ALLIEDTELESYN ATUSB100 0xb100 AT-USB100 + +/* ALLWIN Tech products */ +product ALLWIN RT2070 0x2070 RT2070 +product ALLWIN RT2770 0x2770 RT2770 +product ALLWIN RT2870 0x2870 RT2870 +product ALLWIN RT3070 0x3070 RT3070 +product ALLWIN RT3071 0x3071 RT3071 +product ALLWIN RT3072 0x3072 RT3072 +product ALLWIN RT3572 0x3572 RT3572 + +/* AlphaSmart, Inc. products */ +product ALPHASMART DANA_KB 0xdbac AlphaSmart Dana Keyboard +product ALPHASMART DANA_SYNC 0xdf00 AlphaSmart Dana HotSync + +/* Amoi products */ +product AMOI H01 0x0800 H01 3G modem +product AMOI H01A 0x7002 H01A 3G modem +product AMOI H02 0x0802 H02 3G modem + +/* American Power Conversion products */ +product APC UPS 0x0002 Uninterruptible Power Supply + +/* Ambit Microsystems products */ +product AMBIT WLAN 0x0302 WLAN +product AMBIT NTL_250 0x6098 NTL 250 cable modem + +/* Apacer products */ +product APACER HT202 0xb113 USB 2.0 Flash Drive + +/* American Power Conversion products */ +product APC UPS 0x0002 Uninterruptible Power Supply + +/* Amigo Technology products */ +product AMIGO RT2870_1 0x9031 RT2870 +product AMIGO RT2870_2 0x9041 RT2870 + +/* AMIT products */ +product AMIT CGWLUSB2GO 0x0002 CG-WLUSB2GO +product AMIT CGWLUSB2GNR 0x0008 CG-WLUSB2GNR +product AMIT RT2870_1 0x0012 RT2870 + +/* AMIT(2) products */ +product AMIT2 RT2870 0x0008 RT2870 + +/* Anchor products */ +product ANCHOR SERIAL 0x2008 Serial +product ANCHOR EZUSB 0x2131 EZUSB +product ANCHOR EZLINK 0x2720 EZLINK + +/* AnyData products */ +product ANYDATA ADU_620UW 0x6202 CDMA 2000 EV-DO USB Modem +product ANYDATA ADU_E100X 0x6501 CDMA 2000 1xRTT/EV-DO USB Modem +product ANYDATA ADU_500A 0x6502 CDMA 2000 EV-DO USB Modem + +/* AOX, Inc. products */ +product AOX USB101 0x0008 Ethernet + +/* American Power Conversion products */ +product APC UPS 0x0002 Uninterruptible Power Supply + +/* Apple Computer products */ +product APPLE IMAC_KBD 0x0201 USB iMac Keyboard +product APPLE KBD 0x0202 USB Keyboard M2452 +product APPLE EXT_KBD 0x020c Apple Extended USB Keyboard +product APPLE KBD_TP_ANSI 0x0223 Apple Internal Keyboard/Trackpad (Wellspring/ANSI) +product APPLE KBD_TP_ISO 0x0224 Apple Internal Keyboard/Trackpad (Wellspring/ISO) +product APPLE KBD_TP_JIS 0x0225 Apple Internal Keyboard/Trackpad (Wellspring/JIS) +product APPLE KBD_TP_ANSI2 0x0230 Apple Internal Keyboard/Trackpad (Wellspring2/ANSI) +product APPLE KBD_TP_ISO2 0x0231 Apple Internal Keyboard/Trackpad (Wellspring2/ISO) +product APPLE KBD_TP_JIS2 0x0232 Apple Internal Keyboard/Trackpad (Wellspring2/JIS) +product APPLE MOUSE 0x0301 Mouse M4848 +product APPLE OPTMOUSE 0x0302 Optical mouse +product APPLE MIGHTYMOUSE 0x0304 Mighty Mouse +product APPLE KBD_HUB 0x1001 Hub in Apple USB Keyboard +product APPLE EXT_KBD_HUB 0x1003 Hub in Apple Extended USB Keyboard +product APPLE SPEAKERS 0x1101 Speakers +product APPLE IPOD 0x1201 iPod +product APPLE IPOD2G 0x1202 iPod 2G +product APPLE IPOD3G 0x1203 iPod 3G +product APPLE IPOD_04 0x1204 iPod '04' +product APPLE IPODMINI 0x1205 iPod Mini +product APPLE IPOD_06 0x1206 iPod '06' +product APPLE IPOD_07 0x1207 iPod '07' +product APPLE IPOD_08 0x1208 iPod '08' +product APPLE IPODVIDEO 0x1209 iPod Video +product APPLE IPODNANO 0x120a iPod Nano +product APPLE IPHONE 0x1290 iPhone +product APPLE IPOD_TOUCH 0x1291 iPod Touch +product APPLE IPHONE_3G 0x1292 iPhone 3G +product APPLE IPHONE_3GS 0x1294 iPhone 3GS +product APPLE IPHONE_4 0x1297 iPhone 4 +product APPLE IPAD 0x129a iPad +product APPLE ETHERNET 0x1402 Ethernet A1277 + +/* Arkmicro Technologies */ +product ARKMICRO ARK3116 0x0232 ARK3116 Serial + +/* Asahi Optical products */ +product ASAHIOPTICAL OPTIO230 0x0004 Digital camera +product ASAHIOPTICAL OPTIO330 0x0006 Digital camera + +/* Asante products */ +product ASANTE EA 0x1427 Ethernet + +/* ASIX Electronics products */ +product ASIX AX88172 0x1720 10/100 Ethernet +product ASIX AX88178 0x1780 AX88178 +product ASIX AX88772 0x7720 AX88772 +product ASIX AX88772A 0x772a AX88772A USB 2.0 10/100 Ethernet +product ASIX AX88772B 0x772b AX88772B USB 2.0 10/100 Ethernet +product ASIX AX88772B_1 0x7e2b AX88772B USB 2.0 10/100 Ethernet + +/* ASUS products */ +product ASUS2 USBN11 0x0b05 USB-N11 +product ASUS WL167G 0x1707 WL-167g Wireless Adapter +product ASUS WL159G 0x170c WL-159g +product ASUS A9T_WIFI 0x171b A9T wireless +product ASUS P5B_WIFI 0x171d P5B wireless +product ASUS RT2573_1 0x1723 RT2573 +product ASUS RT2573_2 0x1724 RT2573 +product ASUS LCM 0x1726 LCM display +product ASUS RT2870_1 0x1731 RT2870 +product ASUS RT2870_2 0x1732 RT2870 +product ASUS RT2870_3 0x1742 RT2870 +product ASUS RT2870_4 0x1760 RT2870 +product ASUS RT2870_5 0x1761 RT2870 +product ASUS USBN13 0x1784 USB-N13 +product ASUS RT3070_1 0x1790 RT3070 +product ASUS A730W 0x4202 ASUS MyPal A730W +product ASUS P535 0x420f ASUS P535 PDA +product ASUS GMSC 0x422f ASUS Generic Mass Storage +product ASUS RT2570 0x1706 RT2500USB Wireless Adapter + +/* ATen products */ +product ATEN UC1284 0x2001 Parallel printer +product ATEN UC10T 0x2002 10Mbps Ethernet +product ATEN UC110T 0x2007 UC-110T Ethernet +product ATEN UC232A 0x2008 Serial +product ATEN UC210T 0x2009 UC-210T Ethernet +product ATEN DSB650C 0x4000 DSB-650C + +/* Atheros Communications products */ +product ATHEROS AR5523 0x0001 AR5523 +product ATHEROS AR5523_NF 0x0002 AR5523 (no firmware) +product ATHEROS2 AR5523_1 0x0001 AR5523 +product ATHEROS2 AR5523_1_NF 0x0002 AR5523 (no firmware) +product ATHEROS2 AR5523_2 0x0003 AR5523 +product ATHEROS2 AR5523_2_NF 0x0004 AR5523 (no firmware) +product ATHEROS2 AR5523_3 0x0005 AR5523 +product ATHEROS2 AR5523_3_NF 0x0006 AR5523 (no firmware) + +/* Atmel Comp. products */ +product ATMEL STK541 0x2109 Zigbee Controller +product ATMEL UHB124 0x3301 UHB124 hub +product ATMEL DWL120 0x7603 DWL-120 Wireless Adapter +product ATMEL BW002 0x7605 BW002 Wireless Adapter +product ATMEL WL1130USB 0x7613 WL-1130 USB +product ATMEL AT76C505A 0x7614 AT76c505a Wireless Adapter + +/* AuthenTec products */ +product AUTHENTEC AES1610 0x1600 AES1610 Fingerprint Sensor + +/* Avision products */ +product AVISION 1200U 0x0268 1200U scanner + +/* Axesstel products */ +product AXESSTEL DATAMODEM 0x1000 Data Modem + +/* AsureWave products */ +product AZUREWAVE RT2870_1 0x3247 RT2870 +product AZUREWAVE RT2870_2 0x3262 RT2870 +product AZUREWAVE RT3070_1 0x3273 RT3070 +product AZUREWAVE RT3070_2 0x3284 RT3070 +product AZUREWAVE RT3070_3 0x3305 RT3070 + +/* Baltech products */ +product BALTECH CARDREADER 0x9999 Card reader + +/* B&B Electronics products */ +product BBELECTRONICS USOTL4 0xAC01 RS-422/485 + +/* Belkin products */ +/*product BELKIN F5U111 0x???? F5U111 Ethernet*/ +product BELKIN F5D6050 0x0050 F5D6050 802.11b Wireless Adapter +product BELKIN FBT001V 0x0081 FBT001v2 Bluetooth +product BELKIN FBT003V 0x0084 FBT003v2 Bluetooth +product BELKIN F5U103 0x0103 F5U103 Serial +product BELKIN F5U109 0x0109 F5U109 Serial +product BELKIN USB2SCSI 0x0115 USB to SCSI +product BELKIN F8T012 0x0121 F8T012xx1 Bluetooth USB Adapter +product BELKIN USB2LAN 0x0121 USB to LAN +product BELKIN F5U208 0x0208 F5U208 VideoBus II +product BELKIN F5U237 0x0237 F5U237 USB 2.0 7-Port Hub +product BELKIN F5U257 0x0257 F5U257 Serial +product BELKIN F5U409 0x0409 F5U409 Serial +product BELKIN F6C550AVR 0x0551 F6C550-AVR UPS +product BELKIN F5U120 0x1203 F5U120-PC Hub +product BELKIN ZD1211B 0x4050 ZD1211B +product BELKIN F5D5055 0x5055 F5D5055 +product BELKIN F5D7050 0x7050 F5D7050 Wireless Adapter +product BELKIN F5D7051 0x7051 F5D7051 54g USB Network Adapter +product BELKIN F5D7050A 0x705a F5D7050A Wireless Adapter +/* Also sold as 'Ativa 802.11g wireless card' */ +product BELKIN F5D7050_V4000 0x705c F5D7050 v4000 Wireless Adapter +product BELKIN F5D7050E 0x705e F5D7050E Wireless Adapter +product BELKIN RT2870_1 0x8053 RT2870 +product BELKIN RT2870_2 0x805c RT2870 +product BELKIN F5D8053V3 0x815c F5D8053 v3 +product BELKIN F5D8055 0x825a F5D8055 +product BELKIN F5D8055V2 0x825b F5D8055 v2 +product BELKIN F5D9050V3 0x905b F5D9050 ver 3 Wireless Adapter +product BELKIN2 F5U002 0x0002 F5U002 Parallel printer +product BELKIN F6D4050V1 0x935a F6D4050 v1 + +/* Billionton products */ +product BILLIONTON USB100 0x0986 USB100N 10/100 FastEthernet +product BILLIONTON USBLP100 0x0987 USB100LP +product BILLIONTON USBEL100 0x0988 USB100EL +product BILLIONTON USBE100 0x8511 USBE100 +product BILLIONTON USB2AR 0x90ff USB2AR Ethernet + +/* Broadcom products */ +product BROADCOM BCM2033 0x2033 BCM2033 Bluetooth USB dongle + +/* Brother Industries products */ +product BROTHER HL1050 0x0002 HL-1050 laser printer +product BROTHER MFC8600_9650 0x0100 MFC8600/9650 multifunction device + +/* Behavior Technology Computer products */ +product BTC BTC6100 0x5550 6100C Keyboard +product BTC BTC7932 0x6782 Keyboard with mouse port + +/* Canon, Inc. products */ +product CANON N656U 0x2206 CanoScan N656U +product CANON N1220U 0x2207 CanoScan N1220U +product CANON D660U 0x2208 CanoScan D660U +product CANON N676U 0x220d CanoScan N676U +product CANON N1240U 0x220e CanoScan N1240U +product CANON LIDE25 0x2220 CanoScan LIDE 25 +product CANON S10 0x3041 PowerShot S10 +product CANON S100 0x3045 PowerShot S100 +product CANON S200 0x3065 PowerShot S200 +product CANON REBELXT 0x30ef Digital Rebel XT + +/* CATC products */ +product CATC NETMATE 0x000a Netmate Ethernet +product CATC NETMATE2 0x000c Netmate2 Ethernet +product CATC CHIEF 0x000d USB Chief Bus & Protocol Analyzer +product CATC ANDROMEDA 0x1237 Andromeda hub + +/* CASIO products */ +product CASIO QV_DIGICAM 0x1001 QV DigiCam +product CASIO EXS880 0x1105 Exilim EX-S880 +product CASIO BE300 0x2002 BE-300 PDA +product CASIO NAMELAND 0x4001 CASIO Nameland EZ-USB + +/* CCYU products */ +product CCYU ED1064 0x2136 EasyDisk ED1064 + +/* Century products */ +product CENTURY EX35QUAT 0x011e Century USB Disk Enclosure +product CENTURY EX35SW4_SB4 0x011f Century USB Disk Enclosure + +/* Cherry products */ +product CHERRY MY3000KBD 0x0001 My3000 keyboard +product CHERRY MY3000HUB 0x0003 My3000 hub +product CHERRY CYBOARD 0x0004 CyBoard Keyboard + +/* Chic Technology products */ +product CHIC MOUSE1 0x0001 mouse +product CHIC CYPRESS 0x0003 Cypress USB Mouse + +/* Chicony products */ +product CHICONY KB8933 0x0001 KB-8933 keyboard +product CHICONY KU0325 0x0116 KU-0325 keyboard +product CHICONY CNF7129 0xb071 Notebook Web Camera +product CHICONY2 TWINKLECAM 0x600d TwinkleCam USB camera + +/* CH Products */ +product CHPRODUCTS PROTHROTTLE 0x00f1 Pro Throttle +product CHPRODUCTS PROPEDALS 0x00f2 Pro Pedals +product CHPRODUCTS FIGHTERSTICK 0x00f3 Fighterstick +product CHPRODUCTS FLIGHTYOKE 0x00ff Flight Sim Yoke + +/* Cisco-Linksys products */ +product CISCOLINKSYS WUSB54AG 0x000c WUSB54AG Wireless Adapter +product CISCOLINKSYS WUSB54G 0x000d WUSB54G Wireless Adapter +product CISCOLINKSYS WUSB54GP 0x0011 WUSB54GP Wireless Adapter +product CISCOLINKSYS USB200MV2 0x0018 USB200M v2 +product CISCOLINKSYS HU200TS 0x001a HU200TS Wireless Adapter +product CISCOLINKSYS WUSB54GC 0x0020 WUSB54GC +product CISCOLINKSYS WUSB54GR 0x0023 WUSB54GR +product CISCOLINKSYS WUSBF54G 0x0024 WUSBF54G +product CISCOLINKSYS AE1000 0x002f AE1000 +product CISCOLINKSYS2 RT3070 0x4001 RT3070 +product CISCOLINKSYS3 RT3070 0x0101 RT3070 + +/* Clipsal products */ +product CLIPSAL 5500PCU 0x0303 5500PCU C-Bus + +/* CMOTECH products */ +product CMOTECH CNU510 0x5141 CDMA Technologies USB modem +product CMOTECH CNU550 0x5543 CDMA 2000 1xRTT/1xEVDO USB modem +product CMOTECH CGU628 0x6006 CGU-628 +product CMOTECH CDMA_MODEM1 0x6280 CDMA Technologies USB modem +product CMOTECH DISK 0xf000 disk mode + +/* Compaq products */ +product COMPAQ IPAQPOCKETPC 0x0003 iPAQ PocketPC +product COMPAQ PJB100 0x504a Personal Jukebox PJB100 +product COMPAQ IPAQLINUX 0x505a iPAQ Linux + +/* Composite Corp products looks the same as "TANGTOP" */ +product COMPOSITE USBPS2 0x0001 USB to PS2 Adaptor + +/* Conceptronic products */ +product CONCEPTRONIC PRISM_GT 0x3762 PrismGT USB 2.0 WLAN +product CONCEPTRONIC C11U 0x7100 C11U +product CONCEPTRONIC WL210 0x7110 WL-210 +product CONCEPTRONIC AR5523_1 0x7801 AR5523 +product CONCEPTRONIC AR5523_1_NF 0x7802 AR5523 (no firmware) +product CONCEPTRONIC AR5523_2 0x7811 AR5523 +product CONCEPTRONIC AR5523_2_NF 0x7812 AR5523 (no firmware) +product CONCEPTRONIC2 C54RU 0x3c02 C54RU WLAN +product CONCEPTRONIC2 C54RU2 0x3c22 C54RU +product CONCEPTRONIC2 RT3070_1 0x3c08 RT3070 +product CONCEPTRONIC2 RT3070_2 0x3c11 RT3070 +product CONCEPTRONIC2 VIGORN61 0x3c25 VIGORN61 +product CONCEPTRONIC2 RT2870_1 0x3c06 RT2870 +product CONCEPTRONIC2 RT2870_2 0x3c07 RT2870 +product CONCEPTRONIC2 RT2870_7 0x3c09 RT2870 +product CONCEPTRONIC2 RT2870_8 0x3c12 RT2870 +product CONCEPTRONIC2 RT2870_3 0x3c23 RT2870 +product CONCEPTRONIC2 RT2870_4 0x3c25 RT2870 +product CONCEPTRONIC2 RT2870_5 0x3c27 RT2870 +product CONCEPTRONIC2 RT2870_6 0x3c28 RT2870 + +/* Connectix products */ +product CONNECTIX QUICKCAM 0x0001 QuickCam + +/* Corega products */ +product COREGA ETHER_USB_T 0x0001 Ether USB-T +product COREGA FETHER_USB_TX 0x0004 FEther USB-TX +product COREGA WLAN_USB_USB_11 0x000c WirelessLAN USB-11 +product COREGA FETHER_USB_TXS 0x000d FEther USB-TXS +product COREGA WLANUSB 0x0012 Wireless LAN Stick-11 +product COREGA FETHER_USB2_TX 0x0017 FEther USB2-TX +product COREGA WLUSB_11_KEY 0x001a ULUSB-11 Key +product COREGA CGUSBRS232R 0x002a CG-USBRS232R +product COREGA CGWLUSB2GL 0x002d CG-WLUSB2GL +product COREGA CGWLUSB2GPX 0x002e CG-WLUSB2GPX +product COREGA RT2870_1 0x002f RT2870 +product COREGA RT2870_2 0x003c RT2870 +product COREGA RT2870_3 0x003f RT2870 +product COREGA RT3070 0x0041 RT3070 +product COREGA CGWLUSB300GNM 0x0042 CG-WLUSB300GNM + +product COREGA WLUSB_11_STICK 0x7613 WLAN USB Stick 11 +product COREGA FETHER_USB_TXC 0x9601 FEther USB-TXC + +/* Creative products */ +product CREATIVE NOMAD_II 0x1002 Nomad II MP3 player +product CREATIVE NOMAD_IIMG 0x4004 Nomad II MG +product CREATIVE NOMAD 0x4106 Nomad +product CREATIVE2 VOIP_BLASTER 0x0258 Voip Blaster +product CREATIVE3 OPTICAL_MOUSE 0x0001 Notebook Optical Mouse + +/* Cambridge Silicon Radio Ltd. products */ +product CSR BT_DONGLE 0x0001 Bluetooth USB dongle +product CSR CSRDFU 0xffff USB Bluetooth Device in DFU State + +/* Chipsbank Microelectronics Co., Ltd */ +product CHIPSBANK USBMEMSTICK 0x6025 CBM2080 Flash drive controller +product CHIPSBANK USBMEMSTICK1 0x6026 CBM1180 Flash drive controller + +/* CTX products */ +product CTX EX1300 0x9999 Ex1300 hub + +/* Curitel products */ +product CURITEL HX550C 0x1101 CDMA 2000 1xRTT USB modem (HX-550C) +product CURITEL HX57XB 0x2101 CDMA 2000 1xRTT USB modem (HX-570/575B/PR-600) +product CURITEL PC5740 0x3701 Broadband Wireless modem +product CURITEL UM150 0x3711 EVDO modem +product CURITEL UM175 0x3714 EVDO modem + +/* CyberPower products */ +product CYBERPOWER 1500CAVRLCD 0x0501 1500CAVRLCD + +/* CyberTAN Technology products */ +product CYBERTAN TG54USB 0x1666 TG54USB +product CYBERTAN RT2870 0x1828 RT2870 + +/* Cypress Semiconductor products */ +product CYPRESS MOUSE 0x0001 mouse +product CYPRESS THERMO 0x0002 thermometer +product CYPRESS WISPY1A 0x0bad MetaGeek Wi-Spy +product CYPRESS KBDHUB 0x0101 Keyboard/Hub +product CYPRESS FMRADIO 0x1002 FM Radio +product CYPRESS IKARILASER 0x121f Ikari Laser SteelSeries ApS + +product CYPRESS USBRS232 0x5500 USB-RS232 Interface +product CYPRESS SLIM_HUB 0x6560 Slim Hub +product CYPRESS XX6830XX 0x6830 PATA Storage Device +product CYPRESS SILVERSHIELD 0xfd13 Gembird Silver Shield PM + +/* Daisy Technology products */ +product DAISY DMC 0x6901 USB MultiMedia Reader + +/* Dallas Semiconductor products */ +product DALLAS J6502 0x4201 J-6502 speakers + +/* DataApex products */ +product DATAAPEX MULTICOM 0xead6 MultiCom + +/* Dell products */ +product DELL PORT 0x0058 Port Replicator +product DELL AIO926 0x5115 Photo AIO Printer 926 +product DELL BC02 0x8000 BC02 Bluetooth USB Adapter +product DELL PRISM_GT_1 0x8102 PrismGT USB 2.0 WLAN +product DELL TM350 0x8103 TrueMobile 350 Bluetooth USB Adapter +product DELL PRISM_GT_2 0x8104 PrismGT USB 2.0 WLAN +product DELL U5700 0x8114 Dell 5700 3G +product DELL U5500 0x8115 Dell 5500 3G +product DELL U5505 0x8116 Dell 5505 3G +product DELL U5700_2 0x8117 Dell 5700 3G +product DELL U5510 0x8118 Dell 5510 3G +product DELL U5700_3 0x8128 Dell 5700 3G +product DELL U5700_4 0x8129 Dell 5700 3G +product DELL U5720 0x8133 Dell 5720 3G +product DELL U5720_2 0x8134 Dell 5720 3G +product DELL U740 0x8135 Dell U740 CDMA +product DELL U5520 0x8136 Dell 5520 3G +product DELL U5520_2 0x8137 Dell 5520 3G +product DELL U5520_3 0x8138 Dell 5520 3G +product DELL U5730 0x8180 Dell 5730 3G +product DELL U5730_2 0x8181 Dell 5730 3G +product DELL U5730_3 0x8182 Dell 5730 3G +product DELL DW700 0x9500 Dell DW700 GPS + +/* Delorme Paublishing products */ +product DELORME EARTHMATE 0x0100 Earthmate GPS + +/* Desknote products */ +product DESKNOTE UCR_61S2B 0x0c55 UCR-61S2B + +/* Diamond products */ +product DIAMOND RIO500USB 0x0001 Rio 500 USB + +/* Dick Smith Electronics (really C-Net) products */ +product DICKSMITH RT2573 0x9022 RT2573 +product DICKSMITH CWD854F 0x9032 C-Net CWD-854 rev F + +/* Digi International products */ +product DIGI ACCELEPORT2 0x0002 AccelePort USB 2 +product DIGI ACCELEPORT4 0x0004 AccelePort USB 4 +product DIGI ACCELEPORT8 0x0008 AccelePort USB 8 + +/* Digianswer A/S products */ +product DIGIANSWER ZIGBEE802154 0x000a ZigBee/802.15.4 MAC + +/* D-Link products */ +/*product DLINK DSBS25 0x0100 DSB-S25 serial*/ +product DLINK DUBE100 0x1a00 10/100 Ethernet +product DLINK DSB650TX4 0x200c 10/100 Ethernet +product DLINK DWL120E 0x3200 DWL-120 rev E +product DLINK DWL122 0x3700 DWL-122 +product DLINK DWLG120 0x3701 DWL-G120 +product DLINK DWL120F 0x3702 DWL-120 rev F +product DLINK DWLAG132 0x3a00 DWL-AG132 +product DLINK DWLAG132_NF 0x3a01 DWL-AG132 (no firmware) +product DLINK DWLG132 0x3a02 DWL-G132 +product DLINK DWLG132_NF 0x3a03 DWL-G132 (no firmware) +product DLINK DWLAG122 0x3a04 DWL-AG122 +product DLINK DWLAG122_NF 0x3a05 DWL-AG122 (no firmware) +product DLINK DWLG122 0x3c00 DWL-G122 b1 Wireless Adapter +product DLINK DUBE100B1 0x3c05 DUB-E100 rev B1 +product DLINK RT2870 0x3c09 RT2870 +product DLINK RT3072 0x3c0a RT3072 +product DLINK DSB650C 0x4000 10Mbps Ethernet +product DLINK DSB650TX1 0x4001 10/100 Ethernet +product DLINK DSB650TX 0x4002 10/100 Ethernet +product DLINK DSB650TX_PNA 0x4003 1/10/100 Ethernet +product DLINK DSB650TX3 0x400b 10/100 Ethernet +product DLINK DSB650TX2 0x4102 10/100 Ethernet +product DLINK DSB650 0xabc1 10/100 Ethernet +product DLINK DUBH7 0xf103 DUB-H7 USB 2.0 7-Port Hub +product DLINK2 DWA120 0x3a0c DWA-120 +product DLINK2 DWA120_NF 0x3a0d DWA-120 (no firmware) +product DLINK2 DWLG122C1 0x3c03 DWL-G122 c1 +product DLINK2 WUA1340 0x3c04 WUA-1340 +product DLINK2 DWA111 0x3c06 DWA-111 +product DLINK2 RT2870_1 0x3c09 RT2870 +product DLINK2 DWA110 0x3c07 DWA-110 +product DLINK2 RT3072 0x3c0a RT3072 +product DLINK2 RT3072_1 0x3c0b RT3072 +product DLINK2 RT3070_1 0x3c0d RT3070 +product DLINK2 RT3070_2 0x3c0e RT3070 +product DLINK2 RT3070_3 0x3c0f RT3070 +product DLINK2 RT2870_2 0x3c11 RT2870 +product DLINK2 DWA130 0x3c13 DWA-130 +product DLINK2 RT3070_4 0x3c15 RT3070 +product DLINK2 RT3070_5 0x3c16 RT3070 +product DLINK3 DWM652 0x3e04 DWM-652 + +/* DMI products */ +product DMI CFSM_RW 0xa109 CF/SM Reader/Writer +product DMI DISK 0x2bcf Generic Disk + +/* DrayTek products */ +product DRAYTEK VIGOR550 0x0550 Vigor550 + +/* dresden elektronik products */ +product DRESDENELEKTRONIK SENSORTERMINALBOARD 0x0001 SensorTerminalBoard +product DRESDENELEKTRONIK WIRELESSHANDHELDTERMINAL 0x0004 Wireless Handheld Terminal + +/* Dynastream Innovations */ +product DYNASTREAM ANTDEVBOARD 0x1003 ANT dev board +product DYNASTREAM ANT2USB 0x1004 ANT2USB +product DYNASTREAM ANTDEVBOARD2 0x1006 ANT dev board + +/* Edimax products */ +product EDIMAX EW7318USG 0x7318 USB Wireless dongle +product EDIMAX RT2870_1 0x7711 RT2870 +product EDIMAX EW7717 0x7717 EW-7717 +product EDIMAX EW7718 0x7718 EW-7718 + +/* eGalax Products */ +product EGALAX TPANEL 0x0001 Touch Panel +product EGALAX TPANEL2 0x0002 Touch Panel +product EGALAX2 TPANEL 0x0001 Touch Panel + +/* Eicon Networks */ +product EICON DIVA852 0x4905 Diva 852 ISDN TA + +/* EIZO products */ +product EIZO HUB 0x0000 hub +product EIZO MONITOR 0x0001 monitor + +/* ELCON Systemtechnik products */ +product ELCON PLAN 0x0002 Goldpfeil P-LAN + +/* Elecom products */ +product ELECOM MOUSE29UO 0x0002 mouse 29UO +product ELECOM LDUSBTX0 0x200c LD-USB/TX +product ELECOM LDUSBTX1 0x4002 LD-USB/TX +product ELECOM LDUSBLTX 0x4005 LD-USBL/TX +product ELECOM LDUSBTX2 0x400b LD-USB/TX +product ELECOM LDUSB20 0x4010 LD-USB20 +product ELECOM UCSGT 0x5003 UC-SGT +product ELECOM UCSGT0 0x5004 UC-SGT +product ELECOM LDUSBTX3 0xabc1 LD-USB/TX + +/* Elsa products */ +product ELSA MODEM1 0x2265 ELSA Modem Board +product ELSA USB2ETHERNET 0x3000 Microlink USB2Ethernet + +/* ELV products */ +product ELV USBI2C 0xe00f USB-I2C interface + +/* EMS products */ +product EMS DUAL_SHOOTER 0x0003 PSX gun controller converter + +/* Encore products */ +product ENCORE RT3070_1 0x1480 RT3070 +product ENCORE RT3070_2 0x14a1 RT3070 +product ENCORE RT3070_3 0x14a9 RT3070 + +/* Entrega products */ +product ENTREGA 1S 0x0001 1S serial +product ENTREGA 2S 0x0002 2S serial +product ENTREGA 1S25 0x0003 1S25 serial +product ENTREGA 4S 0x0004 4S serial +product ENTREGA E45 0x0005 E45 Ethernet +product ENTREGA CENTRONICS 0x0006 Parallel Port +product ENTREGA XX1 0x0008 Ethernet +product ENTREGA 1S9 0x0093 1S9 serial +product ENTREGA EZUSB 0x8000 EZ-USB +/*product ENTREGA SERIAL 0x8001 DB25 Serial*/ +product ENTREGA 2U4S 0x8004 2U4S serial/usb hub +product ENTREGA XX2 0x8005 Ethernet +/*product ENTREGA SERIAL_DB9 0x8093 DB9 Serial*/ + +/* Epson products */ +product EPSON PRINTER1 0x0001 USB Printer +product EPSON PRINTER2 0x0002 ISD USB Smart Cable for Mac +product EPSON PRINTER3 0x0003 ISD USB Smart Cable +product EPSON PRINTER5 0x0005 USB Printer +product EPSON 636 0x0101 Perfection 636U / 636Photo scanner +product EPSON 610 0x0103 Perfection 610 scanner +product EPSON 1200 0x0104 Perfection 1200U / 1200Photo scanner +product EPSON 1600 0x0107 Expression 1600 scanner +product EPSON 1640 0x010a Perfection 1640SU scanner +product EPSON 1240 0x010b Perfection 1240U / 1240Photo scanner +product EPSON 640U 0x010c Perfection 640U scanner +product EPSON 1250 0x010f Perfection 1250U / 1250Photo scanner +product EPSON 1650 0x0110 Perfection 1650 scanner +product EPSON GT9700F 0x0112 GT-9700F scanner +product EPSON GT9300UF 0x011b GT-9300UF scanner +product EPSON 3200 0x011c Perfection 3200 scanner +product EPSON 1260 0x011d Perfection 1260 scanner +product EPSON 1660 0x011e Perfection 1660 scanner +product EPSON 1670 0x011f Perfection 1670 scanner +product EPSON 1270 0x0120 Perfection 1270 scanner +product EPSON 2480 0x0121 Perfection 2480 scanner +product EPSON 3590 0x0122 Perfection 3590 scanner +product EPSON 4990 0x012a Perfection 4990 Photo scanner +product EPSON CRESSI_EDY 0x0521 Cressi Edy diving computer +product EPSON N2ITION3 0x0522 Zeagle N2iTion3 diving computer +product EPSON STYLUS_875DC 0x0601 Stylus Photo 875DC Card Reader +product EPSON STYLUS_895 0x0602 Stylus Photo 895 Card Reader +product EPSON CX5400 0x0808 CX5400 scanner +product EPSON 3500 0x080e CX-3500/3600/3650 MFP +product EPSON RX425 0x080f Stylus Photo RX425 scanner +product EPSON DX3800 0x0818 CX3700/CX3800/DX38x0 MFP scanner +product EPSON 4800 0x0819 CX4700/CX4800/DX48x0 MFP scanner +product EPSON 4200 0x0820 CX4100/CX4200/DX4200 MFP scanner +product EPSON 5000 0x082b CX4900/CX5000/DX50x0 MFP scanner +product EPSON 6000 0x082e CX5900/CX6000/DX60x0 MFP scanner +product EPSON DX4000 0x082f DX4000 MFP scanner +product EPSON DX7400 0x0838 CX7300/CX7400/DX7400 MFP scanner +product EPSON DX8400 0x0839 CX8300/CX8400/DX8400 MFP scanner +product EPSON SX100 0x0841 SX100/NX100 MFP scanner +product EPSON NX300 0x0848 NX300 MFP scanner +product EPSON SX200 0x0849 SX200/SX205 MFP scanner +product EPSON SX400 0x084a SX400/NX400/TX400 MFP scanner + +/* e-TEK Labs products */ +product ETEK 1COM 0x8007 Serial + +/* Extended Systems products */ +product EXTENDED XTNDACCESS 0x0100 XTNDAccess IrDA + +/* Falcom products */ +product FALCOM TWIST 0x0001 USB GSM/GPRS Modem + +/* FEIYA products */ +product FEIYA DUMMY 0x0000 Dummy product +product FEIYA 5IN1 0x1132 5-in-1 Card Reader +product FEIYA AC110 0x6300 AC-110 Card Reader + +/* Fiberline */ +product FIBERLINE WL430U 0x6003 WL-430U + +/* Fossil, Inc products */ +product FOSSIL WRISTPDA 0x0002 Wrist PDA + +/* Foxconn products */ +product FOXCONN TCOM_TC_300 0xe000 T-Com TC 300 +product FOXCONN PIRELLI_DP_L10 0xe003 Pirelli DP-L10 + +/* Freecom products */ +product FREECOM DVD 0xfc01 DVD drive +product FREECOM HDD 0xfc05 Classic SL Hard Drive + +/* Fujitsu Siemens Computers products */ +product FSC E5400 0x1009 PrismGT USB 2.0 WLAN + +/* Future Technology Devices products */ +product FTDI SERIAL_8U100AX 0x8372 8U100AX Serial +product FTDI SERIAL_8U232AM 0x6001 8U232AM Serial +product FTDI SERIAL_8U232AM4 0x6004 8U232AM Serial +product FTDI SERIAL_2232C 0x6010 FT2232C Dual port Serial +product FTDI SERIAL_2232D 0x9e90 FT2232D Dual port Serial +product FTDI BEAGLEBONE 0xA6D0 BeagleBone +product FTDI SERIAL_4232H 0x6011 FT4232H Quad port Serial +product FTDI SERIAL_BEAGLEBONE 0xa6d0 BeagleBone FTDI Serial +/* Gude Analog- und Digitalsysteme products also uses FTDI's id: */ +product FTDI TACTRIX_OPENPORT_13M 0xcc48 OpenPort 1.3 Mitsubishi +product FTDI TACTRIX_OPENPORT_13S 0xcc49 OpenPort 1.3 Subaru +product FTDI TACTRIX_OPENPORT_13U 0xcc4a OpenPort 1.3 Universal +product FTDI GAMMASCOUT 0xd678 Gamma-Scout +product FTDI KBS 0xe6c8 Pyramid KBS USB LCD +product FTDI EISCOU 0xe888 Expert ISDN Control USB +product FTDI UOPTBR 0xe889 USB-RS232 OptoBridge +product FTDI EMCU2D 0xe88a Expert mouseCLOCK USB II +product FTDI PCMSFU 0xe88b Precision Clock MSF USB +product FTDI EMCU2H 0xe88c Expert mouseCLOCK USB II HBG +product FTDI MAXSTREAM 0xee18 Maxstream PKG-U +product FTDI USB_UIRT 0xf850 USB-UIRT +product FTDI USBSERIAL 0xfa00 Matrix Orbital USB Serial +product FTDI MX2_3 0xfa01 Matrix Orbital MX2 or MX3 +product FTDI MX4_5 0xfa02 Matrix Orbital MX4 or MX5 +product FTDI LK202 0xfa03 Matrix Orbital VK/LK202 Family +product FTDI LK204 0xfa04 Matrix Orbital VK/LK204 Family +product FTDI CFA_632 0xfc08 Crystalfontz CFA-632 USB LCD +product FTDI CFA_634 0xfc09 Crystalfontz CFA-634 USB LCD +product FTDI CFA_633 0xfc0b Crystalfontz CFA-633 USB LCD +product FTDI CFA_631 0xfc0c Crystalfontz CFA-631 USB LCD +product FTDI CFA_635 0xfc0d Crystalfontz CFA-635 USB LCD +product FTDI SEMC_DSS20 0xfc82 SEMC DSS-20 SyncStation +/* Commerzielle und Technische Informationssysteme GmbH products */ +product FTDI CTI_USB_NANO_485 0xf60b CTI USB-Nano 485 +product FTDI CTI_USB_MINI_485 0xf608 CTI USB-Mini 485 + +/* Fuji photo products */ +product FUJIPHOTO MASS0100 0x0100 Mass Storage + +/* Fujitsu protducts */ +product FUJITSU AH_F401U 0x105b AH-F401U Air H device + +/* Fujitsu-Siemens protducts */ +product FUJITSUSIEMENS SCR 0x0009 Fujitsu-Siemens SCR USB Reader + +/* Garmin products */ +product GARMIN IQUE_3600 0x0004 iQue 3600 + +/* Gemalto products */ +product GEMALTO PROXPU 0x5501 Prox-PU/CU + +/* General Instruments (Motorola) products */ +product GENERALINSTMNTS SB5100 0x5100 SURFboard SB5100 Cable modem + +/* Genesys Logic products */ +product GENESYS GL620USB 0x0501 GL620USB Host-Host interface +product GENESYS GL650 0x0604 GL650 HUB +product GENESYS GL606 0x0606 USB 2.0 HUB +product GENESYS GL641USB 0x0700 GL641USB CompactFlash Card Reader +product GENESYS GL641USB2IDE_2 0x0701 GL641USB USB-IDE Bridge No 2 +product GENESYS GL641USB2IDE 0x0702 GL641USB USB-IDE Bridge +product GENESYS GL641USB_2 0x0760 GL641USB 6-in-1 Card Reader + +/* GIGABYTE products */ +product GIGABYTE GN54G 0x8001 GN-54G +product GIGABYTE GNBR402W 0x8002 GN-BR402W +product GIGABYTE GNWLBM101 0x8003 GN-WLBM101 +product GIGABYTE GNWBKG 0x8007 GN-WBKG +product GIGABYTE GNWB01GS 0x8008 GN-WB01GS +product GIGABYTE GNWI05GS 0x800a GN-WI05GS + +/* Gigaset products */ +product GIGASET WLAN 0x0701 WLAN +product GIGASET SMCWUSBTG 0x0710 SMCWUSBT-G +product GIGASET SMCWUSBTG_NF 0x0711 SMCWUSBT-G (no firmware) +product GIGASET AR5523 0x0712 AR5523 +product GIGASET AR5523_NF 0x0713 AR5523 (no firmware) +product GIGASET RT2573 0x0722 RT2573 +product GIGASET RT3070_1 0x0740 RT3070 +product GIGASET RT3070_2 0x0744 RT3070 +product GIGABYTE RT2870_1 0x800b RT2870 +product GIGABYTE GNWB31N 0x800c GN-WB31N +product GIGABYTE GNWB32L 0x800d GN-WB32L + +/* Global Sun Technology product */ +product GLOBALSUN AR5523_1 0x7801 AR5523 +product GLOBALSUN AR5523_1_NF 0x7802 AR5523 (no firmware) +product GLOBALSUN AR5523_2 0x7811 AR5523 +product GLOBALSUN AR5523_2_NF 0x7812 AR5523 (no firmware) + +/* Globespan products */ +product GLOBESPAN PRISM_GT_1 0x2000 PrismGT USB 2.0 WLAN +product GLOBESPAN PRISM_GT_2 0x2002 PrismGT USB 2.0 WLAN + +/* G.Mate, Inc products */ +product GMATE YP3X00 0x1001 YP3X00 PDA + +/* GoHubs products */ +product GOHUBS GOCOM232 0x1001 GoCOM232 Serial + +/* Good Way Technology products */ +product GOODWAY GWUSB2E 0x6200 GWUSB2E +product GOODWAY RT2573 0xc019 RT2573 + +/* Google products */ +product GOOGLE NEXUSONE 0x4e11 Nexus One + +/* Gravis products */ +product GRAVIS GAMEPADPRO 0x4001 GamePad Pro + +/* GREENHOUSE products */ +product GREENHOUSE KANA21 0x0001 CF-writer with MP3 + +/* Griffin Technology */ +product GRIFFIN IMATE 0x0405 iMate, ADB Adapter + +/* Guillemot Corporation */ +product GUILLEMOT DALEADER 0xa300 DA Leader +product GUILLEMOT HWGUSB254 0xe000 HWGUSB2-54 WLAN +product GUILLEMOT HWGUSB254LB 0xe010 HWGUSB2-54-LB +product GUILLEMOT HWGUSB254V2AP 0xe020 HWGUSB2-54V2-AP +product GUILLEMOT HWNU300 0xe030 HWNU-300 + +/* Hagiwara products */ +product HAGIWARA FGSM 0x0002 FlashGate SmartMedia Card Reader +product HAGIWARA FGCF 0x0003 FlashGate CompactFlash Card Reader +product HAGIWARA FG 0x0005 FlashGate + +/* HAL Corporation products */ +product HAL IMR001 0x0011 Crossam2+USB IR commander + +/* Handspring, Inc. */ +product HANDSPRING VISOR 0x0100 Handspring Visor +product HANDSPRING TREO 0x0200 Handspring Treo +product HANDSPRING TREO600 0x0300 Handspring Treo 600 + +/* Hauppauge Computer Works */ +product HAUPPAUGE WINTV_USB_FM 0x4d12 WinTV USB FM +product HAUPPAUGE2 NOVAT500 0x9580 NovaT 500Stick + +/* Hawking Technologies products */ +product HAWKING RT2870_1 0x0001 RT2870 +product HAWKING RT2870_2 0x0003 RT2870 +product HAWKING HWUN2 0x0009 HWUN2 +product HAWKING RT3070 0x000b RT3070 +product HAWKING UF100 0x400c 10/100 USB Ethernet + +/* HID Global GmbH products */ +product HIDGLOBAL CM2020 0x0596 Omnikey Cardman 2020 +product HIDGLOBAL CM6020 0x1784 Omnikey Cardman 6020 + +/* Hitachi, Ltd. products */ +product HITACHI DVDCAM_DZ_MV100A 0x0004 DVD-CAM DZ-MV100A Camcorder +product HITACHI DVDCAM_USB 0x001e DVDCAM USB HS Interface + +/* HP products */ +product HP 895C 0x0004 DeskJet 895C +product HP 4100C 0x0101 Scanjet 4100C +product HP S20 0x0102 Photosmart S20 +product HP 880C 0x0104 DeskJet 880C +product HP 4200C 0x0105 ScanJet 4200C +product HP CDWRITERPLUS 0x0107 CD-Writer Plus +product HP KBDHUB 0x010c Multimedia Keyboard Hub +product HP G55XI 0x0111 OfficeJet G55xi +product HP HN210W 0x011c HN210W 802.11b WLAN +product HP 49GPLUS 0x0121 49g+ graphing calculator +product HP 6200C 0x0201 ScanJet 6200C +product HP S20b 0x0202 PhotoSmart S20 +product HP 815C 0x0204 DeskJet 815C +product HP 3300C 0x0205 ScanJet 3300C +product HP CDW8200 0x0207 CD-Writer Plus 8200e +product HP MMKEYB 0x020c Multimedia keyboard +product HP 1220C 0x0212 DeskJet 1220C +product HP 810C 0x0304 DeskJet 810C/812C +product HP 4300C 0x0305 Scanjet 4300C +product HP CDW4E 0x0307 CD-Writer+ CD-4e +product HP G85XI 0x0311 OfficeJet G85xi +product HP 1200 0x0317 LaserJet 1200 +product HP 5200C 0x0401 Scanjet 5200C +product HP 830C 0x0404 DeskJet 830C +product HP 3400CSE 0x0405 ScanJet 3400cse +product HP 6300C 0x0601 Scanjet 6300C +product HP 840C 0x0604 DeskJet 840c +product HP 2200C 0x0605 ScanJet 2200C +product HP 5300C 0x0701 Scanjet 5300C +product HP 4400C 0x0705 Scanjet 4400C +product HP 4470C 0x0805 Scanjet 4470C +product HP 82x0C 0x0b01 Scanjet 82x0C +product HP 2300D 0x0b17 Laserjet 2300d +product HP 970CSE 0x1004 Deskjet 970Cse +product HP 5400C 0x1005 Scanjet 5400C +product HP 2215 0x1016 iPAQ 22xx/Jornada 548 +product HP 568J 0x1116 Jornada 568 +product HP 930C 0x1204 DeskJet 930c +product HP P2000U 0x1801 Inkjet P-2000U +product HP HS2300 0x1e1d HS2300 HSDPA (aka MC8775) +product HP 640C 0x2004 DeskJet 640c +product HP 4670V 0x3005 ScanJet 4670v +product HP P1100 0x3102 Photosmart P1100 +product HP LD220 0x3524 LD220 POS Display +product HP OJ4215 0x3d11 OfficeJet 4215 +product HP HN210E 0x811c Ethernet HN210E +product HP2 C500 0x6002 PhotoSmart C500 +product HP EV2200 0x1b1d ev2200 HSDPA (aka MC5720) +product HP HS2300 0x1e1d hs2300 HSDPA (aka MC8775) + +/* HTC products */ +product HTC WINMOBILE 0x00ce HTC USB Sync +product HTC PPC6700MODEM 0x00cf PPC6700 Modem +product HTC SMARTPHONE 0x0a51 SmartPhone USB Sync +product HTC WIZARD 0x0bce HTC Wizard USB Sync +product HTC LEGENDSYNC 0x0c97 HTC Legend USB Sync +product HTC LEGEND 0x0ff9 HTC Legend +product HTC LEGENDINTERNET 0x0ffe HTC Legend Internet Sharing + +/* HUAWEI products */ +product HUAWEI MOBILE 0x1001 Huawei Mobile +product HUAWEI E220 0x1003 HSDPA modem +product HUAWEI E220BIS 0x1004 HSDPA modem +product HUAWEI E1401 0x1401 3G modem +product HUAWEI E1402 0x1402 3G modem +product HUAWEI E1403 0x1403 3G modem +product HUAWEI E1404 0x1404 3G modem +product HUAWEI E1405 0x1405 3G modem +product HUAWEI E1406 0x1406 3G modem +product HUAWEI E1407 0x1407 3G modem +product HUAWEI E1408 0x1408 3G modem +product HUAWEI E1409 0x1409 3G modem +product HUAWEI E140A 0x140a 3G modem +product HUAWEI E140B 0x140b 3G modem +product HUAWEI E180V 0x140c E180V +product HUAWEI E140D 0x140d 3G modem +product HUAWEI E140E 0x140e 3G modem +product HUAWEI E140F 0x140f 3G modem +product HUAWEI E1410 0x1410 3G modem +product HUAWEI E1411 0x1411 3G modem +product HUAWEI E1412 0x1412 3G modem +product HUAWEI E1413 0x1413 3G modem +product HUAWEI E1414 0x1414 3G modem +product HUAWEI E1415 0x1415 3G modem +product HUAWEI E1416 0x1416 3G modem +product HUAWEI E1417 0x1417 3G modem +product HUAWEI E1418 0x1418 3G modem +product HUAWEI E1419 0x1419 3G modem +product HUAWEI E141A 0x141a 3G modem +product HUAWEI E141B 0x141b 3G modem +product HUAWEI E141C 0x141c 3G modem +product HUAWEI E141D 0x141d 3G modem +product HUAWEI E141E 0x141e 3G modem +product HUAWEI E141F 0x141f 3G modem +product HUAWEI E1420 0x1420 3G modem +product HUAWEI E1421 0x1421 3G modem +product HUAWEI E1422 0x1422 3G modem +product HUAWEI E1423 0x1423 3G modem +product HUAWEI E1424 0x1424 3G modem +product HUAWEI E1425 0x1425 3G modem +product HUAWEI E1426 0x1426 3G modem +product HUAWEI E1427 0x1427 3G modem +product HUAWEI E1428 0x1428 3G modem +product HUAWEI E1429 0x1429 3G modem +product HUAWEI E142A 0x142a 3G modem +product HUAWEI E142B 0x142b 3G modem +product HUAWEI E142C 0x142c 3G modem +product HUAWEI E142D 0x142d 3G modem +product HUAWEI E142E 0x142e 3G modem +product HUAWEI E142F 0x142f 3G modem +product HUAWEI E1430 0x1430 3G modem +product HUAWEI E1431 0x1431 3G modem +product HUAWEI E1432 0x1432 3G modem +product HUAWEI E1433 0x1433 3G modem +product HUAWEI E1434 0x1434 3G modem +product HUAWEI E1435 0x1435 3G modem +product HUAWEI E1436 0x1436 3G modem +product HUAWEI E1437 0x1437 3G modem +product HUAWEI E1438 0x1438 3G modem +product HUAWEI E1439 0x1439 3G modem +product HUAWEI E143A 0x143a 3G modem +product HUAWEI E143B 0x143b 3G modem +product HUAWEI E143C 0x143c 3G modem +product HUAWEI E143D 0x143d 3G modem +product HUAWEI E143E 0x143e 3G modem +product HUAWEI E143F 0x143f 3G modem +product HUAWEI E1752 0x1446 3G modem +product HUAWEI K3765 0x1465 3G modem +product HUAWEI E1820 0x14ac E1820 HSPA+ USB Slider +product HUAWEI K3765_INIT 0x1520 K3765 Initial +product HUAWEI E173 0x1c05 3G modem +product HUAWEI E173_INIT 0x1c0b 3G modem initial + +/* HUAWEI 3com products */ +product HUAWEI3COM WUB320G 0x0009 Aolynk WUB320g + +/* IBM Corporation */ +product IBM USBCDROMDRIVE 0x4427 USB CD-ROM Drive + +/* Imagination Technologies products */ +product IMAGINATION DBX1 0x2107 DBX1 DSP core + +/* Initio Corporation products */ +product INITIO DUMMY 0x0000 Dummy product +product INITIO INIC_1610P 0x1e40 USB to SATA Bridge + +/* Inside Out Networks products */ +product INSIDEOUT EDGEPORT4 0x0001 EdgePort/4 serial ports + +/* In-System products */ +product INSYSTEM F5U002 0x0002 Parallel printer +product INSYSTEM ATAPI 0x0031 ATAPI Adapter +product INSYSTEM ISD110 0x0200 IDE Adapter ISD110 +product INSYSTEM ISD105 0x0202 IDE Adapter ISD105 +product INSYSTEM USBCABLE 0x081a USB cable +product INSYSTEM STORAGE_V2 0x5701 USB Storage Adapter V2 + +/* Intel products */ +product INTEL EASYPC_CAMERA 0x0110 Easy PC Camera +product INTEL TESTBOARD 0x9890 82930 test board +product INTEL2 IRMH 0x0020 Integrated Rate Matching Hub + +/* Intersil products */ +product INTERSIL PRISM_GT 0x1000 PrismGT USB 2.0 WLAN +product INTERSIL PRISM_2X 0x3642 Prism2.x or Atmel WLAN + +/* Interpid Control Systems products */ +product INTREPIDCS VALUECAN 0x0601 ValueCAN CAN bus interface +product INTREPIDCS NEOVI 0x0701 NeoVI Blue vehicle bus interface + +/* I/O DATA products */ +product IODATA IU_CD2 0x0204 DVD Multi-plus unit iU-CD2 +product IODATA DVR_UEH8 0x0206 DVD Multi-plus unit DVR-UEH8 +product IODATA USBSSMRW 0x0314 USB-SSMRW SD-card +product IODATA USBSDRW 0x031e USB-SDRW SD-card +product IODATA USBETT 0x0901 USB ETT +product IODATA USBETTX 0x0904 USB ETTX +product IODATA USBETTXS 0x0913 USB ETTX +product IODATA USBWNB11A 0x0919 USB WN-B11 +product IODATA USBWNB11 0x0922 USB Airport WN-B11 +product IODATA ETGUS2 0x0930 ETG-US2 +product IODATA RT3072_1 0x0944 RT3072 +product IODATA RT3072_2 0x0945 RT3072 +product IODATA RT3072_3 0x0947 RT3072 +product IODATA RT3072_4 0x0948 RT3072 +product IODATA USBRSAQ 0x0a03 Serial USB-RSAQ1 +product IODATA USBRSAQ5 0x0a0e Serial USB-RSAQ5 +product IODATA2 USB2SC 0x0a09 USB2.0-SCSI Bridge USB2-SC + +/* Iomega products */ +product IOMEGA ZIP100 0x0001 Zip 100 +product IOMEGA ZIP250 0x0030 Zip 250 + +/* Integrated System Solution Corp. products */ +product ISSC ISSCBTA 0x1001 Bluetooth USB Adapter + +/* iTegno products */ +product ITEGNO WM1080A 0x1080 WM1080A GSM/GPRS modem +product ITEGNO WM2080A 0x2080 WM2080A CDMA modem + +/* Ituner networks products */ +product ITUNERNET USBLCD2X20 0x0002 USB-LCD 2x20 +product ITUNERNET USBLCD4X20 0xc001 USB-LCD 4x20 + +/* Jablotron products */ +product JABLOTRON PC60B 0x0001 PC-60B + +/* Jaton products */ +product JATON EDA 0x5704 Ethernet + +/* JMicron products */ +product JMICRON JM20336 0x2336 USB to SATA Bridge +product JMICRON JM20337 0x2338 USB to ATA/ATAPI Bridge + +/* JVC products */ +product JVC GR_DX95 0x000a GR-DX95 +product JVC MP_PRX1 0x3008 MP-PRX1 Ethernet + +/* JRC products */ +product JRC AH_J3001V_J3002V 0x0001 AirH PHONE AH-J3001V/J3002V + +/* Kawatsu products */ +product KAWATSU MH4000P 0x0003 MiniHub 4000P + +/* Keisokugiken Corp. products */ +product KEISOKUGIKEN USBDAQ 0x0068 HKS-0200 USBDAQ + +/* Kensington products */ +product KENSINGTON ORBIT 0x1003 Orbit USB/PS2 trackball +product KENSINGTON TURBOBALL 0x1005 TurboBall + +/* Keyspan products */ +product KEYSPAN USA28_NF 0x0101 USA-28 serial Adapter (no firmware) +product KEYSPAN USA28X_NF 0x0102 USA-28X serial Adapter (no firmware) +product KEYSPAN USA19_NF 0x0103 USA-19 serial Adapter (no firmware) +product KEYSPAN USA18_NF 0x0104 USA-18 serial Adapter (no firmware) +product KEYSPAN USA18X_NF 0x0105 USA-18X serial Adapter (no firmware) +product KEYSPAN USA19W_NF 0x0106 USA-19W serial Adapter (no firmware) +product KEYSPAN USA19 0x0107 USA-19 serial Adapter +product KEYSPAN USA19W 0x0108 USA-19W serial Adapter +product KEYSPAN USA49W_NF 0x0109 USA-49W serial Adapter (no firmware) +product KEYSPAN USA49W 0x010a USA-49W serial Adapter +product KEYSPAN USA19QI_NF 0x010b USA-19QI serial Adapter (no firmware) +product KEYSPAN USA19QI 0x010c USA-19QI serial Adapter +product KEYSPAN USA19Q_NF 0x010d USA-19Q serial Adapter (no firmware) +product KEYSPAN USA19Q 0x010e USA-19Q serial Adapter +product KEYSPAN USA28 0x010f USA-28 serial Adapter +product KEYSPAN USA28XXB 0x0110 USA-28X/XB serial Adapter +product KEYSPAN USA18 0x0111 USA-18 serial Adapter +product KEYSPAN USA18X 0x0112 USA-18X serial Adapter +product KEYSPAN USA28XB_NF 0x0113 USA-28XB serial Adapter (no firmware) +product KEYSPAN USA28XA_NF 0x0114 USA-28XB serial Adapter (no firmware) +product KEYSPAN USA28XA 0x0115 USA-28XA serial Adapter +product KEYSPAN USA18XA_NF 0x0116 USA-18XA serial Adapter (no firmware) +product KEYSPAN USA18XA 0x0117 USA-18XA serial Adapter +product KEYSPAN USA19QW_NF 0x0118 USA-19WQ serial Adapter (no firmware) +product KEYSPAN USA19QW 0x0119 USA-19WQ serial Adapter +product KEYSPAN USA19HA 0x0121 USA-19HS serial Adapter +product KEYSPAN UIA10 0x0201 UIA-10 remote control +product KEYSPAN UIA11 0x0202 UIA-11 remote control + +/* Kingston products */ +product KINGSTON XX1 0x0008 Ethernet +product KINGSTON KNU101TX 0x000a KNU101TX USB Ethernet + +/* Kawasaki products */ +product KLSI DUH3E10BT 0x0008 USB Ethernet +product KLSI DUH3E10BTN 0x0009 USB Ethernet + +/* Kodak products */ +product KODAK DC220 0x0100 Digital Science DC220 +product KODAK DC260 0x0110 Digital Science DC260 +product KODAK DC265 0x0111 Digital Science DC265 +product KODAK DC290 0x0112 Digital Science DC290 +product KODAK DC240 0x0120 Digital Science DC240 +product KODAK DC280 0x0130 Digital Science DC280 + +/* Kontron AG products */ +product KONTRON DM9601 0x8101 USB Ethernet +product KONTRON JP1082 0x9700 USB Ethernet + +/* Konica Corp. Products */ +product KONICA CAMERA 0x0720 Digital Color Camera + +/* KYE products */ +product KYE NICHE 0x0001 Niche mouse +product KYE NETSCROLL 0x0003 Genius NetScroll mouse +product KYE FLIGHT2000 0x1004 Flight 2000 joystick +product KYE VIVIDPRO 0x2001 ColorPage Vivid-Pro scanner + +/* Kyocera products */ +product KYOCERA FINECAM_S3X 0x0100 Finecam S3x +product KYOCERA FINECAM_S4 0x0101 Finecam S4 +product KYOCERA FINECAM_S5 0x0103 Finecam S5 +product KYOCERA FINECAM_L3 0x0105 Finecam L3 +product KYOCERA AHK3001V 0x0203 AH-K3001V +product KYOCERA2 CDMA_MSM_K 0x17da Qualcomm Kyocera CDMA Technologies MSM +product KYOCERA2 KPC680 0x180a Qualcomm Kyocera CDMA Technologies MSM + +/* LaCie products */ +product LACIE HD 0xa601 Hard Disk +product LACIE CDRW 0xa602 CD R/W + +/* Leadtek products */ +product LEADTEK 9531 0x2101 9531 GPS + +/* Lexar products */ +product LEXAR JUMPSHOT 0x0001 jumpSHOT CompactFlash Reader +product LEXAR CF_READER 0xb002 USB CF Reader + +/* Lexmark products */ +product LEXMARK S2450 0x0009 Optra S 2450 + +/* Liebert products */ +product LIEBERT POWERSURE_PXT 0xffff PowerSure Personal XT + +/* Linksys products */ +product LINKSYS MAUSB2 0x0105 Camedia MAUSB-2 +product LINKSYS USB10TX1 0x200c USB10TX +product LINKSYS USB10T 0x2202 USB10T Ethernet +product LINKSYS USB100TX 0x2203 USB100TX Ethernet +product LINKSYS USB100H1 0x2204 USB100H1 Ethernet/HPNA +product LINKSYS USB10TA 0x2206 USB10TA Ethernet +product LINKSYS USB10TX2 0x400b USB10TX +product LINKSYS2 WUSB11 0x2219 WUSB11 Wireless Adapter +product LINKSYS2 USB200M 0x2226 USB 2.0 10/100 Ethernet +product LINKSYS3 WUSB11v28 0x2233 WUSB11 v2.8 Wireless Adapter +product LINKSYS4 USB1000 0x0039 USB1000 +product LINKSYS4 WUSB100 0x0070 WUSB100 +product LINKSYS4 WUSB600N 0x0071 WUSB600N +product LINKSYS4 WUSB54GCV2 0x0073 WUSB54GC v2 +product LINKSYS4 WUSB54GCV3 0x0077 WUSB54GC v3 +product LINKSYS4 RT3070 0x0078 RT3070 +product LINKSYS4 WUSB600NV2 0x0079 WUSB600N v2 + +/* Logitech products */ +product LOGITECH M2452 0x0203 M2452 keyboard +product LOGITECH M4848 0x0301 M4848 mouse +product LOGITECH PAGESCAN 0x040f PageScan +product LOGITECH QUICKCAMWEB 0x0801 QuickCam Web +product LOGITECH QUICKCAMPRO 0x0810 QuickCam Pro +product LOGITECH WEBCAMC100 0X0817 Webcam C100 +product LOGITECH QUICKCAMEXP 0x0840 QuickCam Express +product LOGITECH QUICKCAM 0x0850 QuickCam +product LOGITECH QUICKCAMPRO3 0x0990 QuickCam Pro 9000 +product LOGITECH N43 0xc000 N43 +product LOGITECH N48 0xc001 N48 mouse +product LOGITECH MBA47 0xc002 M-BA47 mouse +product LOGITECH WMMOUSE 0xc004 WingMan Gaming Mouse +product LOGITECH BD58 0xc00c BD58 mouse +product LOGITECH UN58A 0xc030 iFeel Mouse +product LOGITECH UN53B 0xc032 iFeel MouseMan +product LOGITECH WMPAD 0xc208 WingMan GamePad Extreme +product LOGITECH WMRPAD 0xc20a WingMan RumblePad +product LOGITECH WMJOY 0xc281 WingMan Force joystick +product LOGITECH BB13 0xc401 USB-PS/2 Trackball +product LOGITECH RK53 0xc501 Cordless mouse +product LOGITECH RB6 0xc503 Cordless keyboard +product LOGITECH MX700 0xc506 Cordless optical mouse +product LOGITECH QUICKCAMPRO2 0xd001 QuickCam Pro + +/* Logitec Corp. products */ +product LOGITEC LDR_H443SU2 0x0033 DVD Multi-plus unit LDR-H443SU2 +product LOGITEC LDR_H443U2 0x00b3 DVD Multi-plus unit LDR-H443U2 +product LOGITEC LAN_GTJU2A 0x0160 LAN-GTJ/U2A Ethernet +product LOGITEC RT2870_1 0x0162 RT2870 +product LOGITEC RT2870_2 0x0163 RT2870 +product LOGITEC RT2870_3 0x0164 RT2870 +product LOGITEC LANW300NU2 0x0166 LAN-W300N/U2 + +/* Longcheer Holdings, Ltd. products */ +product LONGCHEER WM66 0x6061 Longcheer WM66 HSDPA +product LONGCHEER W14 0x9603 Mobilcom W14 +product LONGCHEER DISK 0xf000 Driver disk +product LONGCHEER XSSTICK 0x9605 4G Systems XSStick P14 + + +/* Lucent products */ +product LUCENT EVALKIT 0x1001 USS-720 evaluation kit + +/* Luwen products */ +product LUWEN EASYDISK 0x0005 EasyDisc + +/* Macally products */ +product MACALLY MOUSE1 0x0101 mouse + +/* Marvell Technology Group, Ltd. products */ +product MARVELL SHEEVAPLUG 0x9e8f SheevaPlug serial interface + +/* Matrix Orbital products */ +product MATRIXORBITAL MOUA 0x0153 Martrix Orbital MOU-Axxxx LCD displays + +/* MCT Corp. */ +product MCT HUB0100 0x0100 Hub +product MCT DU_H3SP_USB232 0x0200 D-Link DU-H3SP USB BAY Hub +product MCT USB232 0x0210 USB-232 Interface +product MCT SITECOM_USB232 0x0230 Sitecom USB-232 Products + +/* Medeli */ +product MEDELI DD305 0x5011 DD305 Digital Drum Set + +/* MediaTek, Inc. */ +product MEDIATEK MTK3329 0x3329 MTK II GPS Receiver + +/* Meizu Electronics */ +product MEIZU M6_SL 0x0140 MiniPlayer M6 (SL) + +/* Melco, Inc products */ +product MELCO LUATX1 0x0001 LUA-TX Ethernet +product MELCO LUATX5 0x0005 LUA-TX Ethernet +product MELCO LUA2TX5 0x0009 LUA2-TX Ethernet +product MELCO LUAKTX 0x0012 LUA-KTX Ethernet +product MELCO DUBPXXG 0x001c DUB-PxxG +product MELCO LUAU2KTX 0x003d LUA-U2-KTX Ethernet +product MELCO KG54YB 0x005e WLI-U2-KG54-YB WLAN +product MELCO KG54 0x0066 WLI-U2-KG54 WLAN +product MELCO KG54AI 0x0067 WLI-U2-KG54-AI WLAN +product MELCO LUA3U2AGT 0x006e LUA3-U2-AGT +product MELCO NINWIFI 0x008b Nintendo Wi-Fi +product MELCO PCOPRS1 0x00b3 PC-OP-RS1 RemoteStation +product MELCO SG54HP 0x00d8 WLI-U2-SG54HP +product MELCO G54HP 0x00d9 WLI-U2-G54HP +product MELCO KG54L 0x00da WLI-U2-KG54L +product MELCO WLIUCG300N 0x00e8 WLI-UC-G300N +product MELCO SG54HG 0x00f4 WLI-U2-SG54HG +product MELCO WLRUCG 0x0116 WLR-UC-G +product MELCO WLRUCGAOSS 0x0119 WLR-UC-G-AOSS +product MELCO WLIUCAG300N 0x012e WLI-UC-AG300N +product MELCO WLIUCG 0x0137 WLI-UC-G +product MELCO RT2870_1 0x0148 RT2870 +product MELCO RT2870_2 0x0150 RT2870 +product MELCO WLIUCGN 0x015d WLI-UC-GN +product MELCO WLIUCG301N 0x016f WLI-UC-G301N +product MELCO WLIUCGNM 0x01a2 WLI-UC-GNM + +/* Merlin products */ +product MERLIN V620 0x1110 Merlin V620 + +/* MetaGeek products */ +product METAGEEK WISPY1B 0x083e MetaGeek Wi-Spy +product METAGEEK WISPY24X 0x083f MetaGeek Wi-Spy 2.4x +product METAGEEK2 WISPYDBX 0x5000 MetaGeek Wi-Spy DBx + +/* Metricom products */ +product METRICOM RICOCHET_GS 0x0001 Ricochet GS + +/* MGE UPS Systems */ +product MGE UPS1 0x0001 MGE UPS SYSTEMS PROTECTIONCENTER 1 +product MGE UPS2 0xffff MGE UPS SYSTEMS PROTECTIONCENTER 2 + +/* MEI products */ +product MEI CASHFLOW_SC 0x1100 Cashflow-SC Cash Acceptor +product MEI S2000 0x1101 Seies 2000 Combo Acceptor + +/* Micro Star International products */ +product MSI BT_DONGLE 0x1967 Bluetooth USB dongle +product MSI RT3070_1 0x3820 RT3070 +product MSI RT3070_2 0x3821 RT3070 +product MSI RT3070_8 0x3822 RT3070 +product MSI RT3070_3 0x3870 RT3070 +product MSI RT3070_9 0x3871 RT3070 +product MSI UB11B 0x6823 UB11B +product MSI RT2570 0x6861 RT2570 +product MSI RT2570_2 0x6865 RT2570 +product MSI RT2570_3 0x6869 RT2570 +product MSI RT2573_1 0x6874 RT2573 +product MSI RT2573_2 0x6877 RT2573 +product MSI RT3070_4 0x6899 RT3070 +product MSI RT3070_5 0x821a RT3070 +product MSI RT3070_10 0x822a RT3070 +product MSI RT3070_6 0x870a RT3070 +product MSI RT3070_11 0x871a RT3070 +product MSI RT3070_7 0x899a RT3070 +product MSI RT2573_3 0xa861 RT2573 +product MSI RT2573_4 0xa874 RT2573 + +/* Micron products */ +product MICRON REALSSD 0x0655 Real SSD eUSB + +/* Microsoft products */ +product MICROSOFT SIDEPREC 0x0008 SideWinder Precision Pro +product MICROSOFT INTELLIMOUSE 0x0009 IntelliMouse +product MICROSOFT NATURALKBD 0x000b Natural Keyboard Elite +product MICROSOFT DDS80 0x0014 Digital Sound System 80 +product MICROSOFT SIDEWINDER 0x001a Sidewinder Precision Racing Wheel +product MICROSOFT INETPRO 0x001c Internet Keyboard Pro +product MICROSOFT TBEXPLORER 0x0024 Trackball Explorer +product MICROSOFT INTELLIEYE 0x0025 IntelliEye mouse +product MICROSOFT INETPRO2 0x002b Internet Keyboard Pro +product MICROSOFT INTELLIMOUSE5 0x0039 IntelliMouse 1.1 5-Button Mouse +product MICROSOFT WHEELMOUSE 0x0040 Wheel Mouse Optical +product MICROSOFT MN510 0x006e MN510 Wireless +product MICROSOFT 700WX 0x0079 Palm 700WX +product MICROSOFT MN110 0x007a 10/100 USB NIC +product MICROSOFT WLINTELLIMOUSE 0x008c Wireless Optical IntelliMouse +product MICROSOFT WLNOTEBOOK 0x00b9 Wireless Optical Mouse (Model 1023) +product MICROSOFT COMFORT3000 0x00d1 Comfort Optical Mouse 3000 (Model 1043) +product MICROSOFT WLNOTEBOOK3 0x00d2 Wireless Optical Mouse 3000 (Model 1049) +product MICROSOFT NATURAL4000 0x00db Natural Ergonomic Keyboard 4000 +product MICROSOFT WLNOTEBOOK2 0x00e1 Wireless Optical Mouse 3000 (Model 1056) +product MICROSOFT XBOX360 0x0292 XBOX 360 WLAN + +/* Microtech products */ +product MICROTECH SCSIDB25 0x0004 USB-SCSI-DB25 +product MICROTECH SCSIHD50 0x0005 USB-SCSI-HD50 +product MICROTECH DPCM 0x0006 USB CameraMate +product MICROTECH FREECOM 0xfc01 Freecom USB-IDE + +/* Microtek products */ +product MICROTEK 336CX 0x0094 Phantom 336CX - C3 scanner +product MICROTEK X6U 0x0099 ScanMaker X6 - X6U +product MICROTEK C6 0x009a Phantom C6 scanner +product MICROTEK 336CX2 0x00a0 Phantom 336CX - C3 scanner +product MICROTEK V6USL 0x00a3 ScanMaker V6USL +product MICROTEK V6USL2 0x80a3 ScanMaker V6USL +product MICROTEK V6UL 0x80ac ScanMaker V6UL + +/* Microtune, Inc. products */ +product MICROTUNE BT_DONGLE 0x1000 Bluetooth USB dongle + +/* Midiman products */ +product MIDIMAN MIDISPORT2X2 0x1001 Midisport 2x2 + +/* MindsAtWork products */ +product MINDSATWORK WALLET 0x0001 Digital Wallet + +/* Minolta Co., Ltd. */ +product MINOLTA 2300 0x4001 Dimage 2300 +product MINOLTA S304 0x4007 Dimage S304 +product MINOLTA X 0x4009 Dimage X +product MINOLTA 5400 0x400e Dimage 5400 +product MINOLTA F300 0x4011 Dimage F300 +product MINOLTA E223 0x4017 Dimage E223 + +/* Mitsumi products */ +product MITSUMI CDRRW 0x0000 CD-R/RW Drive +product MITSUMI BT_DONGLE 0x641f Bluetooth USB dongle +product MITSUMI FDD 0x6901 USB FDD + +/* Mobile Action products */ +product MOBILEACTION MA620 0x0620 MA-620 Infrared Adapter + +/* Mobility products */ +product MOBILITY EA 0x0204 Ethernet +product MOBILITY EASIDOCK 0x0304 EasiDock Ethernet + +/* MosChip products */ +product MOSCHIP MCS7703 0x7703 MCS7703 Serial Port Adapter +product MOSCHIP MCS7730 0x7730 MCS7730 Ethernet +product MOSCHIP MCS7820 0x7820 MCS7820 Serial Port Adapter +product MOSCHIP MCS7830 0x7830 MCS7830 Ethernet +product MOSCHIP MCS7840 0x7840 MCS7840 Serial Port Adapter + +/* Motorola products */ +product MOTOROLA MC141555 0x1555 MC141555 hub controller +product MOTOROLA SB4100 0x4100 SB4100 USB Cable Modem +product MOTOROLA2 T720C 0x2822 T720c +product MOTOROLA2 A41XV32X 0x2a22 A41x/V32x Mobile Phones +product MOTOROLA2 E398 0x4810 E398 Mobile Phone +product MOTOROLA2 USBLAN 0x600c USBLAN +product MOTOROLA2 USBLAN2 0x6027 USBLAN +product MOTOROLA4 RT2770 0x9031 RT2770 +product MOTOROLA4 RT3070 0x9032 RT3070 + +/* MultiTech products */ +product MULTITECH ATLAS 0xf101 MT5634ZBA-USB modem + +/* Mustek products */ +product MUSTEK 1200CU 0x0001 1200 CU scanner +product MUSTEK 600CU 0x0002 600 CU scanner +product MUSTEK 1200USB 0x0003 1200 USB scanner +product MUSTEK 1200UB 0x0006 1200 UB scanner +product MUSTEK 1200USBPLUS 0x0007 1200 USB Plus scanner +product MUSTEK 1200CUPLUS 0x0008 1200 CU Plus scanner +product MUSTEK BEARPAW1200F 0x0010 BearPaw 1200F scanner +product MUSTEK BEARPAW2400TA 0x0218 BearPaw 2400TA scanner +product MUSTEK BEARPAW1200TA 0x021e BearPaw 1200TA scanner +product MUSTEK 600USB 0x0873 600 USB scanner +product MUSTEK MDC800 0xa800 MDC-800 digital camera + +/* M-Systems products */ +product MSYSTEMS DISKONKEY 0x0010 DiskOnKey +product MSYSTEMS DISKONKEY2 0x0011 DiskOnKey + +/* Myson products */ +product MYSON HEDEN_8813 0x8813 USB-IDE +product MYSON HEDEN 0x8818 USB-IDE +product MYSON HUBREADER 0x8819 COMBO Card reader with USB HUB +product MYSON STARREADER 0x9920 USB flash card adapter + +/* National Semiconductor */ +product NATIONAL BEARPAW1200 0x1000 BearPaw 1200 +product NATIONAL BEARPAW2400 0x1001 BearPaw 2400 + +/* NEC products */ +product NEC HUB_0050 0x0050 USB 2.0 7-Port Hub +product NEC HUB_005A 0x005a USB 2.0 4-Port Hub +product NEC HUB 0x55aa hub +product NEC HUB_B 0x55ab hub + +/* NEODIO products */ +product NEODIO ND3260 0x3260 8-in-1 Multi-format Flash Controller +product NEODIO ND5010 0x5010 Multi-format Flash Controller + +/* Neotel products */ +product NEOTEL PRIME 0x4000 Prime USB modem + +/* Netac products */ +product NETAC CF_CARD 0x1060 USB-CF-Card +product NETAC ONLYDISK 0x0003 OnlyDisk + +/* NetChip Technology Products */ +product NETCHIP TURBOCONNECT 0x1080 Turbo-Connect +product NETCHIP CLIK_40 0xa140 USB Clik! 40 +product NETCHIP ETHERNETGADGET 0xa4a2 Linux Ethernet/RNDIS gadget on pxa210/25x/26x + +/* Netgear products */ +product NETGEAR EA101 0x1001 Ethernet +product NETGEAR EA101X 0x1002 Ethernet +product NETGEAR FA101 0x1020 Ethernet 10/100, USB1.1 +product NETGEAR FA120 0x1040 USB 2.0 Ethernet +product NETGEAR WG111V2_2 0x4240 PrismGT USB 2.0 WLAN +product NETGEAR WG111V3 0x4260 WG111v3 +product NETGEAR WG111U 0x4300 WG111U +product NETGEAR WG111U_NF 0x4301 WG111U (no firmware) +product NETGEAR WG111V2 0x6a00 WG111V2 +product NETGEAR2 MA101 0x4100 MA101 +product NETGEAR2 MA101B 0x4102 MA101 Rev B +product NETGEAR3 WG111T 0x4250 WG111T +product NETGEAR3 WG111T_NF 0x4251 WG111T (no firmware) +product NETGEAR3 WPN111 0x5f00 WPN111 +product NETGEAR3 WPN111_NF 0x5f01 WPN111 (no firmware) +product NETGEAR3 WPN111_2 0x5f02 WPN111 + +/* NetIndex products */ +product NETINDEX WS002IN 0x2001 Willcom WS002IN + +/* NEWlink */ +product NEWLINK USB2IDEBRIDGE 0x00ff USB 2.0 Hard Drive Enclosure + +/* Nikon products */ +product NIKON E990 0x0102 Digital Camera E990 +product NIKON LS40 0x4000 CoolScan LS40 ED +product NIKON D300 0x041a Digital Camera D300 + +/* NovaTech Products */ +product NOVATECH NV902 0x9020 NovaTech NV-902W +product NOVATECH RT2573 0x9021 RT2573 + +/* Nokia products */ +product NOKIA N958GB 0x0070 Nokia N95 8GBc +product NOKIA2 CA42 0x1234 CA-42 cable + +/* Novatel Wireless products */ +product NOVATEL V640 0x1100 Merlin V620 +product NOVATEL CDMA_MODEM 0x1110 Novatel Wireless Merlin CDMA +product NOVATEL V620 0x1110 Merlin V620 +product NOVATEL V740 0x1120 Merlin V740 +product NOVATEL V720 0x1130 Merlin V720 +product NOVATEL U740 0x1400 Merlin U740 +product NOVATEL U740_2 0x1410 Merlin U740 +product NOVATEL U870 0x1420 Merlin U870 +product NOVATEL XU870 0x1430 Merlin XU870 +product NOVATEL X950D 0x1450 Merlin X950D +product NOVATEL ES620 0x2100 Expedite ES620 +product NOVATEL E725 0x2120 Expedite E725 +product NOVATEL ES620_2 0x2130 Expedite ES620 +product NOVATEL ES620 0x2100 ES620 CDMA +product NOVATEL U720 0x2110 Merlin U720 +product NOVATEL EU730 0x2400 Expedite EU730 +product NOVATEL EU740 0x2410 Expedite EU740 +product NOVATEL EU870D 0x2420 Expedite EU870D +product NOVATEL U727 0x4100 Merlin U727 CDMA +product NOVATEL MC950D 0x4400 Novatel MC950D HSUPA +product NOVATEL ZEROCD 0x5010 Novatel ZeroCD +product NOVATEL ZEROCD2 0x5030 Novatel ZeroCD +product NOVATEL U727_2 0x5100 Merlin U727 CDMA +product NOVATEL U760 0x6000 Novatel U760 +product NOVATEL MC760 0x6002 Novatel MC760 +product NOVATEL MC547 0x7042 Novatel MC547 +product NOVATEL2 FLEXPACKGPS 0x0100 NovAtel FlexPack GPS receiver + +/* Merlin products */ +product MERLIN V620 0x1110 Merlin V620 + +/* O2Micro products */ +product O2MICRO OZ776_HUB 0x7761 OZ776 hub +product O2MICRO OZ776_CCID_SC 0x7772 OZ776 CCID SC Reader + +/* Olympus products */ +product OLYMPUS C1 0x0102 C-1 Digital Camera +product OLYMPUS C700 0x0105 C-700 Ultra Zoom + +/* OmniVision Technologies, Inc. products */ +product OMNIVISION OV511 0x0511 OV511 Camera +product OMNIVISION OV511PLUS 0xa511 OV511+ Camera + +/* OnSpec Electronic, Inc. */ +product ONSPEC SDS_HOTFIND_D 0x0400 SDS-infrared.com Hotfind-D Infrared Camera +product ONSPEC MDCFE_B_CF_READER 0xa000 MDCFE-B USB CF Reader +product ONSPEC CFMS_RW 0xa001 SIIG/Datafab Memory Stick+CF Reader/Writer +product ONSPEC READER 0xa003 Datafab-based Reader +product ONSPEC CFSM_READER 0xa005 PNY/Datafab CF+SM Reader +product ONSPEC CFSM_READER2 0xa006 Simple Tech/Datafab CF+SM Reader +product ONSPEC MDSM_B_READER 0xa103 MDSM-B reader +product ONSPEC CFSM_COMBO 0xa109 USB to CF + SM Combo (LC1) +product ONSPEC UCF100 0xa400 FlashLink UCF-100 CompactFlash Reader +product ONSPEC2 IMAGEMATE_SDDR55 0xa103 ImageMate SDDR55 + +/* Option products */ +product OPTION VODAFONEMC3G 0x5000 Vodafone Mobile Connect 3G datacard +product OPTION GT3G 0x6000 GlobeTrotter 3G datacard +product OPTION GT3GQUAD 0x6300 GlobeTrotter 3G QUAD datacard +product OPTION GT3GPLUS 0x6600 GlobeTrotter 3G+ datacard +product OPTION GTICON322 0xd033 GlobeTrotter Icon322 storage +product OPTION GTMAX36 0x6701 GlobeTrotter Max 3.6 Modem +product OPTION GTMAX72 0x6711 GlobeTrotter Max 7.2 HSDPA +product OPTION GTHSDPA 0x6971 GlobeTrotter HSDPA +product OPTION GTMAXHSUPA 0x7001 GlobeTrotter HSUPA +product OPTION GTMAXHSUPAE 0x6901 GlobeTrotter HSUPA PCIe +product OPTION GTMAX380HSUPAE 0x7211 GlobeTrotter 380HSUPA PCIe +product OPTION GT3G_1 0x6050 3G modem +product OPTION GT3G_2 0x6100 3G modem +product OPTION GT3G_3 0x6150 3G modem +product OPTION GT3G_4 0x6200 3G modem +product OPTION GT3G_5 0x6250 3G modem +product OPTION GT3G_6 0x6350 3G modem +product OPTION E6500 0x6500 3G modem +product OPTION E6501 0x6501 3G modem +product OPTION E6601 0x6601 3G modem +product OPTION E6721 0x6721 3G modem +product OPTION E6741 0x6741 3G modem +product OPTION E6761 0x6761 3G modem +product OPTION E6800 0x6800 3G modem +product OPTION E7021 0x7021 3G modem +product OPTION E7041 0x7041 3G modem +product OPTION E7061 0x7061 3G modem +product OPTION E7100 0x7100 3G modem +product OPTION GTM380 0x7201 3G modem +product OPTION GE40X 0x7601 Globetrotter HSUPA +product OPTION GSICON72 0x6911 GlobeSurfer iCON +product OPTION GSICONHSUPA 0x7251 Globetrotter HSUPA +product OPTION ICON401 0x7401 GlobeSurfer iCON 401 +product OPTION GTHSUPA 0x7011 Globetrotter HSUPA +product OPTION GMT382 0x7501 Globetrotter HSUPA +product OPTION GE40X_1 0x7301 Globetrotter HSUPA +product OPTION GE40X_2 0x7361 Globetrotter HSUPA +product OPTION GE40X_3 0x7381 Globetrotter HSUPA +product OPTION ICONEDGE 0xc031 GlobeSurfer iCON EDGE +product OPTION MODHSXPA 0xd013 Globetrotter HSUPA +product OPTION ICON321 0xd031 Globetrotter HSUPA +product OPTION ICON505 0xd055 Globetrotter iCON 505 +product OPTION ICON452 0x7901 Globetrotter iCON 452 + +/* OvisLink product */ +product OVISLINK RT3072 0x3072 RT3072 + +/* OQO */ +product OQO WIFI01 0x0002 model 01 WiFi interface +product OQO BT01 0x0003 model 01 Bluetooth interface +product OQO ETHER01PLUS 0x7720 model 01+ Ethernet +product OQO ETHER01 0x8150 model 01 Ethernet interface + +/* Ours Technology Inc. */ +product OTI DKU5 0x6858 DKU-5 Serial + +/* Owen.ru products */ +product OWEN AC4 0x0004 AC4 USB-RS485 converter + +/* Palm Computing, Inc. product */ +product PALM SERIAL 0x0080 USB Serial +product PALM M500 0x0001 Palm m500 +product PALM M505 0x0002 Palm m505 +product PALM M515 0x0003 Palm m515 +product PALM I705 0x0020 Palm i705 +product PALM TUNGSTEN_Z 0x0031 Palm Tungsten Z +product PALM M125 0x0040 Palm m125 +product PALM M130 0x0050 Palm m130 +product PALM TUNGSTEN_T 0x0060 Palm Tungsten T +product PALM ZIRE31 0x0061 Palm Zire 31 +product PALM ZIRE 0x0070 Palm Zire + +/* Panasonic products */ +product PANASONIC LS120CAM 0x0901 LS-120 Camera +product PANASONIC KXL840AN 0x0d01 CD-R Drive KXL-840AN +product PANASONIC KXLRW32AN 0x0d09 CD-R Drive KXL-RW32AN +product PANASONIC KXLCB20AN 0x0d0a CD-R Drive KXL-CB20AN +product PANASONIC KXLCB35AN 0x0d0e DVD-ROM & CD-R/RW +product PANASONIC SDCAAE 0x1b00 MultiMediaCard +product PANASONIC TYTP50P6S 0x3900 TY-TP50P6-S 50in Touch Panel + +/* PARA Industrial products */ +product PARA RT3070 0x8888 RT3070 + +/* Simtec Electronics products */ +product SIMTEC ENTROPYKEY 0x0001 Entropy Key + +/* Pegatron products */ +product PEGATRON RT2870 0x0002 RT2870 +product PEGATRON RT3070 0x000c RT3070 +product PEGATRON RT3070_2 0x000e RT3070 +product PEGATRON RT3070_3 0x0010 RT3070 + +/* Peracom products */ +product PERACOM SERIAL1 0x0001 Serial +product PERACOM ENET 0x0002 Ethernet +product PERACOM ENET3 0x0003 At Home Ethernet +product PERACOM ENET2 0x0005 Ethernet + +/* Philips products */ +product PHILIPS DSS350 0x0101 DSS 350 Digital Speaker System +product PHILIPS DSS 0x0104 DSS XXX Digital Speaker System +product PHILIPS HUB 0x0201 hub +product PHILIPS PCA646VC 0x0303 PCA646VC PC Camera +product PHILIPS PCVC680K 0x0308 PCVC680K Vesta Pro PC Camera +product PHILIPS DSS150 0x0471 DSS 150 Digital Speaker System +product PHILIPS ACE1001 0x066a AKTAKOM ACE-1001 cable +product PHILIPS SPE3030CC 0x083a USB 2.0 External Disk +product PHILIPS SNU5600 0x1236 SNU5600 +product PHILIPS UM10016 0x1552 ISP 1581 Hi-Speed USB MPEG2 Encoder Reference Kit +product PHILIPS DIVAUSB 0x1801 DIVA USB mp3 player +product PHILIPS RT2870 0x200f RT2870 + +/* Philips Semiconductor products */ +product PHILIPSSEMI HUB1122 0x1122 HUB + +/* Megatec */ +product MEGATEC UPS 0x5161 Phoenixtec protocol based UPS + +/* P.I. Engineering products */ +product PIENGINEERING PS2USB 0x020b PS2 to Mac USB Adapter + +/* Planex Communications products */ +product PLANEX GW_US11H 0x14ea GW-US11H WLAN +product PLANEX2 GW_US11S 0x3220 GW-US11S WLAN +product PLANEX2 GW_US54GXS 0x5303 GW-US54GXS WLAN +product PLANEX2 GWUS54HP 0xab01 GW-US54HP +product PLANEX2 GWUS300MINIS 0xab24 GW-US300MiniS +product PLANEX2 RT3070 0xab25 RT3070 +product PLANEX2 GWUS54MINI2 0xab50 GW-US54Mini2 +product PLANEX2 GWUS54SG 0xc002 GW-US54SG +product PLANEX2 GWUS54GZL 0xc007 GW-US54GZL +product PLANEX2 GWUS54GD 0xed01 GW-US54GD +product PLANEX2 GWUSMM 0xed02 GW-USMM +product PLANEX2 RT2870 0xed06 RT2870 +product PLANEX2 GWUSMICRON 0xed14 GW-USMicroN +product PLANEX3 GWUS54GZ 0xab10 GW-US54GZ +product PLANEX3 GU1000T 0xab11 GU-1000T +product PLANEX3 GWUS54MINI 0xab13 GW-US54Mini + +/* Plextor Corp. */ +product PLEXTOR 40_12_40U 0x0011 PlexWriter 40/12/40U + +/* PLX products */ +product PLX TESTBOARD 0x9060 test board +product PLX CA42 0xac70 CA-42 + +/* PNY products */ +product PNY ATTACHE2 0x0010 USB 2.0 Flash Drive + +/* PortGear products */ +product PORTGEAR EA8 0x0008 Ethernet +product PORTGEAR EA9 0x0009 Ethernet + +/* Portsmith products */ +product PORTSMITH EEA 0x3003 Express Ethernet + +/* Primax products */ +product PRIMAX G2X300 0x0300 G2-200 scanner +product PRIMAX G2E300 0x0301 G2E-300 scanner +product PRIMAX G2300 0x0302 G2-300 scanner +product PRIMAX G2E3002 0x0303 G2E-300 scanner +product PRIMAX 9600 0x0340 Colorado USB 9600 scanner +product PRIMAX 600U 0x0341 Colorado 600u scanner +product PRIMAX 6200 0x0345 Visioneer 6200 scanner +product PRIMAX 19200 0x0360 Colorado USB 19200 scanner +product PRIMAX 1200U 0x0361 Colorado 1200u scanner +product PRIMAX G600 0x0380 G2-600 scanner +product PRIMAX 636I 0x0381 ReadyScan 636i +product PRIMAX G2600 0x0382 G2-600 scanner +product PRIMAX G2E600 0x0383 G2E-600 scanner +product PRIMAX COMFORT 0x4d01 Comfort +product PRIMAX MOUSEINABOX 0x4d02 Mouse-in-a-Box +product PRIMAX PCGAUMS1 0x4d04 Sony PCGA-UMS1 +product PRIMAX HP_RH304AA 0x4d17 HP RH304AA mouse + +/* Prolific products */ +product PROLIFIC PL2301 0x0000 PL2301 Host-Host interface +product PROLIFIC PL2302 0x0001 PL2302 Host-Host interface +product PROLIFIC RSAQ2 0x04bb PL2303 Serial (IODATA USB-RSAQ2) +product PROLIFIC ALLTRONIX_GPRS 0x0609 Alltronix ACM003U00 modem +product PROLIFIC ALDIGA_AL11U 0x0611 AlDiga AL-11U modem +product PROLIFIC MICROMAX_610U 0x0612 Micromax 610U +product PROLIFIC DCU11 0x1234 DCU-11 Phone Cable +product PROLIFIC UIC_MSR206 0x206a UIC MSR206 Card Reader +product PROLIFIC PL2303 0x2303 PL2303 Serial (ATEN/IOGEAR UC232A) +product PROLIFIC PL2305 0x2305 Parallel printer +product PROLIFIC ATAPI4 0x2307 ATAPI-4 Controller +product PROLIFIC PL2501 0x2501 PL2501 Host-Host interface +product PROLIFIC PL2506 0x2506 PL2506 USB to IDE Bridge +product PROLIFIC HCR331 0x331a HCR331 Hybrid Card Reader +product PROLIFIC PHAROS 0xaaa0 Prolific Pharos +product PROLIFIC RSAQ3 0xaaa2 PL2303 Serial Adapter (IODATA USB-RSAQ3) +product PROLIFIC2 PL2303 0x2303 PL2303 Serial Adapter + +/* Putercom products */ +product PUTERCOM UPA100 0x047e USB-1284 BRIDGE + +/* Qcom products */ +product QCOM RT2573 0x6196 RT2573 +product QCOM RT2573_2 0x6229 RT2573 +product QCOM RT2573_3 0x6238 RT2573 +product QCOM RT2870 0x6259 RT2870 + +/* Qisda products */ +product QISDA H21_1 0x4512 3G modem +product QISDA H21_2 0x4523 3G modem +product QISDA H20_1 0x4515 3G modem +product QISDA H20_2 0x4519 3G modem + +/* Qualcomm products */ +product QUALCOMM CDMA_MSM 0x6000 CDMA Technologies MSM phone +product QUALCOMM2 MF330 0x6613 MF330 +product QUALCOMM2 RWT_FCT 0x3100 RWT FCT-CDMA 2000 1xRTT modem +product QUALCOMM2 CDMA_MSM 0x3196 CDMA Technologies MSM modem +product QUALCOMM2 AC8700 0x6000 AC8700 +product QUALCOMM2 VW110L 0x1000 Vertex Wireless 110L modem +product QUALCOMMINC CDMA_MSM 0x0001 CDMA Technologies MSM modem +product QUALCOMMINC E0002 0x0002 3G modem +product QUALCOMMINC E0003 0x0003 3G modem +product QUALCOMMINC E0004 0x0004 3G modem +product QUALCOMMINC E0005 0x0005 3G modem +product QUALCOMMINC E0006 0x0006 3G modem +product QUALCOMMINC E0007 0x0007 3G modem +product QUALCOMMINC E0008 0x0008 3G modem +product QUALCOMMINC E0009 0x0009 3G modem +product QUALCOMMINC E000A 0x000a 3G modem +product QUALCOMMINC E000B 0x000b 3G modem +product QUALCOMMINC E000C 0x000c 3G modem +product QUALCOMMINC E000D 0x000d 3G modem +product QUALCOMMINC E000E 0x000e 3G modem +product QUALCOMMINC E000F 0x000f 3G modem +product QUALCOMMINC E0010 0x0010 3G modem +product QUALCOMMINC E0011 0x0011 3G modem +product QUALCOMMINC E0012 0x0012 3G modem +product QUALCOMMINC E0013 0x0013 3G modem +product QUALCOMMINC E0014 0x0014 3G modem +product QUALCOMMINC MF628 0x0015 3G modem +product QUALCOMMINC MF633R 0x0016 ZTE WCDMA modem +product QUALCOMMINC E0017 0x0017 3G modem +product QUALCOMMINC E0018 0x0018 3G modem +product QUALCOMMINC E0019 0x0019 3G modem +product QUALCOMMINC E0020 0x0020 3G modem +product QUALCOMMINC E0021 0x0021 3G modem +product QUALCOMMINC E0022 0x0022 3G modem +product QUALCOMMINC E0023 0x0023 3G modem +product QUALCOMMINC E0024 0x0024 3G modem +product QUALCOMMINC E0025 0x0025 3G modem +product QUALCOMMINC E0026 0x0026 3G modem +product QUALCOMMINC E0027 0x0027 3G modem +product QUALCOMMINC E0028 0x0028 3G modem +product QUALCOMMINC E0029 0x0029 3G modem +product QUALCOMMINC E0030 0x0030 3G modem +product QUALCOMMINC MF626 0x0031 3G modem +product QUALCOMMINC E0032 0x0032 3G modem +product QUALCOMMINC E0033 0x0033 3G modem +product QUALCOMMINC E0037 0x0037 3G modem +product QUALCOMMINC E0039 0x0039 3G modem +product QUALCOMMINC E0042 0x0042 3G modem +product QUALCOMMINC E0043 0x0043 3G modem +product QUALCOMMINC E0048 0x0048 3G modem +product QUALCOMMINC E0049 0x0049 3G modem +product QUALCOMMINC E0051 0x0051 3G modem +product QUALCOMMINC E0052 0x0052 3G modem +product QUALCOMMINC ZTE_STOR2 0x0053 USB ZTE Storage +product QUALCOMMINC E0054 0x0054 3G modem +product QUALCOMMINC E0055 0x0055 3G modem +product QUALCOMMINC E0057 0x0057 3G modem +product QUALCOMMINC E0058 0x0058 3G modem +product QUALCOMMINC E0059 0x0059 3G modem +product QUALCOMMINC E0060 0x0060 3G modem +product QUALCOMMINC E0061 0x0061 3G modem +product QUALCOMMINC E0062 0x0062 3G modem +product QUALCOMMINC E0063 0x0063 3G modem +product QUALCOMMINC E0064 0x0064 3G modem +product QUALCOMMINC E0066 0x0066 3G modem +product QUALCOMMINC E0069 0x0069 3G modem +product QUALCOMMINC E0070 0x0070 3G modem +product QUALCOMMINC E0073 0x0073 3G modem +product QUALCOMMINC E0076 0x0076 3G modem +product QUALCOMMINC E0078 0x0078 3G modem +product QUALCOMMINC E0082 0x0082 3G modem +product QUALCOMMINC E0086 0x0086 3G modem +product QUALCOMMINC SURFSTICK 0x0117 1&1 Surf Stick +product QUALCOMMINC ZTE_STOR 0x2000 USB ZTE Storage +product QUALCOMMINC E2002 0x2002 3G modem +product QUALCOMMINC E2003 0x2003 3G modem +product QUALCOMMINC AC8710 0xfff1 3G modem +product QUALCOMMINC AC2726 0xfff5 3G modem +product QUALCOMMINC AC8700 0xfffe CDMA 1xEVDO USB modem + +/* Quanta products */ +product QUANTA RW6815_1 0x00ce HP iPAQ rw6815 +product QUANTA RT3070 0x0304 RT3070 +product QUANTA Q101_STOR 0x1000 USB Q101 Storage +product QUANTA Q101 0xea02 HSDPA modem +product QUANTA Q111 0xea03 HSDPA modem +product QUANTA GLX 0xea04 HSDPA modem +product QUANTA GKE 0xea05 HSDPA modem +product QUANTA GLE 0xea06 HSDPA modem +product QUANTA RW6815R 0xf003 HP iPAQ rw6815 RNDIS + +/* Qtronix products */ +product QTRONIX 980N 0x2011 Scorpion-980N keyboard + +/* Quickshot products */ +product QUICKSHOT STRIKEPAD 0x6238 USB StrikePad + +/* Radio Shack */ +product RADIOSHACK USBCABLE 0x4026 USB to Serial Cable + +/* Rainbow Technologies products */ +product RAINBOW IKEY2000 0x1200 i-Key 2000 + +/* Ralink Technology products */ +product RALINK RT2570 0x1706 RT2500USB Wireless Adapter +product RALINK RT2070 0x2070 RT2070 +product RALINK RT2570_2 0x2570 RT2500USB Wireless Adapter +product RALINK RT2573 0x2573 RT2501USB Wireless Adapter +product RALINK RT2671 0x2671 RT2601USB Wireless Adapter +product RALINK RT2770 0x2770 RT2770 +product RALINK RT2870 0x2870 RT2870 +product RALINK RT3070 0x3070 RT3070 +product RALINK RT3071 0x3071 RT3071 +product RALINK RT3072 0x3072 RT3072 +product RALINK RT3370 0x3370 RT3370 +product RALINK RT3572 0x3572 RT3572 +product RALINK RT8070 0x8070 RT8070 +product RALINK RT2570_3 0x9020 RT2500USB Wireless Adapter +product RALINK RT2573_2 0x9021 RT2501USB Wireless Adapter + +/* RATOC Systems products */ +product RATOC REXUSB60 0xb000 USB serial adapter REX-USB60 +product RATOC REXUSB60F 0xb020 USB serial adapter REX-USB60F + +/* ReakTek products */ +/* Green House and CompUSA OEM this part */ +product REALTEK DUMMY 0x0000 Dummy product +product REALTEK USB20CRW 0x0158 USB20CRW Card Reader +product REALTEK USBKR100 0x8150 USBKR100 USB Ethernet +product REALTEK RTL8187 0x8187 RTL8187 Wireless Adapter +product REALTEK RTL8187B_0 0x8189 RTL8187B Wireless Adapter +product REALTEK RTL8187B_1 0x8197 RTL8187B Wireless Adapter +product REALTEK RTL8187B_2 0x8198 RTL8187B Wireless Adapter + +/* Renesas products */ +product RENESAS RX610 0x0053 RX610 RX-Stick + +/* Ricoh products */ +product RICOH VGPVCC2 0x1830 VGP-VCC2 Camera +product RICOH VGPVCC3 0x1832 VGP-VCC3 Camera +product RICOH VGPVCC2_2 0x1833 VGP-VCC2 Camera +product RICOH VGPVCC2_3 0x1834 VGP-VCC2 Camera +product RICOH VGPVCC7 0x183a VGP-VCC7 Camera +product RICOH VGPVCC8 0x183b VGP-VCC8 Camera + +/* Reiner-SCT products */ +product REINERSCT CYBERJACK_ECOM 0x0100 e-com cyberJack + +/* Roland products */ +product ROLAND UA100 0x0000 UA-100 Audio I/F +product ROLAND UM4 0x0002 UM-4 MIDI I/F +product ROLAND SC8850 0x0003 SC-8850 MIDI Synth +product ROLAND U8 0x0004 U-8 Audio I/F +product ROLAND UM2 0x0005 UM-2 MIDI I/F +product ROLAND SC8820 0x0007 SC-8820 MIDI Synth +product ROLAND PC300 0x0008 PC-300 MIDI Keyboard +product ROLAND UM1 0x0009 UM-1 MIDI I/F +product ROLAND SK500 0x000b SK-500 MIDI Keyboard +product ROLAND SCD70 0x000c SC-D70 MIDI Synth +product ROLAND UM880N 0x0014 EDIROL UM-880 MIDI I/F (native) +product ROLAND UM880G 0x0015 EDIROL UM-880 MIDI I/F (generic) +product ROLAND SD90 0x0016 SD-90 MIDI Synth +product ROLAND UM550 0x0023 UM-550 MIDI I/F +product ROLAND SD20 0x0027 SD-20 MIDI Synth +product ROLAND SD80 0x0029 SD-80 MIDI Synth +product ROLAND UA700 0x002b UA-700 Audio I/F + +/* Rockfire products */ +product ROCKFIRE GAMEPAD 0x2033 gamepad 203USB + +/* RATOC Systems products */ +product RATOC REXUSB60 0xb000 REX-USB60 +product RATOC REXUSB60F 0xb020 REX-USB60F + +/* Sagem products */ +product SAGEM USBSERIAL 0x0027 USB-Serial Controller +product SAGEM XG760A 0x004a XG-760A +product SAGEM XG76NA 0x0062 XG-76NA + +/* Samsung products */ +product SAMSUNG WIS09ABGN 0x2018 WIS09ABGN Wireless LAN adapter +product SAMSUNG ML6060 0x3008 ML-6060 laser printer +product SAMSUNG YP_U2 0x5050 YP-U2 MP3 Player +product SAMSUNG YP_U4 0x5092 YP-U4 MP3 Player +product SAMSUNG I500 0x6601 I500 Palm USB Phone +product SAMSUNG I330 0x8001 I330 phone cradle +product SAMSUNG2 RT2870_1 0x2018 RT2870 + +/* Samsung Techwin products */ +product SAMSUNG_TECHWIN DIGIMAX_410 0x000a Digimax 410 + +/* SanDisk products */ +product SANDISK SDDR05A 0x0001 ImageMate SDDR-05a +product SANDISK SDDR31 0x0002 ImageMate SDDR-31 +product SANDISK SDDR05 0x0005 ImageMate SDDR-05 +product SANDISK SDDR12 0x0100 ImageMate SDDR-12 +product SANDISK SDDR09 0x0200 ImageMate SDDR-09 +product SANDISK SDDR75 0x0810 ImageMate SDDR-75 +product SANDISK SDCZ2_256 0x7104 Cruzer Mini 256MB +product SANDISK SDCZ4_128 0x7112 Cruzer Micro 128MB +product SANDISK SDCZ4_256 0x7113 Cruzer Micro 256MB + +/* Sanwa Electric Instrument Co., Ltd. products */ +product SANWA KB_USB2 0x0701 KB-USB2 multimeter cable + +/* Sanyo Electric products */ +product SANYO SCP4900 0x0701 Sanyo SCP-4900 USB Phone + +/* ScanLogic products */ +product SCANLOGIC SL11R 0x0002 SL11R IDE Adapter +product SCANLOGIC 336CX 0x0300 Phantom 336CX - C3 scanner + +/* Senao products */ +product SENAO RT2870_3 0x0605 RT2870 +product SENAO RT2870_4 0x0615 RT2870 +product SENAO NUB8301 0x2000 NUB-8301 +product SENAO RT2870_1 0x9701 RT2870 +product SENAO RT2870_2 0x9702 RT2870 +product SENAO RT3070 0x9703 RT3070 +product SENAO RT3071 0x9705 RT3071 +product SENAO RT3072_1 0x9706 RT3072 +product SENAO RT3072_2 0x9707 RT3072 +product SENAO RT3072_3 0x9708 RT3072 +product SENAO RT3072_4 0x9709 RT3072 +product SENAO RT3072_5 0x9801 RT3072 + +/* ShanTou products */ +product SHANTOU ST268 0x0268 ST268 +product SHANTOU DM9601 0x9601 DM 9601 +product SHANTOU ADM8515 0x8515 ADM8515 + +/* Shark products */ +product SHARK PA 0x0400 Pocket Adapter + +/* Sharp products */ +product SHARP SL5500 0x8004 Zaurus SL-5500 PDA +product SHARP SLA300 0x8005 Zaurus SL-A300 PDA +product SHARP SL5600 0x8006 Zaurus SL-5600 PDA +product SHARP SLC700 0x8007 Zaurus SL-C700 PDA +product SHARP SLC750 0x9031 Zaurus SL-C750 PDA +product SHARP WZERO3ES 0x9123 W-ZERO3 ES Smartphone +product SHARP WZERO3ADES 0x91ac Advanced W-ZERO3 ES Smartphone +product SHARP WILLCOM03 0x9242 WILLCOM03 + +/* Shuttle Technology products */ +product SHUTTLE EUSB 0x0001 E-USB Bridge +product SHUTTLE EUSCSI 0x0002 eUSCSI Bridge +product SHUTTLE SDDR09 0x0003 ImageMate SDDR09 +product SHUTTLE EUSBCFSM 0x0005 eUSB SmartMedia / CompactFlash Adapter +product SHUTTLE ZIOMMC 0x0006 eUSB MultiMediaCard Adapter +product SHUTTLE HIFD 0x0007 Sony Hifd +product SHUTTLE EUSBATAPI 0x0009 eUSB ATA/ATAPI Adapter +product SHUTTLE CF 0x000a eUSB CompactFlash Adapter +product SHUTTLE EUSCSI_B 0x000b eUSCSI Bridge +product SHUTTLE EUSCSI_C 0x000c eUSCSI Bridge +product SHUTTLE CDRW 0x0101 CD-RW Device +product SHUTTLE EUSBORCA 0x0325 eUSB ORCA Quad Reader + +/* Siemens products */ +product SIEMENS SPEEDSTREAM 0x1001 SpeedStream +product SIEMENS SPEEDSTREAM22 0x1022 SpeedStream 1022 +product SIEMENS2 WLL013 0x001b WLL013 +product SIEMENS2 ES75 0x0034 GSM module MC35 +product SIEMENS2 WL54G 0x3c06 54g USB Network Adapter +product SIEMENS3 SX1 0x0001 SX1 +product SIEMENS3 X65 0x0003 X65 +product SIEMENS3 X75 0x0004 X75 +product SIEMENS3 EF81 0x0005 EF81 + +/* Sierra Wireless products */ +product SIERRA EM5625 0x0017 EM5625 +product SIERRA MC5720_2 0x0018 MC5720 +product SIERRA MC5725 0x0020 MC5725 +product SIERRA AIRCARD580 0x0112 Sierra Wireless AirCard 580 +product SIERRA AIRCARD595 0x0019 Sierra Wireless AirCard 595 +product SIERRA AC595U 0x0120 Sierra Wireless AirCard 595U +product SIERRA AC597E 0x0021 Sierra Wireless AirCard 597E +product SIERRA EM5725 0x0022 EM5725 +product SIERRA C597 0x0023 Sierra Wireless Compass 597 +product SIERRA MC5727 0x0024 MC5727 +product SIERRA T598 0x0025 T598 +product SIERRA T11 0x0026 T11 +product SIERRA AC402 0x0027 AC402 +product SIERRA MC5728 0x0028 MC5728 +product SIERRA E0029 0x0029 E0029 +product SIERRA AIRCARD580 0x0112 Sierra Wireless AirCard 580 +product SIERRA AC595U 0x0120 Sierra Wireless AirCard 595U +product SIERRA MC5720 0x0218 MC5720 Wireless Modem +product SIERRA MINI5725 0x0220 Sierra Wireless miniPCI 5275 +product SIERRA MC5727_2 0x0224 MC5727 +product SIERRA MC8755_2 0x6802 MC8755 +product SIERRA MC8765 0x6803 MC8765 +product SIERRA MC8755 0x6804 MC8755 +product SIERRA MC8765_2 0x6805 MC8765 +product SIERRA MC8755_4 0x6808 MC8755 +product SIERRA MC8765_3 0x6809 MC8765 +product SIERRA AC875U 0x6812 AC875U HSDPA USB Modem +product SIERRA MC8755_3 0x6813 MC8755 HSDPA +product SIERRA MC8775_2 0x6815 MC8775 +product SIERRA MC8775 0x6816 MC8775 +product SIERRA AC875 0x6820 Sierra Wireless AirCard 875 +product SIERRA AC875U_2 0x6821 AC875U +product SIERRA AC875E 0x6822 AC875E +product SIERRA MC8780 0x6832 MC8780 +product SIERRA MC8781 0x6833 MC8781 +product SIERRA MC8780_2 0x6834 MC8780 +product SIERRA MC8781_2 0x6835 MC8781 +product SIERRA MC8780_3 0x6838 MC8780 +product SIERRA MC8781_3 0x6839 MC8781 +product SIERRA MC8785 0x683A MC8785 +product SIERRA MC8785_2 0x683B MC8785 +product SIERRA MC8790 0x683C MC8790 +product SIERRA MC8791 0x683D MC8791 +product SIERRA MC8792 0x683E MC8792 +product SIERRA AC880 0x6850 Sierra Wireless AirCard 880 +product SIERRA AC881 0x6851 Sierra Wireless AirCard 881 +product SIERRA AC880E 0x6852 Sierra Wireless AirCard 880E +product SIERRA AC881E 0x6853 Sierra Wireless AirCard 881E +product SIERRA AC880U 0x6855 Sierra Wireless AirCard 880U +product SIERRA AC881U 0x6856 Sierra Wireless AirCard 881U +product SIERRA AC885E 0x6859 AC885E +product SIERRA AC885E_2 0x685A AC885E +product SIERRA AC885U 0x6880 Sierra Wireless AirCard 885U +product SIERRA C888 0x6890 C888 +product SIERRA C22 0x6891 C22 +product SIERRA E6892 0x6892 E6892 +product SIERRA E6893 0x6893 E6893 +product SIERRA MC8700 0x68A3 MC8700 +product SIERRA AIRCARD875 0x6820 Aircard 875 HSDPA +product SIERRA AC313U 0x68aa Sierra Wireless AirCard 313U +product SIERRA TRUINSTALL 0x0fff Aircard Tru Installer + +/* Sigmatel products */ +product SIGMATEL WBT_3052 0x4200 WBT-3052 IrDA/USB Bridge +product SIGMATEL I_BEAD100 0x8008 i-Bead 100 MP3 Player + +/* SIIG products */ +/* Also: Omnidirectional Control Technology products */ +product SIIG DIGIFILMREADER 0x0004 DigiFilm-Combo Reader +product SIIG WINTERREADER 0x0330 WINTERREADER Reader +product SIIG2 USBTOETHER 0x0109 USB TO Ethernet +product SIIG2 US2308 0x0421 Serial + +/* Silicom products */ +product SILICOM U2E 0x0001 U2E +product SILICOM GPE 0x0002 Psion Gold Port Ethernet + +/* SI Labs */ +product SILABS VSTABI 0x0f91 Vstabi +product SILABS ARKHAM_DS101_M 0x1101 Arkham DS101 Monitor +product SILABS ARKHAM_DS101_A 0x1601 Arkham DS101 Adapter +product SILABS BSM7DUSB 0x800a BSM7-D-USB +product SILABS POLOLU 0x803b Pololu Serial +product SILABS CYGNAL_DEBUG 0x8044 Cygnal Debug Adapter +product SILABS SB_PARAMOUNT_ME 0x8043 Software Bisque Paramount ME +product SILABS SAEL 0x8053 SA-EL USB +product SILABS GSM2228 0x8054 Enfora GSM2228 USB +product SILABS ARGUSISP 0x8066 Argussoft ISP +product SILABS IMS_USB_RS422 0x806f IMS USB-RS422 +product SILABS CRUMB128 0x807a Crumb128 board +product SILABS DEGREE 0x80ca Degree Controls Inc +product SILABS TRACIENT 0x80dd Tracient RFID +product SILABS TRAQMATE 0x80ed Track Systems Traqmate +product SILABS SUUNTO 0x80f6 Suunto Sports Instrument +product SILABS ARYGON_MIFARE 0x8115 Arygon Mifare RFID reader +product SILABS BURNSIDE 0x813d Burnside Telecon Deskmobile +product SILABS TAMSMASTER 0x813f Tams Master Easy Control +product SILABS WMRBATT 0x814a WMR RIGblaster Plug&Play +product SILABS WMRRIGBLASTER 0x814a WMR RIGblaster Plug&Play +product SILABS WMRRIGTALK 0x814b WMR RIGtalk RT1 +product SILABS B_G_H3000 0x8156 B&G H3000 Data Cable +product SILABS HELICOM 0x815e Helicomm IP-Link 1220-DVM +product SILABS AVIT_USB_TTL 0x818b AVIT Research USB-TTL +product SILABS MJS_TOSLINK 0x819f MJS USB-TOSLINk +product SILABS WAVIT 0x81a6 ThinkOptics WavIt +product SILABS MSD_DASHHAWK 0x81ac MSD DashHawk +product SILABS INSYS_MODEM 0x81ad INSYS Modem +product SILABS LIPOWSKY_JTAG 0x81c8 Lipowsky Baby-JTAG +product SILABS LIPOWSKY_LIN 0x81e2 Lipowsky Baby-LIN +product SILABS AEROCOMM 0x81e7 Aerocomm Radio +product SILABS ZEPHYR_BIO 0x81e8 Zephyr Bioharness +product SILABS EMS_C1007 0x81f2 EMS C1007 HF RFID controller +product SILABS LIPOWSKY_HARP 0x8218 Lipowsky HARP-1 +product SILABS C2_EDGE_MODEM 0x822b Commander 2 EDGE(GSM) Modem +product SILABS CYGNAL_GPS 0x826b Cygnal Fasttrax GPS +product SILABS TELEGESYS_ETRX2 0x8293 Telegesys ETRX2USB +product SILABS PROCYON_AVS 0x82f9 Procyon AVS +product SILABS MC35PU 0x8341 MC35pu +product SILABS CYGNAL 0x8382 Cygnal +product SILABS AMBER_AMB2560 0x83a8 Amber Wireless AMB2560 +product SILABS KYOCERA_GPS 0x8411 Kyocera GPS +product SILABS BEI_VCP 0x846e BEI USB Sensor (VCP) +product SILABS BALLUFF_RFID 0x8477 Balluff RFID reader +product SILABS CP2102 0xea60 SILABS USB UART +product SILABS CP210X_2 0xea61 CP210x Serial +product SILABS INFINITY_MIC 0xea71 Infinity GPS-MIC-1 Radio Monophone +product SILABS USBSCOPE50 0xf001 USBscope50 +product SILABS USBWAVE12 0xf002 USBwave12 +product SILABS USBPULSE100 0xf003 USBpulse100 +product SILABS USBCOUNT50 0xf004 USBcount50 +product SILABS2 DCU11CLONE 0xaa26 DCU-11 clone +product SILABS3 GPRS_MODEM 0xea61 GPRS Modem +product SILABS4 100EU_MODEM 0xea61 GPRS Modem 100EU + +/* Silicon Portals Inc. */ +product SILICONPORTALS YAPPH_NF 0x0200 YAP Phone (no firmware) +product SILICONPORTALS YAPPHONE 0x0201 YAP Phone + +/* Sirius Technologies products */ +product SIRIUS ROADSTER 0x0001 NetComm Roadster II 56 USB + +/* Sitecom products */ +product SITECOM LN029 0x182d USB 2.0 Ethernet +product SITECOM SERIAL 0x2068 USB to serial cable (v2) +product SITECOM2 WL022 0x182d WL-022 + +/* Sitecom Europe products */ +product SITECOMEU RT2870_1 0x0017 RT2870 +product SITECOMEU WL168V1 0x000d WL-168 v1 +product SITECOMEU LN030 0x0021 MCS7830 +product SITECOMEU WL168V4 0x0028 WL-168 v4 +product SITECOMEU RT2870_2 0x002b RT2870 +product SITECOMEU RT2870_3 0x002c RT2870 +product SITECOMEU RT2870_4 0x002d RT2870 +product SITECOMEU RT2770 0x0039 RT2770 +product SITECOMEU RT3070_2 0x003b RT3070 +product SITECOMEU RT3070_3 0x003c RT3070 +product SITECOMEU RT3070_4 0x003d RT3070 +product SITECOMEU RT3070 0x003e RT3070 +product SITECOMEU WL608 0x003f WL-608 +product SITECOMEU RT3071 0x0040 RT3071 +product SITECOMEU RT3072_1 0x0041 RT3072 +product SITECOMEU RT3072_2 0x0042 RT3072 +product SITECOMEU RT3072_3 0x0047 RT3072 +product SITECOMEU RT3072_4 0x0048 RT3072 +product SITECOMEU RT3072_5 0x004a RT3072 +product SITECOMEU RT3072_6 0x004d RT3072 +product SITECOMEU LN028 0x061c LN-028 +product SITECOMEU WL113 0x9071 WL-113 +product SITECOMEU ZD1211B 0x9075 ZD1211B +product SITECOMEU WL172 0x90ac WL-172 +product SITECOMEU WL113R2 0x9712 WL-113 rev 2 + +/* Skanhex Technology products */ +product SKANHEX MD_7425 0x410a MD 7425 Camera +product SKANHEX SX_520Z 0x5200 SX 520z Camera + +/* Smart Technologies products */ +product SMART PL2303 0x2303 Serial adapter + +/* SmartBridges products */ +product SMARTBRIDGES SMARTLINK 0x0001 SmartLink USB Ethernet +product SMARTBRIDGES SMARTNIC 0x0003 smartNIC 2 PnP Ethernet + +/* SMC products */ +product SMC 2102USB 0x0100 10Mbps Ethernet +product SMC 2202USB 0x0200 10/100 Ethernet +product SMC 2206USB 0x0201 EZ Connect USB Ethernet +product SMC 2862WG 0xee13 EZ Connect Wireless Adapter +product SMC2 2020HUB 0x2020 USB Hub +product SMC2 2514HUB 0x2514 USB Hub +product SMC3 2662WUSB 0xa002 2662W-AR Wireless + +/* SOHOware products */ +product SOHOWARE NUB100 0x9100 10/100 USB Ethernet +product SOHOWARE NUB110 0x9110 10/100 USB Ethernet + +/* SOLID YEAR products */ +product SOLIDYEAR KEYBOARD 0x2101 Solid Year USB keyboard + +/* SONY products */ +product SONY DSC 0x0010 DSC cameras +product SONY MS_NW_MS7 0x0025 Memorystick NW-MS7 +product SONY PORTABLE_HDD_V2 0x002b Portable USB Harddrive V2 +product SONY MSACUS1 0x002d Memorystick MSAC-US1 +product SONY HANDYCAM 0x002e Handycam +product SONY MSC 0x0032 MSC memory stick slot +product SONY CLIE_35 0x0038 Sony Clie v3.5 +product SONY MS_PEG_N760C 0x0058 PEG N760c Memorystick +product SONY CLIE_40 0x0066 Sony Clie v4.0 +product SONY MS_MSC_U03 0x0069 Memorystick MSC-U03 +product SONY CLIE_40_MS 0x006d Sony Clie v4.0 Memory Stick slot +product SONY CLIE_S360 0x0095 Sony Clie s360 +product SONY CLIE_41_MS 0x0099 Sony Clie v4.1 Memory Stick slot +product SONY CLIE_41 0x009a Sony Clie v4.1 +product SONY CLIE_NX60 0x00da Sony Clie nx60 +product SONY CLIE_TH55 0x0144 Sony Clie th55 +product SONY CLIE_TJ37 0x0169 Sony Clie tj37 +product SONY RF_RECEIVER 0x01db Sony RF mouse/kbd Receiver VGP-WRC1 +product SONY QN3 0x0437 Sony QN3 CMD-Jxx phone cable + +/* Sony Ericsson products */ +product SONYERICSSON DCU10 0x0528 DCU-10 Phone Data Cable +product SONYERICSSON DATAPILOT 0x2003 Datapilot Phone Cable + +/* SOURCENEXT products */ +product SOURCENEXT KEIKAI8 0x039f KeikaiDenwa 8 +product SOURCENEXT KEIKAI8_CHG 0x012e KeikaiDenwa 8 with charger + +/* SparkLAN products */ +product SPARKLAN RT2573 0x0004 RT2573 +product SPARKLAN RT2870_1 0x0006 RT2870 +product SPARKLAN RT3070 0x0010 RT3070 + +/* Soundgraph products */ +product SOUNDGRAPH IMON_VFD 0x0044 Antec Veris Elite VFD Panel, Knob, and Remote +product SOUNDGRAPH SSTONE_LC16 0xffdc Silverstone LC16 VFD Panel, Knob, and Remote + +/* Speed Dragon Multimedia products */ +product SPEEDDRAGON MS3303H 0x110b MS3303H Serial + +/* Sphairon Access Systems GmbH products */ +product SPHAIRON UB801R 0x0110 UB801R + +/* Stelera Wireless products */ +product STELERA ZEROCD 0x1000 Zerocd Installer +product STELERA C105 0x1002 Stelera/Bandrish C105 USB +product STELERA E1003 0x1003 3G modem +product STELERA E1004 0x1004 3G modem +product STELERA E1005 0x1005 3G modem +product STELERA E1006 0x1006 3G modem +product STELERA E1007 0x1007 3G modem +product STELERA E1008 0x1008 3G modem +product STELERA E1009 0x1009 3G modem +product STELERA E100A 0x100a 3G modem +product STELERA E100B 0x100b 3G modem +product STELERA E100C 0x100c 3G modem +product STELERA E100D 0x100d 3G modem +product STELERA E100E 0x100e 3G modem +product STELERA E100F 0x100f 3G modem +product STELERA E1010 0x1010 3G modem +product STELERA E1011 0x1011 3G modem +product STELERA E1012 0x1012 3G modem + +/* MpMan products */ +product MPMAN MPF400_1 0x36d0 MPF400 Music Player 1Go +product MPMAN MPF400_2 0x25a8 MPF400 Music Player 2Go + +/* STMicroelectronics products */ +product STMICRO BIOCPU 0x2016 Biometric Coprocessor +product STMICRO COMMUNICATOR 0x7554 USB Communicator + +/* STSN products */ +product STSN STSN0001 0x0001 Internet Access Device + +/* SUN Corporation products */ +product SUNTAC DS96L 0x0003 SUNTAC U-Cable type D2 +product SUNTAC PS64P1 0x0005 SUNTAC U-Cable type P1 +product SUNTAC VS10U 0x0009 SUNTAC Slipper U +product SUNTAC IS96U 0x000a SUNTAC Ir-Trinity +product SUNTAC AS64LX 0x000b SUNTAC U-Cable type A3 +product SUNTAC AS144L4 0x0011 SUNTAC U-Cable type A4 + +/* Sun Microsystems products */ +product SUN KEYBOARD_TYPE_6 0x0005 Type 6 USB keyboard +product SUN KEYBOARD_TYPE_7 0x00a2 Type 7 USB keyboard +/* XXX The above is a North American PC style keyboard possibly */ +product SUN MOUSE 0x0100 Type 6 USB mouse +product SUN KBD_HUB 0x100e Kbd Hub + +/* Super Top products */ +product SUPERTOP IDE 0x6600 USB-IDE + +/* Syntech products */ +product SYNTECH CPT8001C 0x0001 CPT-8001C Barcode scanner +product SYNTECH CYPHERLAB100 0x1000 CipherLab USB Barcode Scanner + +/* Teclast products */ +product TECLAST TLC300 0x3203 USB Media Player + +/* Supra products */ +product DIAMOND2 SUPRAEXPRESS56K 0x07da Supra Express 56K modem +product DIAMOND2 SUPRA2890 0x0b4a SupraMax 2890 56K Modem +product DIAMOND2 RIO600USB 0x5001 Rio 600 USB +product DIAMOND2 RIO800USB 0x5002 Rio 800 USB + +/* Surecom Technology products */ +product SURECOM EP9001G2A 0x11f2 EP-9001-G rev 2A +product SURECOM RT2570 0x11f3 RT2570 +product SURECOM RT2573 0x31f3 RT2573 + +/* Sweex products */ +product SWEEX ZD1211 0x1809 ZD1211 +product SWEEX2 LW153 0x0153 LW153 +product SWEEX2 LW303 0x0302 LW303 +product SWEEX2 LW313 0x0313 LW313 + +/* System TALKS, Inc. */ +product SYSTEMTALKS SGCX2UL 0x1920 SGC-X2UL + +/* Tapwave products */ +product TAPWAVE ZODIAC 0x0100 Zodiac + +/* Taugagreining products */ +product TAUGA CAMERAMATE 0x0005 CameraMate (DPCM_USB) + +/* TCTMobile products */ +product TCTMOBILE X060S 0x0000 X060S 3G modem +product TCTMOBILE X080S 0xf000 X080S 3G modem + +/* TDK products */ +product TDK UPA9664 0x0115 USB-PDC Adapter UPA9664 +product TDK UCA1464 0x0116 USB-cdmaOne Adapter UCA1464 +product TDK UHA6400 0x0117 USB-PHS Adapter UHA6400 +product TDK UPA6400 0x0118 USB-PHS Adapter UPA6400 +product TDK BT_DONGLE 0x0309 Bluetooth USB dongle + +/* TEAC products */ +product TEAC FD05PUB 0x0000 FD-05PUB floppy + +/* Tekram Technology products */ +product TEKRAM QUICKWLAN 0x1630 QuickWLAN +product TEKRAM ZD1211_1 0x5630 ZD1211 +product TEKRAM ZD1211_2 0x6630 ZD1211 + +/* Telex Communications products */ +product TELEX MIC1 0x0001 Enhanced USB Microphone + +/* Telit products */ +product TELIT UC864E 0x1003 UC864E 3G modem +product TELIT UC864G 0x1004 UC864G 3G modem + +/* Ten X Technology, Inc. */ +product TENX UAUDIO0 0xf211 USB audio headset + +/* Texas Intel products */ +product TI UTUSB41 0x1446 UT-USB41 hub +product TI TUSB2046 0x2046 TUSB2046 hub + +/* Thrustmaster products */ +product THRUST FUSION_PAD 0xa0a3 Fusion Digital Gamepad + +/* TLayTech products */ +product TLAYTECH TEU800 0x1682 TEU800 3G modem + +/* Topre Corporation products */ +product TOPRE HHKB 0x0100 HHKB Professional + +/* Toshiba Corporation products */ +product TOSHIBA POCKETPC_E740 0x0706 PocketPC e740 +product TOSHIBA RT3070 0x0a07 RT3070 +product TOSHIBA G450 0x0d45 G450 modem +product TOSHIBA HSDPA 0x1302 G450 modem + +/* Trek Technology products */ +product TREK THUMBDRIVE 0x1111 ThumbDrive +product TREK MEMKEY 0x8888 IBM USB Memory Key +product TREK THUMBDRIVE_8MB 0x9988 ThumbDrive_8MB + +/* Tripp-Lite products */ +product TRIPPLITE U209 0x2008 Serial + +/* Trumpion products */ +product TRUMPION T33520 0x1001 T33520 USB Flash Card Controller +product TRUMPION C3310 0x1100 Comotron C3310 MP3 player +product TRUMPION MP3 0x1200 MP3 player + +/* TwinMOS */ +product TWINMOS G240 0xa006 G240 +product TWINMOS MDIV 0x1325 Memory Disk IV + +/* Ubiquam products */ +product UBIQUAM UALL 0x3100 CDMA 1xRTT USB Modem (U-100/105/200/300/520) + +/* Ultima products */ +product ULTIMA 1200UBPLUS 0x4002 1200 UB Plus scanner + +/* UMAX products */ +product UMAX ASTRA1236U 0x0002 Astra 1236U Scanner +product UMAX ASTRA1220U 0x0010 Astra 1220U Scanner +product UMAX ASTRA2000U 0x0030 Astra 2000U Scanner +product UMAX ASTRA2100U 0x0130 Astra 2100U Scanner +product UMAX ASTRA2200U 0x0230 Astra 2200U Scanner +product UMAX ASTRA3400 0x0060 Astra 3400 Scanner + +/* U-MEDIA Communications products */ +product UMEDIA TEW444UBEU 0x3006 TEW-444UB EU +product UMEDIA TEW444UBEU_NF 0x3007 TEW-444UB EU (no firmware) +product UMEDIA TEW429UB_A 0x300a TEW-429UB_A +product UMEDIA TEW429UB 0x300b TEW-429UB +product UMEDIA TEW429UBC1 0x300d TEW-429UB C1 +product UMEDIA RT2870_1 0x300e RT2870 +product UMEDIA ALL0298V2 0x3204 ALL0298 v2 +product UMEDIA AR5523_2 0x3205 AR5523 +product UMEDIA AR5523_2_NF 0x3206 AR5523 (no firmware) + +/* Universal Access products */ +product UNIACCESS PANACHE 0x0101 Panache Surf USB ISDN Adapter + +/* USI products */ +product USI MC60 0x10c5 MC60 Serial + +/* U.S. Robotics products */ +product USR USR5422 0x0118 USR5422 WLAN +product USR USR5423 0x0121 USR5423 WLAN + +/* VIA Technologies products */ +product VIA USB2IDEBRIDGE 0x6204 USB 2.0 IDE Bridge + +/* Vaisala products */ +product VAISALA CABLE 0x0200 USB Interface cable + +/* Vertex products */ +product VERTEX VW110L 0x0100 Vertex VW110L modem + +/* VidzMedia products */ +product VIDZMEDIA MONSTERTV 0x4fb1 MonsterTV P2H + +/* Vision products */ +product VISION VC6452V002 0x0002 CPiA Camera + +/* Visioneer products */ +product VISIONEER 7600 0x0211 OneTouch 7600 +product VISIONEER 5300 0x0221 OneTouch 5300 +product VISIONEER 3000 0x0224 Scanport 3000 +product VISIONEER 6100 0x0231 OneTouch 6100 +product VISIONEER 6200 0x0311 OneTouch 6200 +product VISIONEER 8100 0x0321 OneTouch 8100 +product VISIONEER 8600 0x0331 OneTouch 8600 + +/* Vivitar products */ +product VIVITAR 35XX 0x0003 Vivicam 35Xx + +/* VTech products */ +product VTECH RT2570 0x3012 RT2570 +product VTECH ZD1211B 0x3014 ZD1211B + +/* Wacom products */ +product WACOM CT0405U 0x0000 CT-0405-U Tablet +product WACOM GRAPHIRE 0x0010 Graphire +product WACOM GRAPHIRE3_4X5 0x0013 Graphire 3 4x5 +product WACOM INTUOSA5 0x0021 Intuos A5 +product WACOM GD0912U 0x0022 Intuos 9x12 Graphics Tablet + +/* WAGO Kontakttechnik GmbH products */ +product WAGO SERVICECABLE 0x07a6 USB Service Cable 750-923 + +/* WaveSense products */ +product WAVESENSE JAZZ 0xaaaa Jazz blood glucose meter + +/* WCH products */ +product WCH CH341SER 0x5523 CH341/CH340 USB-Serial Bridge +product WCH2 CH341SER 0x7523 CH341/CH340 USB-Serial Bridge + +/* Western Digital products */ +product WESTERN COMBO 0x0200 Firewire USB Combo +product WESTERN EXTHDD 0x0400 External HDD +product WESTERN HUB 0x0500 USB HUB +product WESTERN MYBOOK 0x0901 MyBook External HDD +product WESTERN MYPASSWORD 0x0704 MyPassword External HDD + +/* WIENER Plein & Baus GmbH products */ +product WIENERPLEINBAUS PL512 0x0010 PL512 PSU +product WIENERPLEINBAUS RCM 0x0011 RCM Remote Control +product WIENERPLEINBAUS MPOD 0x0012 MPOD PSU +product WIENERPLEINBAUS CML 0x0015 CML Data Logger + +/* Windbond Electronics */ +product WINBOND UH104 0x5518 4-port USB Hub + +/* WinMaxGroup products */ +product WINMAXGROUP FLASH64MC 0x6660 USB Flash Disk 64M-C + +/* Wistron NeWeb products */ +product WISTRONNEWEB UR045G 0x0427 PrismGT USB 2.0 WLAN +product WISTRONNEWEB UR055G 0x0711 UR055G +product WISTRONNEWEB AR5523_1 0x0826 AR5523 +product WISTRONNEWEB AR5523_1_NF 0x0827 AR5523 (no firmware) +product WISTRONNEWEB AR5523_2 0x082a AR5523 +product WISTRONNEWEB AR5523_2_NF 0x0829 AR5523 (no firmware) + +/* Xerox products */ +product XEROX WCM15 0xffef WorkCenter M15 + +/* Xirlink products */ +product XIRLINK PCCAM 0x8080 IBM PC Camera + +/* Xyratex products */ +product XYRATEX PRISM_GT_1 0x2000 PrismGT USB 2.0 WLAN +product XYRATEX PRISM_GT_2 0x2002 PrismGT USB 2.0 WLAN + +/* Yamaha products */ +product YAMAHA UX256 0x1000 UX256 MIDI I/F +product YAMAHA UX96 0x1008 UX96 MIDI I/F +product YAMAHA RPU200 0x3104 RP-U200 +product YAMAHA RTA54I 0x4000 NetVolante RTA54i Broadband&ISDN Router +product YAMAHA RTW65B 0x4001 NetVolante RTW65b Broadband Wireless Router +product YAMAHA RTW65I 0x4002 NetVolante RTW65i Broadband&ISDN Wireless Router +product YAMAHA RTA55I 0x4004 NetVolante RTA55i Broadband VoIP Router + +/* Yano products */ +product YANO U640MO 0x0101 U640MO-03 +product YANO FW800HD 0x05fc METALWEAR-HDD + +/* Y.C. Cable products */ +product YCCABLE PL2303 0x0fba PL2303 Serial + +/* Y-E Data products */ +product YEDATA FLASHBUSTERU 0x0000 Flashbuster-U + +/* Yiso Wireless Co. products */ +product YISO C893 0xc893 CDMA 2000 1xEVDO PC Card + +/* Z-Com products */ +product ZCOM M4Y750 0x0001 M4Y-750 +product ZCOM XI725 0x0002 XI-725/726 +product ZCOM XI735 0x0005 XI-735 +product ZCOM XG703A 0x0008 PrismGT USB 2.0 WLAN +product ZCOM ZD1211 0x0011 ZD1211 +product ZCOM AR5523 0x0012 AR5523 +product ZCOM AR5523_NF 0x0013 AR5523 driver (no firmware) +product ZCOM XM142 0x0015 XM-142 +product ZCOM ZD1211B 0x001a ZD1211B +product ZCOM RT2870_1 0x0022 RT2870 +product ZCOM RT2870_2 0x0025 RT2870 + +/* Zinwell products */ +product ZINWELL RT2570 0x0260 RT2570 +product ZINWELL RT2870_1 0x0280 RT2870 +product ZINWELL RT2870_2 0x0282 RT2870 +product ZINWELL RT3072_1 0x0283 RT3072 +product ZINWELL RT3072_2 0x0284 RT3072 +product ZINWELL RT3070 0x5257 RT3070 + +/* Zoom Telephonics, Inc. products */ +product ZOOM 2986L 0x9700 2986L Fax modem + +/* Zoran Microelectronics products */ +product ZORAN EX20DSC 0x4343 Digital Camera EX-20 DSC + +/* Zydas Technology Corporation products */ +product ZYDAS ZD1211 0x1211 ZD1211 WLAN abg +product ZYDAS ZD1211B 0x1215 ZD1211B + +/* ZyXEL Communication Co. products */ +product ZYXEL OMNI56K 0x1500 Omni 56K Plus +product ZYXEL 980N 0x2011 Scorpion-980N keyboard +product ZYXEL ZYAIRG220 0x3401 ZyAIR G-220 +product ZYXEL G200V2 0x3407 G-200 v2 +product ZYXEL AG225H 0x3409 AG-225H +product ZYXEL M202 0x340a M-202 +product ZYXEL G220V2 0x340f G-220 v2 +product ZYXEL G202 0x3410 G-202 +product ZYXEL RT2870_1 0x3416 RT2870 +product ZYXEL RT2870_2 0x341a RT2870 diff --git a/sys/bus/u4b/usbdi.h b/sys/bus/u4b/usbdi.h new file mode 100644 index 0000000000..6446720e86 --- /dev/null +++ b/sys/bus/u4b/usbdi.h @@ -0,0 +1,598 @@ +/*- + * Copyright (c) 2009 Andrew Thompson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _USB_USBDI_H_ +#define _USB_USBDI_H_ + +struct usb_fifo; +struct usb_xfer; +struct usb_device; +struct usb_attach_arg; +struct usb_interface; +struct usb_endpoint; +struct usb_page_cache; +struct usb_page_search; +struct usb_process; +struct usb_proc_msg; +struct usb_mbuf; +struct usb_fs_privdata; +struct mbuf; + +typedef enum { /* keep in sync with usb_errstr_table */ + USB_ERR_NORMAL_COMPLETION = 0, + USB_ERR_PENDING_REQUESTS, /* 1 */ + USB_ERR_NOT_STARTED, /* 2 */ + USB_ERR_INVAL, /* 3 */ + USB_ERR_NOMEM, /* 4 */ + USB_ERR_CANCELLED, /* 5 */ + USB_ERR_BAD_ADDRESS, /* 6 */ + USB_ERR_BAD_BUFSIZE, /* 7 */ + USB_ERR_BAD_FLAG, /* 8 */ + USB_ERR_NO_CALLBACK, /* 9 */ + USB_ERR_IN_USE, /* 10 */ + USB_ERR_NO_ADDR, /* 11 */ + USB_ERR_NO_PIPE, /* 12 */ + USB_ERR_ZERO_NFRAMES, /* 13 */ + USB_ERR_ZERO_MAXP, /* 14 */ + USB_ERR_SET_ADDR_FAILED, /* 15 */ + USB_ERR_NO_POWER, /* 16 */ + USB_ERR_TOO_DEEP, /* 17 */ + USB_ERR_IOERROR, /* 18 */ + USB_ERR_NOT_CONFIGURED, /* 19 */ + USB_ERR_TIMEOUT, /* 20 */ + USB_ERR_SHORT_XFER, /* 21 */ + USB_ERR_STALLED, /* 22 */ + USB_ERR_INTERRUPTED, /* 23 */ + USB_ERR_DMA_LOAD_FAILED, /* 24 */ + USB_ERR_BAD_CONTEXT, /* 25 */ + USB_ERR_NO_ROOT_HUB, /* 26 */ + USB_ERR_NO_INTR_THREAD, /* 27 */ + USB_ERR_NOT_LOCKED, /* 28 */ + USB_ERR_MAX +} usb_error_t; + +/* + * Flags for transfers + */ +#define USB_FORCE_SHORT_XFER 0x0001 /* force a short transmit last */ +#define USB_SHORT_XFER_OK 0x0004 /* allow short reads */ +#define USB_DELAY_STATUS_STAGE 0x0010 /* insert delay before STATUS stage */ +#define USB_USER_DATA_PTR 0x0020 /* internal flag */ +#define USB_MULTI_SHORT_OK 0x0040 /* allow multiple short frames */ +#define USB_MANUAL_STATUS 0x0080 /* manual ctrl status */ + +#define USB_NO_TIMEOUT 0 +#define USB_DEFAULT_TIMEOUT 5000 /* 5000 ms = 5 seconds */ + +#if defined(_KERNEL) +/* typedefs */ + +typedef void (usb_callback_t)(struct usb_xfer *, usb_error_t); +typedef void (usb_proc_callback_t)(struct usb_proc_msg *); +typedef usb_error_t (usb_handle_req_t)(struct usb_device *, + struct usb_device_request *, const void **, uint16_t *); + +typedef int (usb_fifo_open_t)(struct usb_fifo *fifo, int fflags); +typedef void (usb_fifo_close_t)(struct usb_fifo *fifo, int fflags); +typedef int (usb_fifo_ioctl_t)(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags); +typedef void (usb_fifo_cmd_t)(struct usb_fifo *fifo); +typedef void (usb_fifo_filter_t)(struct usb_fifo *fifo, struct usb_mbuf *m); + + +/* USB events */ +#include +typedef void (*usb_dev_configured_t)(void *, struct usb_device *, + struct usb_attach_arg *); +EVENTHANDLER_DECLARE(usb_dev_configured, usb_dev_configured_t); + +/* + * The following macros are used used to convert milliseconds into + * HZ. We use 1024 instead of 1000 milliseconds per second to save a + * full division. + */ +#define USB_MS_HZ 1024 + +#define USB_MS_TO_TICKS(ms) \ + (((uint32_t)((((uint32_t)(ms)) * ((uint32_t)(hz))) + USB_MS_HZ - 1)) / USB_MS_HZ) + +/* + * Common queue structure for USB transfers. + */ +struct usb_xfer_queue { + TAILQ_HEAD(, usb_xfer) head; + struct usb_xfer *curr; /* current USB transfer processed */ + void (*command) (struct usb_xfer_queue *pq); + uint8_t recurse_1:1; + uint8_t recurse_2:1; +}; + +/* + * The following structure defines an USB endpoint + * USB endpoint. + */ +struct usb_endpoint { + struct usb_xfer_queue endpoint_q; /* queue of USB transfers */ + + struct usb_endpoint_descriptor *edesc; + struct usb_endpoint_ss_comp_descriptor *ecomp; + struct usb_pipe_methods *methods; /* set by HC driver */ + + uint16_t isoc_next; + + uint8_t toggle_next:1; /* next data toggle value */ + uint8_t is_stalled:1; /* set if endpoint is stalled */ + uint8_t is_synced:1; /* set if we a synchronised */ + uint8_t unused:5; + uint8_t iface_index; /* not used by "default endpoint" */ + + uint8_t refcount_alloc; /* allocation refcount */ + uint8_t refcount_bw; /* bandwidth refcount */ +#define USB_EP_REF_MAX 0x3f + + /* High-Speed resource allocation (valid if "refcount_bw" > 0) */ + + uint8_t usb_smask; /* USB start mask */ + uint8_t usb_cmask; /* USB complete mask */ + uint8_t usb_uframe; /* USB microframe */ +}; + +/* + * The following structure defines an USB interface. + */ +struct usb_interface { + struct usb_interface_descriptor *idesc; + device_t subdev; + uint8_t alt_index; + uint8_t parent_iface_index; + + /* Linux compat */ + struct usb_host_interface *altsetting; + struct usb_host_interface *cur_altsetting; + struct usb_device *linux_udev; + void *bsd_priv_sc; /* device specific information */ + char *pnpinfo; /* additional PnP-info for this interface */ + uint8_t num_altsetting; /* number of alternate settings */ + uint8_t bsd_iface_index; +}; + +/* + * The following structure defines a set of USB transfer flags. + */ +struct usb_xfer_flags { + uint8_t force_short_xfer:1; /* force a short transmit transfer + * last */ + uint8_t short_xfer_ok:1; /* allow short receive transfers */ + uint8_t short_frames_ok:1; /* allow short frames */ + uint8_t pipe_bof:1; /* block pipe on failure */ + uint8_t proxy_buffer:1; /* makes buffer size a factor of + * "max_frame_size" */ + uint8_t ext_buffer:1; /* uses external DMA buffer */ + uint8_t manual_status:1; /* non automatic status stage on + * control transfers */ + uint8_t no_pipe_ok:1; /* set if "USB_ERR_NO_PIPE" error can + * be ignored */ + uint8_t stall_pipe:1; /* set if the endpoint belonging to + * this USB transfer should be stalled + * before starting this transfer! */ + uint8_t pre_scale_frames:1; /* "usb_config->frames" is + * assumed to give the + * buffering time in + * milliseconds and is + * converted into the nearest + * number of frames when the + * USB transfer is setup. This + * option only has effect for + * ISOCHRONOUS transfers. + */ +}; + +/* + * The following structure define an USB configuration, that basically + * is used when setting up an USB transfer. + */ +struct usb_config { + usb_callback_t *callback; /* USB transfer callback */ + usb_frlength_t bufsize; /* total pipe buffer size in bytes */ + usb_frcount_t frames; /* maximum number of USB frames */ + usb_timeout_t interval; /* interval in milliseconds */ +#define USB_DEFAULT_INTERVAL 0 + usb_timeout_t timeout; /* transfer timeout in milliseconds */ + struct usb_xfer_flags flags; /* transfer flags */ + enum usb_hc_mode usb_mode; /* host or device mode */ + uint8_t type; /* pipe type */ + uint8_t endpoint; /* pipe number */ + uint8_t direction; /* pipe direction */ + uint8_t ep_index; /* pipe index match to use */ + uint8_t if_index; /* "ifaces" index to use */ +}; + +/* + * Use these macro when defining USB device ID arrays if you want to + * have your driver module automatically loaded in host, device or + * both modes respectivly: + */ +#define STRUCT_USB_HOST_ID \ + struct usb_device_id __section("usb_host_id") +#define STRUCT_USB_DEVICE_ID \ + struct usb_device_id __section("usb_device_id") +#define STRUCT_USB_DUAL_ID \ + struct usb_device_id __section("usb_dual_id") + +/* + * The following structure is used when looking up an USB driver for + * an USB device. It is inspired by the Linux structure called + * "usb_device_id". + */ +struct usb_device_id { + + /* Hook for driver specific information */ + unsigned long driver_info; + + /* Used for product specific matches; the BCD range is inclusive */ + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice_lo; + uint16_t bcdDevice_hi; + + /* Used for device class matches */ + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + + /* Used for interface class matches */ + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + + /* Select which fields to match against */ + uint8_t match_flag_vendor:1; + uint8_t match_flag_product:1; + uint8_t match_flag_dev_lo:1; + uint8_t match_flag_dev_hi:1; + + uint8_t match_flag_dev_class:1; + uint8_t match_flag_dev_subclass:1; + uint8_t match_flag_dev_protocol:1; + uint8_t match_flag_int_class:1; + + uint8_t match_flag_int_subclass:1; + uint8_t match_flag_int_protocol:1; + uint8_t match_flag_unused:6; + +#if USB_HAVE_COMPAT_LINUX + /* which fields to match against */ + uint16_t match_flags; +#define USB_DEVICE_ID_MATCH_VENDOR 0x0001 +#define USB_DEVICE_ID_MATCH_PRODUCT 0x0002 +#define USB_DEVICE_ID_MATCH_DEV_LO 0x0004 +#define USB_DEVICE_ID_MATCH_DEV_HI 0x0008 +#define USB_DEVICE_ID_MATCH_DEV_CLASS 0x0010 +#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS 0x0020 +#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL 0x0040 +#define USB_DEVICE_ID_MATCH_INT_CLASS 0x0080 +#define USB_DEVICE_ID_MATCH_INT_SUBCLASS 0x0100 +#define USB_DEVICE_ID_MATCH_INT_PROTOCOL 0x0200 +#endif +} __aligned(32); + +/* check that the size of the structure above is correct */ +extern char usb_device_id_assert[(sizeof(struct usb_device_id) == 32) ? 1 : -1]; + +#define USB_VENDOR(vend) \ + .match_flag_vendor = 1, .idVendor = (vend) + +#define USB_PRODUCT(prod) \ + .match_flag_product = 1, .idProduct = (prod) + +#define USB_VP(vend,prod) \ + USB_VENDOR(vend), USB_PRODUCT(prod) + +#define USB_VPI(vend,prod,info) \ + USB_VENDOR(vend), USB_PRODUCT(prod), USB_DRIVER_INFO(info) + +#define USB_DEV_BCD_GTEQ(lo) /* greater than or equal */ \ + .match_flag_dev_lo = 1, .bcdDevice_lo = (lo) + +#define USB_DEV_BCD_LTEQ(hi) /* less than or equal */ \ + .match_flag_dev_hi = 1, .bcdDevice_hi = (hi) + +#define USB_DEV_CLASS(dc) \ + .match_flag_dev_class = 1, .bDeviceClass = (dc) + +#define USB_DEV_SUBCLASS(dsc) \ + .match_flag_dev_subclass = 1, .bDeviceSubClass = (dsc) + +#define USB_DEV_PROTOCOL(dp) \ + .match_flag_dev_protocol = 1, .bDeviceProtocol = (dp) + +#define USB_IFACE_CLASS(ic) \ + .match_flag_int_class = 1, .bInterfaceClass = (ic) + +#define USB_IFACE_SUBCLASS(isc) \ + .match_flag_int_subclass = 1, .bInterfaceSubClass = (isc) + +#define USB_IFACE_PROTOCOL(ip) \ + .match_flag_int_protocol = 1, .bInterfaceProtocol = (ip) + +#define USB_IF_CSI(class,subclass,info) \ + USB_IFACE_CLASS(class), USB_IFACE_SUBCLASS(subclass), USB_DRIVER_INFO(info) + +#define USB_DRIVER_INFO(n) \ + .driver_info = (n) + +#define USB_GET_DRIVER_INFO(did) \ + (did)->driver_info + +/* + * The following structure keeps information that is used to match + * against an array of "usb_device_id" elements. + */ +struct usbd_lookup_info { + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t bIfaceIndex; + uint8_t bIfaceNum; + uint8_t bConfigIndex; + uint8_t bConfigNum; +}; + +/* Structure used by probe and attach */ + +struct usb_attach_arg { + struct usbd_lookup_info info; + device_t temp_dev; /* for internal use */ + unsigned long driver_info; /* for internal use */ + void *driver_ivar; + struct usb_device *device; /* current device */ + struct usb_interface *iface; /* current interface */ + enum usb_hc_mode usb_mode; /* host or device mode */ + uint8_t port; + uint8_t dev_state; +#define UAA_DEV_READY 0 +#define UAA_DEV_DISABLED 1 +#define UAA_DEV_EJECTING 2 +}; + +/* + * The following is a wrapper for the callout structure to ease + * porting the code to other platforms. + */ +struct usb_callout { + struct callout co; +}; +#define usb_callout_init_mtx(c,m,f) callout_init_mtx(&(c)->co,m,f) +#define usb_callout_reset(c,t,f,d) callout_reset(&(c)->co,t,f,d) +#define usb_callout_stop(c) callout_stop(&(c)->co) +#define usb_callout_drain(c) callout_drain(&(c)->co) +#define usb_callout_pending(c) callout_pending(&(c)->co) + +/* USB transfer states */ + +#define USB_ST_SETUP 0 +#define USB_ST_TRANSFERRED 1 +#define USB_ST_ERROR 2 + +/* USB handle request states */ +#define USB_HR_NOT_COMPLETE 0 +#define USB_HR_COMPLETE_OK 1 +#define USB_HR_COMPLETE_ERR 2 + +/* + * The following macro will return the current state of an USB + * transfer like defined by the "USB_ST_XXX" enums. + */ +#define USB_GET_STATE(xfer) (usbd_xfer_state(xfer)) + +/* + * The following structure defines the USB process message header. + */ +struct usb_proc_msg { + TAILQ_ENTRY(usb_proc_msg) pm_qentry; + usb_proc_callback_t *pm_callback; + usb_size_t pm_num; +}; + +#define USB_FIFO_TX 0 +#define USB_FIFO_RX 1 + +/* + * Locking note for the following functions. All the + * "usb_fifo_cmd_t" and "usb_fifo_filter_t" functions are called + * locked. The others are called unlocked. + */ +struct usb_fifo_methods { + usb_fifo_open_t *f_open; + usb_fifo_close_t *f_close; + usb_fifo_ioctl_t *f_ioctl; + /* + * NOTE: The post-ioctl callback is called after the USB reference + * gets locked in the IOCTL handler: + */ + usb_fifo_ioctl_t *f_ioctl_post; + usb_fifo_cmd_t *f_start_read; + usb_fifo_cmd_t *f_stop_read; + usb_fifo_cmd_t *f_start_write; + usb_fifo_cmd_t *f_stop_write; + usb_fifo_filter_t *f_filter_read; + usb_fifo_filter_t *f_filter_write; + const char *basename[4]; + const char *postfix[4]; +}; + +struct usb_fifo_sc { + struct usb_fifo *fp[2]; + struct usb_fs_privdata *dev; +}; + +const char *usbd_errstr(usb_error_t error); +void *usbd_find_descriptor(struct usb_device *udev, void *id, + uint8_t iface_index, uint8_t type, uint8_t type_mask, + uint8_t subtype, uint8_t subtype_mask); +struct usb_config_descriptor *usbd_get_config_descriptor( + struct usb_device *udev); +struct usb_device_descriptor *usbd_get_device_descriptor( + struct usb_device *udev); +struct usb_interface *usbd_get_iface(struct usb_device *udev, + uint8_t iface_index); +struct usb_interface_descriptor *usbd_get_interface_descriptor( + struct usb_interface *iface); +struct usb_endpoint *usbd_get_endpoint(struct usb_device *udev, uint8_t iface_index, + const struct usb_config *setup); +struct usb_endpoint *usbd_get_ep_by_addr(struct usb_device *udev, uint8_t ea_val); +usb_error_t usbd_interface_count(struct usb_device *udev, uint8_t *count); +enum usb_hc_mode usbd_get_mode(struct usb_device *udev); +enum usb_dev_speed usbd_get_speed(struct usb_device *udev); +void device_set_usb_desc(device_t dev); +void usb_pause_mtx(struct mtx *mtx, int _ticks); +usb_error_t usbd_set_pnpinfo(struct usb_device *udev, + uint8_t iface_index, const char *pnpinfo); +usb_error_t usbd_add_dynamic_quirk(struct usb_device *udev, + uint16_t quirk); + +const struct usb_device_id *usbd_lookup_id_by_info( + const struct usb_device_id *id, usb_size_t sizeof_id, + const struct usbd_lookup_info *info); +int usbd_lookup_id_by_uaa(const struct usb_device_id *id, + usb_size_t sizeof_id, struct usb_attach_arg *uaa); + +usb_error_t usbd_do_request_flags(struct usb_device *udev, struct mtx *mtx, + struct usb_device_request *req, void *data, uint16_t flags, + uint16_t *actlen, usb_timeout_t timeout); +#define usbd_do_request(u,m,r,d) \ + usbd_do_request_flags(u,m,r,d,0,NULL,USB_DEFAULT_TIMEOUT) + +uint8_t usbd_clear_stall_callback(struct usb_xfer *xfer1, + struct usb_xfer *xfer2); +uint8_t usbd_get_interface_altindex(struct usb_interface *iface); +usb_error_t usbd_set_alt_interface_index(struct usb_device *udev, + uint8_t iface_index, uint8_t alt_index); +uint32_t usbd_get_isoc_fps(struct usb_device *udev); +usb_error_t usbd_transfer_setup(struct usb_device *udev, + const uint8_t *ifaces, struct usb_xfer **pxfer, + const struct usb_config *setup_start, uint16_t n_setup, + void *priv_sc, struct mtx *priv_mtx); +void usbd_transfer_submit(struct usb_xfer *xfer); +void usbd_transfer_clear_stall(struct usb_xfer *xfer); +void usbd_transfer_drain(struct usb_xfer *xfer); +uint8_t usbd_transfer_pending(struct usb_xfer *xfer); +void usbd_transfer_start(struct usb_xfer *xfer); +void usbd_transfer_stop(struct usb_xfer *xfer); +void usbd_transfer_unsetup(struct usb_xfer **pxfer, uint16_t n_setup); +void usbd_transfer_poll(struct usb_xfer **ppxfer, uint16_t max); +void usbd_set_parent_iface(struct usb_device *udev, uint8_t iface_index, + uint8_t parent_index); +uint8_t usbd_get_bus_index(struct usb_device *udev); +uint8_t usbd_get_device_index(struct usb_device *udev); +void usbd_set_power_mode(struct usb_device *udev, uint8_t power_mode); +uint8_t usbd_filter_power_mode(struct usb_device *udev, uint8_t power_mode); +uint8_t usbd_device_attached(struct usb_device *udev); + +usb_frlength_t + usbd_xfer_old_frame_length(struct usb_xfer *xfer, usb_frcount_t frindex); +void usbd_xfer_status(struct usb_xfer *xfer, int *actlen, int *sumlen, + int *aframes, int *nframes); +struct usb_page_cache *usbd_xfer_get_frame(struct usb_xfer *xfer, + usb_frcount_t frindex); +void *usbd_xfer_softc(struct usb_xfer *xfer); +void *usbd_xfer_get_priv(struct usb_xfer *xfer); +void usbd_xfer_set_priv(struct usb_xfer *xfer, void *); +void usbd_xfer_set_interval(struct usb_xfer *xfer, int); +uint8_t usbd_xfer_state(struct usb_xfer *xfer); +void usbd_xfer_set_frame_data(struct usb_xfer *xfer, usb_frcount_t frindex, + void *ptr, usb_frlength_t len); +void usbd_xfer_frame_data(struct usb_xfer *xfer, usb_frcount_t frindex, + void **ptr, int *len); +void usbd_xfer_set_frame_offset(struct usb_xfer *xfer, usb_frlength_t offset, + usb_frcount_t frindex); +usb_frlength_t usbd_xfer_max_len(struct usb_xfer *xfer); +usb_frlength_t usbd_xfer_max_framelen(struct usb_xfer *xfer); +usb_frcount_t usbd_xfer_max_frames(struct usb_xfer *xfer); +uint8_t usbd_xfer_get_fps_shift(struct usb_xfer *xfer); +usb_frlength_t usbd_xfer_frame_len(struct usb_xfer *xfer, + usb_frcount_t frindex); +void usbd_xfer_set_frame_len(struct usb_xfer *xfer, usb_frcount_t frindex, + usb_frlength_t len); +void usbd_xfer_set_timeout(struct usb_xfer *xfer, int timeout); +void usbd_xfer_set_frames(struct usb_xfer *xfer, usb_frcount_t n); +void usbd_xfer_set_stall(struct usb_xfer *xfer); +int usbd_xfer_is_stalled(struct usb_xfer *xfer); +void usbd_xfer_set_flag(struct usb_xfer *xfer, int flag); +void usbd_xfer_clr_flag(struct usb_xfer *xfer, int flag); +uint16_t usbd_xfer_get_timestamp(struct usb_xfer *xfer); + +void usbd_copy_in(struct usb_page_cache *cache, usb_frlength_t offset, + const void *ptr, usb_frlength_t len); +int usbd_copy_in_user(struct usb_page_cache *cache, usb_frlength_t offset, + const void *ptr, usb_frlength_t len); +void usbd_copy_out(struct usb_page_cache *cache, usb_frlength_t offset, + void *ptr, usb_frlength_t len); +int usbd_copy_out_user(struct usb_page_cache *cache, usb_frlength_t offset, + void *ptr, usb_frlength_t len); +void usbd_get_page(struct usb_page_cache *pc, usb_frlength_t offset, + struct usb_page_search *res); +void usbd_m_copy_in(struct usb_page_cache *cache, usb_frlength_t dst_offset, + struct mbuf *m, usb_size_t src_offset, usb_frlength_t src_len); +void usbd_frame_zero(struct usb_page_cache *cache, usb_frlength_t offset, + usb_frlength_t len); +void usbd_start_re_enumerate(struct usb_device *udev); + +int usb_fifo_attach(struct usb_device *udev, void *priv_sc, + struct mtx *priv_mtx, struct usb_fifo_methods *pm, + struct usb_fifo_sc *f_sc, uint16_t unit, uint16_t subunit, + uint8_t iface_index, uid_t uid, gid_t gid, int mode); +void usb_fifo_detach(struct usb_fifo_sc *f_sc); +int usb_fifo_alloc_buffer(struct usb_fifo *f, uint32_t bufsize, + uint16_t nbuf); +void usb_fifo_free_buffer(struct usb_fifo *f); +uint32_t usb_fifo_put_bytes_max(struct usb_fifo *fifo); +void usb_fifo_put_data(struct usb_fifo *fifo, struct usb_page_cache *pc, + usb_frlength_t offset, usb_frlength_t len, uint8_t what); +void usb_fifo_put_data_linear(struct usb_fifo *fifo, void *ptr, + usb_size_t len, uint8_t what); +uint8_t usb_fifo_put_data_buffer(struct usb_fifo *f, void *ptr, usb_size_t len); +void usb_fifo_put_data_error(struct usb_fifo *fifo); +uint8_t usb_fifo_get_data(struct usb_fifo *fifo, struct usb_page_cache *pc, + usb_frlength_t offset, usb_frlength_t len, usb_frlength_t *actlen, + uint8_t what); +uint8_t usb_fifo_get_data_linear(struct usb_fifo *fifo, void *ptr, + usb_size_t len, usb_size_t *actlen, uint8_t what); +uint8_t usb_fifo_get_data_buffer(struct usb_fifo *f, void **pptr, + usb_size_t *plen); +void usb_fifo_reset(struct usb_fifo *f); +void usb_fifo_wakeup(struct usb_fifo *f); +void usb_fifo_get_data_error(struct usb_fifo *fifo); +void *usb_fifo_softc(struct usb_fifo *fifo); +void usb_fifo_set_close_zlp(struct usb_fifo *, uint8_t); +void usb_fifo_set_write_defrag(struct usb_fifo *, uint8_t); +void usb_fifo_free(struct usb_fifo *f); +#endif /* _KERNEL */ +#endif /* _USB_USBDI_H_ */ diff --git a/sys/bus/u4b/usbdi_util.h b/sys/bus/u4b/usbdi_util.h new file mode 100644 index 0000000000..1e450f8f4f --- /dev/null +++ b/sys/bus/u4b/usbdi_util.h @@ -0,0 +1,91 @@ +/*- + * Copyright (c) 2009 Andrew Thompson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _USB_USBDI_UTIL_H_ +#define _USB_USBDI_UTIL_H_ + +struct cv; + +/* structures */ + +struct usb_idesc_parse_state { + struct usb_descriptor *desc; + uint8_t iface_index; /* current interface index */ + uint8_t iface_no_last; + uint8_t iface_index_alt; /* current alternate setting */ +}; + +/* prototypes */ + +usb_error_t usbd_do_request_proc(struct usb_device *udev, struct usb_process *pproc, + struct usb_device_request *req, void *data, uint16_t flags, + uint16_t *actlen, usb_timeout_t timeout); + +struct usb_descriptor *usb_desc_foreach(struct usb_config_descriptor *cd, + struct usb_descriptor *desc); +struct usb_interface_descriptor *usb_idesc_foreach( + struct usb_config_descriptor *cd, + struct usb_idesc_parse_state *ps); +struct usb_endpoint_descriptor *usb_edesc_foreach( + struct usb_config_descriptor *cd, + struct usb_endpoint_descriptor *ped); +struct usb_endpoint_ss_comp_descriptor *usb_ed_comp_foreach( + struct usb_config_descriptor *cd, + struct usb_endpoint_ss_comp_descriptor *ped); +uint8_t usbd_get_no_descriptors(struct usb_config_descriptor *cd, + uint8_t type); +uint8_t usbd_get_no_alts(struct usb_config_descriptor *cd, + struct usb_interface_descriptor *id); + +usb_error_t usbd_req_get_report(struct usb_device *udev, struct mtx *mtx, + void *data, uint16_t len, uint8_t iface_index, uint8_t type, + uint8_t id); +usb_error_t usbd_req_get_report_descriptor(struct usb_device *udev, + struct mtx *mtx, void *d, uint16_t size, + uint8_t iface_index); +usb_error_t usbd_req_get_string_any(struct usb_device *udev, struct mtx *mtx, + char *buf, uint16_t len, uint8_t string_index); +usb_error_t usbd_req_get_string_desc(struct usb_device *udev, struct mtx *mtx, + void *sdesc, uint16_t max_len, uint16_t lang_id, + uint8_t string_index); +usb_error_t usbd_req_set_config(struct usb_device *udev, struct mtx *mtx, + uint8_t conf); +usb_error_t usbd_req_set_alt_interface_no(struct usb_device *udev, + struct mtx *mtx, uint8_t iface_index, uint8_t alt_no); +usb_error_t usbd_req_set_idle(struct usb_device *udev, struct mtx *mtx, + uint8_t iface_index, uint8_t duration, uint8_t id); +usb_error_t usbd_req_set_protocol(struct usb_device *udev, struct mtx *mtx, + uint8_t iface_index, uint16_t report); +usb_error_t usbd_req_set_report(struct usb_device *udev, struct mtx *mtx, + void *data, uint16_t len, uint8_t iface_index, + uint8_t type, uint8_t id); + +/* The following functions will not return NULL strings. */ + +const char *usb_get_manufacturer(struct usb_device *); +const char *usb_get_product(struct usb_device *); +const char *usb_get_serial(struct usb_device *); + +#endif /* _USB_USBDI_UTIL_H_ */ diff --git a/sys/bus/u4b/usbhid.h b/sys/bus/u4b/usbhid.h new file mode 100644 index 0000000000..4816e87076 --- /dev/null +++ b/sys/bus/u4b/usbhid.h @@ -0,0 +1,246 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _USB_HID_H_ +#define _USB_HID_H_ + +#include + +#define UR_GET_HID_DESCRIPTOR 0x06 +#define UDESC_HID 0x21 +#define UDESC_REPORT 0x22 +#define UDESC_PHYSICAL 0x23 +#define UR_SET_HID_DESCRIPTOR 0x07 +#define UR_GET_REPORT 0x01 +#define UR_SET_REPORT 0x09 +#define UR_GET_IDLE 0x02 +#define UR_SET_IDLE 0x0a +#define UR_GET_PROTOCOL 0x03 +#define UR_SET_PROTOCOL 0x0b + +struct usb_hid_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord bcdHID; + uByte bCountryCode; + uByte bNumDescriptors; + struct { + uByte bDescriptorType; + uWord wDescriptorLength; + } descrs[1]; +} __packed; + +#define USB_HID_DESCRIPTOR_SIZE(n) (9+((n)*3)) + +/* Usage pages */ +#define HUP_UNDEFINED 0x0000 +#define HUP_GENERIC_DESKTOP 0x0001 +#define HUP_SIMULATION 0x0002 +#define HUP_VR_CONTROLS 0x0003 +#define HUP_SPORTS_CONTROLS 0x0004 +#define HUP_GAMING_CONTROLS 0x0005 +#define HUP_KEYBOARD 0x0007 +#define HUP_LEDS 0x0008 +#define HUP_BUTTON 0x0009 +#define HUP_ORDINALS 0x000a +#define HUP_TELEPHONY 0x000b +#define HUP_CONSUMER 0x000c +#define HUP_DIGITIZERS 0x000d +#define HUP_PHYSICAL_IFACE 0x000e +#define HUP_UNICODE 0x0010 +#define HUP_ALPHANUM_DISPLAY 0x0014 +#define HUP_MONITOR 0x0080 +#define HUP_MONITOR_ENUM_VAL 0x0081 +#define HUP_VESA_VC 0x0082 +#define HUP_VESA_CMD 0x0083 +#define HUP_POWER 0x0084 +#define HUP_BATTERY_SYSTEM 0x0085 +#define HUP_BARCODE_SCANNER 0x008b +#define HUP_SCALE 0x008c +#define HUP_CAMERA_CONTROL 0x0090 +#define HUP_ARCADE 0x0091 +#define HUP_MICROSOFT 0xff00 + +/* Usages, generic desktop */ +#define HUG_POINTER 0x0001 +#define HUG_MOUSE 0x0002 +#define HUG_JOYSTICK 0x0004 +#define HUG_GAME_PAD 0x0005 +#define HUG_KEYBOARD 0x0006 +#define HUG_KEYPAD 0x0007 +#define HUG_X 0x0030 +#define HUG_Y 0x0031 +#define HUG_Z 0x0032 +#define HUG_RX 0x0033 +#define HUG_RY 0x0034 +#define HUG_RZ 0x0035 +#define HUG_SLIDER 0x0036 +#define HUG_DIAL 0x0037 +#define HUG_WHEEL 0x0038 +#define HUG_HAT_SWITCH 0x0039 +#define HUG_COUNTED_BUFFER 0x003a +#define HUG_BYTE_COUNT 0x003b +#define HUG_MOTION_WAKEUP 0x003c +#define HUG_VX 0x0040 +#define HUG_VY 0x0041 +#define HUG_VZ 0x0042 +#define HUG_VBRX 0x0043 +#define HUG_VBRY 0x0044 +#define HUG_VBRZ 0x0045 +#define HUG_VNO 0x0046 +#define HUG_TWHEEL 0x0048 /* M$ Wireless Intellimouse Wheel */ +#define HUG_SYSTEM_CONTROL 0x0080 +#define HUG_SYSTEM_POWER_DOWN 0x0081 +#define HUG_SYSTEM_SLEEP 0x0082 +#define HUG_SYSTEM_WAKEUP 0x0083 +#define HUG_SYSTEM_CONTEXT_MENU 0x0084 +#define HUG_SYSTEM_MAIN_MENU 0x0085 +#define HUG_SYSTEM_APP_MENU 0x0086 +#define HUG_SYSTEM_MENU_HELP 0x0087 +#define HUG_SYSTEM_MENU_EXIT 0x0088 +#define HUG_SYSTEM_MENU_SELECT 0x0089 +#define HUG_SYSTEM_MENU_RIGHT 0x008a +#define HUG_SYSTEM_MENU_LEFT 0x008b +#define HUG_SYSTEM_MENU_UP 0x008c +#define HUG_SYSTEM_MENU_DOWN 0x008d +#define HUG_APPLE_EJECT 0x00b8 + +/* Usages Digitizers */ +#define HUD_UNDEFINED 0x0000 +#define HUD_TIP_PRESSURE 0x0030 +#define HUD_BARREL_PRESSURE 0x0031 +#define HUD_IN_RANGE 0x0032 +#define HUD_TOUCH 0x0033 +#define HUD_UNTOUCH 0x0034 +#define HUD_TAP 0x0035 +#define HUD_QUALITY 0x0036 +#define HUD_DATA_VALID 0x0037 +#define HUD_TRANSDUCER_INDEX 0x0038 +#define HUD_TABLET_FKEYS 0x0039 +#define HUD_PROGRAM_CHANGE_KEYS 0x003a +#define HUD_BATTERY_STRENGTH 0x003b +#define HUD_INVERT 0x003c +#define HUD_X_TILT 0x003d +#define HUD_Y_TILT 0x003e +#define HUD_AZIMUTH 0x003f +#define HUD_ALTITUDE 0x0040 +#define HUD_TWIST 0x0041 +#define HUD_TIP_SWITCH 0x0042 +#define HUD_SEC_TIP_SWITCH 0x0043 +#define HUD_BARREL_SWITCH 0x0044 +#define HUD_ERASER 0x0045 +#define HUD_TABLET_PICK 0x0046 + +/* Usages, Consumer */ +#define HUC_AC_PAN 0x0238 + +#define HID_USAGE2(p,u) (((p) << 16) | (u)) + +#define UHID_INPUT_REPORT 0x01 +#define UHID_OUTPUT_REPORT 0x02 +#define UHID_FEATURE_REPORT 0x03 + +/* Bits in the input/output/feature items */ +#define HIO_CONST 0x001 +#define HIO_VARIABLE 0x002 +#define HIO_RELATIVE 0x004 +#define HIO_WRAP 0x008 +#define HIO_NONLINEAR 0x010 +#define HIO_NOPREF 0x020 +#define HIO_NULLSTATE 0x040 +#define HIO_VOLATILE 0x080 +#define HIO_BUFBYTES 0x100 + +#ifdef _KERNEL +struct usb_config_descriptor; + +enum hid_kind { + hid_input, hid_output, hid_feature, hid_collection, hid_endcollection +}; + +struct hid_location { + uint32_t size; + uint32_t count; + uint32_t pos; +}; + +struct hid_item { + /* Global */ + int32_t _usage_page; + int32_t logical_minimum; + int32_t logical_maximum; + int32_t physical_minimum; + int32_t physical_maximum; + int32_t unit_exponent; + int32_t unit; + int32_t report_ID; + /* Local */ + int32_t usage; + int32_t usage_minimum; + int32_t usage_maximum; + int32_t designator_index; + int32_t designator_minimum; + int32_t designator_maximum; + int32_t string_index; + int32_t string_minimum; + int32_t string_maximum; + int32_t set_delimiter; + /* Misc */ + int32_t collection; + int collevel; + enum hid_kind kind; + uint32_t flags; + /* Location */ + struct hid_location loc; +}; + +/* prototypes from "usb_hid.c" */ + +struct hid_data *hid_start_parse(const void *d, usb_size_t len, int kindset); +void hid_end_parse(struct hid_data *s); +int hid_get_item(struct hid_data *s, struct hid_item *h); +int hid_report_size(const void *buf, usb_size_t len, enum hid_kind k, + uint8_t *id); +int hid_locate(const void *desc, usb_size_t size, uint32_t usage, + enum hid_kind kind, uint8_t index, struct hid_location *loc, + uint32_t *flags, uint8_t *id); +int32_t hid_get_data(const uint8_t *buf, usb_size_t len, + struct hid_location *loc); +uint32_t hid_get_data_unsigned(const uint8_t *buf, usb_size_t len, + struct hid_location *loc); +void hid_put_data_unsigned(uint8_t *buf, usb_size_t len, + struct hid_location *loc, unsigned int value); +int hid_is_collection(const void *desc, usb_size_t size, uint32_t usage); +struct usb_hid_descriptor *hid_get_descriptor_from_usb( + struct usb_config_descriptor *cd, + struct usb_interface_descriptor *id); +usb_error_t usbd_req_get_hid_desc(struct usb_device *udev, struct mtx *mtx, + void **descp, uint16_t *sizep, struct malloc_type *mem, + uint8_t iface_index); +#endif /* _KERNEL */ +#endif /* _USB_HID_H_ */ diff --git a/sys/bus/u4b/wlan/if_rum.c b/sys/bus/u4b/wlan/if_rum.c new file mode 100644 index 0000000000..a809819276 --- /dev/null +++ b/sys/bus/u4b/wlan/if_rum.c @@ -0,0 +1,2377 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005-2007 Damien Bergamini + * Copyright (c) 2006 Niall O'Higgins + * Copyright (c) 2007-2008 Hans Petter Selasky + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Ralink Technology RT2501USB/RT2601USB chipset driver + * http://www.ralinktech.com.tw/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef INET +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR rum_debug +#include + +#include +#include +#include + +#ifdef USB_DEBUG +static int rum_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, rum, CTLFLAG_RW, 0, "USB rum"); +SYSCTL_INT(_hw_usb_rum, OID_AUTO, debug, CTLFLAG_RW, &rum_debug, 0, + "Debug level"); +#endif + +static const STRUCT_USB_HOST_ID rum_devs[] = { +#define RUM_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } + RUM_DEV(ABOCOM, HWU54DM), + RUM_DEV(ABOCOM, RT2573_2), + RUM_DEV(ABOCOM, RT2573_3), + RUM_DEV(ABOCOM, RT2573_4), + RUM_DEV(ABOCOM, WUG2700), + RUM_DEV(AMIT, CGWLUSB2GO), + RUM_DEV(ASUS, RT2573_1), + RUM_DEV(ASUS, RT2573_2), + RUM_DEV(BELKIN, F5D7050A), + RUM_DEV(BELKIN, F5D9050V3), + RUM_DEV(CISCOLINKSYS, WUSB54GC), + RUM_DEV(CISCOLINKSYS, WUSB54GR), + RUM_DEV(CONCEPTRONIC2, C54RU2), + RUM_DEV(COREGA, CGWLUSB2GL), + RUM_DEV(COREGA, CGWLUSB2GPX), + RUM_DEV(DICKSMITH, CWD854F), + RUM_DEV(DICKSMITH, RT2573), + RUM_DEV(EDIMAX, EW7318USG), + RUM_DEV(DLINK2, DWLG122C1), + RUM_DEV(DLINK2, WUA1340), + RUM_DEV(DLINK2, DWA111), + RUM_DEV(DLINK2, DWA110), + RUM_DEV(GIGABYTE, GNWB01GS), + RUM_DEV(GIGABYTE, GNWI05GS), + RUM_DEV(GIGASET, RT2573), + RUM_DEV(GOODWAY, RT2573), + RUM_DEV(GUILLEMOT, HWGUSB254LB), + RUM_DEV(GUILLEMOT, HWGUSB254V2AP), + RUM_DEV(HUAWEI3COM, WUB320G), + RUM_DEV(MELCO, G54HP), + RUM_DEV(MELCO, SG54HP), + RUM_DEV(MELCO, WLIUCG), + RUM_DEV(MELCO, WLRUCG), + RUM_DEV(MELCO, WLRUCGAOSS), + RUM_DEV(MSI, RT2573_1), + RUM_DEV(MSI, RT2573_2), + RUM_DEV(MSI, RT2573_3), + RUM_DEV(MSI, RT2573_4), + RUM_DEV(NOVATECH, RT2573), + RUM_DEV(PLANEX2, GWUS54HP), + RUM_DEV(PLANEX2, GWUS54MINI2), + RUM_DEV(PLANEX2, GWUSMM), + RUM_DEV(QCOM, RT2573), + RUM_DEV(QCOM, RT2573_2), + RUM_DEV(QCOM, RT2573_3), + RUM_DEV(RALINK, RT2573), + RUM_DEV(RALINK, RT2573_2), + RUM_DEV(RALINK, RT2671), + RUM_DEV(SITECOMEU, WL113R2), + RUM_DEV(SITECOMEU, WL172), + RUM_DEV(SPARKLAN, RT2573), + RUM_DEV(SURECOM, RT2573), +#undef RUM_DEV +}; + +static device_probe_t rum_match; +static device_attach_t rum_attach; +static device_detach_t rum_detach; + +static usb_callback_t rum_bulk_read_callback; +static usb_callback_t rum_bulk_write_callback; + +static usb_error_t rum_do_request(struct rum_softc *sc, + struct usb_device_request *req, void *data); +static struct ieee80211vap *rum_vap_create(struct ieee80211com *, + const char [IFNAMSIZ], int, enum ieee80211_opmode, + int, const uint8_t [IEEE80211_ADDR_LEN], + const uint8_t [IEEE80211_ADDR_LEN]); +static void rum_vap_delete(struct ieee80211vap *); +static void rum_tx_free(struct rum_tx_data *, int); +static void rum_setup_tx_list(struct rum_softc *); +static void rum_unsetup_tx_list(struct rum_softc *); +static int rum_newstate(struct ieee80211vap *, + enum ieee80211_state, int); +static void rum_setup_tx_desc(struct rum_softc *, + struct rum_tx_desc *, uint32_t, uint16_t, int, + int); +static int rum_tx_mgt(struct rum_softc *, struct mbuf *, + struct ieee80211_node *); +static int rum_tx_raw(struct rum_softc *, struct mbuf *, + struct ieee80211_node *, + const struct ieee80211_bpf_params *); +static int rum_tx_data(struct rum_softc *, struct mbuf *, + struct ieee80211_node *); +static void rum_start(struct ifnet *); +static int rum_ioctl(struct ifnet *, u_long, caddr_t); +static void rum_eeprom_read(struct rum_softc *, uint16_t, void *, + int); +static uint32_t rum_read(struct rum_softc *, uint16_t); +static void rum_read_multi(struct rum_softc *, uint16_t, void *, + int); +static usb_error_t rum_write(struct rum_softc *, uint16_t, uint32_t); +static usb_error_t rum_write_multi(struct rum_softc *, uint16_t, void *, + size_t); +static void rum_bbp_write(struct rum_softc *, uint8_t, uint8_t); +static uint8_t rum_bbp_read(struct rum_softc *, uint8_t); +static void rum_rf_write(struct rum_softc *, uint8_t, uint32_t); +static void rum_select_antenna(struct rum_softc *); +static void rum_enable_mrr(struct rum_softc *); +static void rum_set_txpreamble(struct rum_softc *); +static void rum_set_basicrates(struct rum_softc *); +static void rum_select_band(struct rum_softc *, + struct ieee80211_channel *); +static void rum_set_chan(struct rum_softc *, + struct ieee80211_channel *); +static void rum_enable_tsf_sync(struct rum_softc *); +static void rum_enable_tsf(struct rum_softc *); +static void rum_update_slot(struct ifnet *); +static void rum_set_bssid(struct rum_softc *, const uint8_t *); +static void rum_set_macaddr(struct rum_softc *, const uint8_t *); +static void rum_update_mcast(struct ifnet *); +static void rum_update_promisc(struct ifnet *); +static void rum_setpromisc(struct rum_softc *); +static const char *rum_get_rf(int); +static void rum_read_eeprom(struct rum_softc *); +static int rum_bbp_init(struct rum_softc *); +static void rum_init_locked(struct rum_softc *); +static void rum_init(void *); +static void rum_stop(struct rum_softc *); +static void rum_load_microcode(struct rum_softc *, const uint8_t *, + size_t); +static void rum_prepare_beacon(struct rum_softc *, + struct ieee80211vap *); +static int rum_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static void rum_scan_start(struct ieee80211com *); +static void rum_scan_end(struct ieee80211com *); +static void rum_set_channel(struct ieee80211com *); +static int rum_get_rssi(struct rum_softc *, uint8_t); +static void rum_ratectl_start(struct rum_softc *, + struct ieee80211_node *); +static void rum_ratectl_timeout(void *); +static void rum_ratectl_task(void *, int); +static int rum_pause(struct rum_softc *, int); + +static const struct { + uint32_t reg; + uint32_t val; +} rum_def_mac[] = { + { RT2573_TXRX_CSR0, 0x025fb032 }, + { RT2573_TXRX_CSR1, 0x9eaa9eaf }, + { RT2573_TXRX_CSR2, 0x8a8b8c8d }, + { RT2573_TXRX_CSR3, 0x00858687 }, + { RT2573_TXRX_CSR7, 0x2e31353b }, + { RT2573_TXRX_CSR8, 0x2a2a2a2c }, + { RT2573_TXRX_CSR15, 0x0000000f }, + { RT2573_MAC_CSR6, 0x00000fff }, + { RT2573_MAC_CSR8, 0x016c030a }, + { RT2573_MAC_CSR10, 0x00000718 }, + { RT2573_MAC_CSR12, 0x00000004 }, + { RT2573_MAC_CSR13, 0x00007f00 }, + { RT2573_SEC_CSR0, 0x00000000 }, + { RT2573_SEC_CSR1, 0x00000000 }, + { RT2573_SEC_CSR5, 0x00000000 }, + { RT2573_PHY_CSR1, 0x000023b0 }, + { RT2573_PHY_CSR5, 0x00040a06 }, + { RT2573_PHY_CSR6, 0x00080606 }, + { RT2573_PHY_CSR7, 0x00000408 }, + { RT2573_AIFSN_CSR, 0x00002273 }, + { RT2573_CWMIN_CSR, 0x00002344 }, + { RT2573_CWMAX_CSR, 0x000034aa } +}; + +static const struct { + uint8_t reg; + uint8_t val; +} rum_def_bbp[] = { + { 3, 0x80 }, + { 15, 0x30 }, + { 17, 0x20 }, + { 21, 0xc8 }, + { 22, 0x38 }, + { 23, 0x06 }, + { 24, 0xfe }, + { 25, 0x0a }, + { 26, 0x0d }, + { 32, 0x0b }, + { 34, 0x12 }, + { 37, 0x07 }, + { 39, 0xf8 }, + { 41, 0x60 }, + { 53, 0x10 }, + { 54, 0x18 }, + { 60, 0x10 }, + { 61, 0x04 }, + { 62, 0x04 }, + { 75, 0xfe }, + { 86, 0xfe }, + { 88, 0xfe }, + { 90, 0x0f }, + { 99, 0x00 }, + { 102, 0x16 }, + { 107, 0x04 } +}; + +static const struct rfprog { + uint8_t chan; + uint32_t r1, r2, r3, r4; +} rum_rf5226[] = { + { 1, 0x00b03, 0x001e1, 0x1a014, 0x30282 }, + { 2, 0x00b03, 0x001e1, 0x1a014, 0x30287 }, + { 3, 0x00b03, 0x001e2, 0x1a014, 0x30282 }, + { 4, 0x00b03, 0x001e2, 0x1a014, 0x30287 }, + { 5, 0x00b03, 0x001e3, 0x1a014, 0x30282 }, + { 6, 0x00b03, 0x001e3, 0x1a014, 0x30287 }, + { 7, 0x00b03, 0x001e4, 0x1a014, 0x30282 }, + { 8, 0x00b03, 0x001e4, 0x1a014, 0x30287 }, + { 9, 0x00b03, 0x001e5, 0x1a014, 0x30282 }, + { 10, 0x00b03, 0x001e5, 0x1a014, 0x30287 }, + { 11, 0x00b03, 0x001e6, 0x1a014, 0x30282 }, + { 12, 0x00b03, 0x001e6, 0x1a014, 0x30287 }, + { 13, 0x00b03, 0x001e7, 0x1a014, 0x30282 }, + { 14, 0x00b03, 0x001e8, 0x1a014, 0x30284 }, + + { 34, 0x00b03, 0x20266, 0x36014, 0x30282 }, + { 38, 0x00b03, 0x20267, 0x36014, 0x30284 }, + { 42, 0x00b03, 0x20268, 0x36014, 0x30286 }, + { 46, 0x00b03, 0x20269, 0x36014, 0x30288 }, + + { 36, 0x00b03, 0x00266, 0x26014, 0x30288 }, + { 40, 0x00b03, 0x00268, 0x26014, 0x30280 }, + { 44, 0x00b03, 0x00269, 0x26014, 0x30282 }, + { 48, 0x00b03, 0x0026a, 0x26014, 0x30284 }, + { 52, 0x00b03, 0x0026b, 0x26014, 0x30286 }, + { 56, 0x00b03, 0x0026c, 0x26014, 0x30288 }, + { 60, 0x00b03, 0x0026e, 0x26014, 0x30280 }, + { 64, 0x00b03, 0x0026f, 0x26014, 0x30282 }, + + { 100, 0x00b03, 0x0028a, 0x2e014, 0x30280 }, + { 104, 0x00b03, 0x0028b, 0x2e014, 0x30282 }, + { 108, 0x00b03, 0x0028c, 0x2e014, 0x30284 }, + { 112, 0x00b03, 0x0028d, 0x2e014, 0x30286 }, + { 116, 0x00b03, 0x0028e, 0x2e014, 0x30288 }, + { 120, 0x00b03, 0x002a0, 0x2e014, 0x30280 }, + { 124, 0x00b03, 0x002a1, 0x2e014, 0x30282 }, + { 128, 0x00b03, 0x002a2, 0x2e014, 0x30284 }, + { 132, 0x00b03, 0x002a3, 0x2e014, 0x30286 }, + { 136, 0x00b03, 0x002a4, 0x2e014, 0x30288 }, + { 140, 0x00b03, 0x002a6, 0x2e014, 0x30280 }, + + { 149, 0x00b03, 0x002a8, 0x2e014, 0x30287 }, + { 153, 0x00b03, 0x002a9, 0x2e014, 0x30289 }, + { 157, 0x00b03, 0x002ab, 0x2e014, 0x30281 }, + { 161, 0x00b03, 0x002ac, 0x2e014, 0x30283 }, + { 165, 0x00b03, 0x002ad, 0x2e014, 0x30285 } +}, rum_rf5225[] = { + { 1, 0x00b33, 0x011e1, 0x1a014, 0x30282 }, + { 2, 0x00b33, 0x011e1, 0x1a014, 0x30287 }, + { 3, 0x00b33, 0x011e2, 0x1a014, 0x30282 }, + { 4, 0x00b33, 0x011e2, 0x1a014, 0x30287 }, + { 5, 0x00b33, 0x011e3, 0x1a014, 0x30282 }, + { 6, 0x00b33, 0x011e3, 0x1a014, 0x30287 }, + { 7, 0x00b33, 0x011e4, 0x1a014, 0x30282 }, + { 8, 0x00b33, 0x011e4, 0x1a014, 0x30287 }, + { 9, 0x00b33, 0x011e5, 0x1a014, 0x30282 }, + { 10, 0x00b33, 0x011e5, 0x1a014, 0x30287 }, + { 11, 0x00b33, 0x011e6, 0x1a014, 0x30282 }, + { 12, 0x00b33, 0x011e6, 0x1a014, 0x30287 }, + { 13, 0x00b33, 0x011e7, 0x1a014, 0x30282 }, + { 14, 0x00b33, 0x011e8, 0x1a014, 0x30284 }, + + { 34, 0x00b33, 0x01266, 0x26014, 0x30282 }, + { 38, 0x00b33, 0x01267, 0x26014, 0x30284 }, + { 42, 0x00b33, 0x01268, 0x26014, 0x30286 }, + { 46, 0x00b33, 0x01269, 0x26014, 0x30288 }, + + { 36, 0x00b33, 0x01266, 0x26014, 0x30288 }, + { 40, 0x00b33, 0x01268, 0x26014, 0x30280 }, + { 44, 0x00b33, 0x01269, 0x26014, 0x30282 }, + { 48, 0x00b33, 0x0126a, 0x26014, 0x30284 }, + { 52, 0x00b33, 0x0126b, 0x26014, 0x30286 }, + { 56, 0x00b33, 0x0126c, 0x26014, 0x30288 }, + { 60, 0x00b33, 0x0126e, 0x26014, 0x30280 }, + { 64, 0x00b33, 0x0126f, 0x26014, 0x30282 }, + + { 100, 0x00b33, 0x0128a, 0x2e014, 0x30280 }, + { 104, 0x00b33, 0x0128b, 0x2e014, 0x30282 }, + { 108, 0x00b33, 0x0128c, 0x2e014, 0x30284 }, + { 112, 0x00b33, 0x0128d, 0x2e014, 0x30286 }, + { 116, 0x00b33, 0x0128e, 0x2e014, 0x30288 }, + { 120, 0x00b33, 0x012a0, 0x2e014, 0x30280 }, + { 124, 0x00b33, 0x012a1, 0x2e014, 0x30282 }, + { 128, 0x00b33, 0x012a2, 0x2e014, 0x30284 }, + { 132, 0x00b33, 0x012a3, 0x2e014, 0x30286 }, + { 136, 0x00b33, 0x012a4, 0x2e014, 0x30288 }, + { 140, 0x00b33, 0x012a6, 0x2e014, 0x30280 }, + + { 149, 0x00b33, 0x012a8, 0x2e014, 0x30287 }, + { 153, 0x00b33, 0x012a9, 0x2e014, 0x30289 }, + { 157, 0x00b33, 0x012ab, 0x2e014, 0x30281 }, + { 161, 0x00b33, 0x012ac, 0x2e014, 0x30283 }, + { 165, 0x00b33, 0x012ad, 0x2e014, 0x30285 } +}; + +static const struct usb_config rum_config[RUM_N_TRANSFER] = { + [RUM_BULK_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = (MCLBYTES + RT2573_TX_DESC_SIZE + 8), + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = rum_bulk_write_callback, + .timeout = 5000, /* ms */ + }, + [RUM_BULK_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = (MCLBYTES + RT2573_RX_DESC_SIZE), + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = rum_bulk_read_callback, + }, +}; + +static int +rum_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != 0) + return (ENXIO); + if (uaa->info.bIfaceIndex != RT2573_IFACE_INDEX) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(rum_devs, sizeof(rum_devs), uaa)); +} + +static int +rum_attach(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + struct rum_softc *sc = device_get_softc(self); + struct ieee80211com *ic; + struct ifnet *ifp; + uint8_t iface_index, bands; + uint32_t tmp; + int error, ntries; + + device_set_usb_desc(self); + sc->sc_udev = uaa->device; + sc->sc_dev = self; + + mtx_init(&sc->sc_mtx, device_get_nameunit(self), + MTX_NETWORK_LOCK, MTX_DEF); + + iface_index = RT2573_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, rum_config, RUM_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + device_printf(self, "could not allocate USB transfers, " + "err=%s\n", usbd_errstr(error)); + goto detach; + } + + RUM_LOCK(sc); + /* retrieve RT2573 rev. no */ + for (ntries = 0; ntries < 100; ntries++) { + if ((tmp = rum_read(sc, RT2573_MAC_CSR0)) != 0) + break; + if (rum_pause(sc, hz / 100)) + break; + } + if (ntries == 100) { + device_printf(sc->sc_dev, "timeout waiting for chip to settle\n"); + RUM_UNLOCK(sc); + goto detach; + } + + /* retrieve MAC address and various other things from EEPROM */ + rum_read_eeprom(sc); + + device_printf(sc->sc_dev, "MAC/BBP RT2573 (rev 0x%05x), RF %s\n", + tmp, rum_get_rf(sc->rf_rev)); + + rum_load_microcode(sc, rt2573_ucode, sizeof(rt2573_ucode)); + RUM_UNLOCK(sc); + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(sc->sc_dev, "can not if_alloc()\n"); + goto detach; + } + ic = ifp->if_l2com; + + ifp->if_softc = sc; + if_initname(ifp, "rum", device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_init = rum_init; + ifp->if_ioctl = rum_ioctl; + ifp->if_start = rum_start; + IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); + ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; + IFQ_SET_READY(&ifp->if_snd); + + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA /* station mode supported */ + | IEEE80211_C_IBSS /* IBSS mode supported */ + | IEEE80211_C_MONITOR /* monitor mode supported */ + | IEEE80211_C_HOSTAP /* HostAp mode supported */ + | IEEE80211_C_TXPMGT /* tx power management */ + | IEEE80211_C_SHPREAMBLE /* short preamble supported */ + | IEEE80211_C_SHSLOT /* short slot time supported */ + | IEEE80211_C_BGSCAN /* bg scanning supported */ + | IEEE80211_C_WPA /* 802.11i */ + ; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + if (sc->rf_rev == RT2573_RF_5225 || sc->rf_rev == RT2573_RF_5226) + setbit(&bands, IEEE80211_MODE_11A); + ieee80211_init_channels(ic, NULL, &bands); + + ieee80211_ifattach(ic, sc->sc_bssid); + ic->ic_update_promisc = rum_update_promisc; + ic->ic_raw_xmit = rum_raw_xmit; + ic->ic_scan_start = rum_scan_start; + ic->ic_scan_end = rum_scan_end; + ic->ic_set_channel = rum_set_channel; + + ic->ic_vap_create = rum_vap_create; + ic->ic_vap_delete = rum_vap_delete; + ic->ic_update_mcast = rum_update_mcast; + + ieee80211_radiotap_attach(ic, + &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), + RT2573_TX_RADIOTAP_PRESENT, + &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), + RT2573_RX_RADIOTAP_PRESENT); + + if (bootverbose) + ieee80211_announce(ic); + + return (0); + +detach: + rum_detach(self); + return (ENXIO); /* failure */ +} + +static int +rum_detach(device_t self) +{ + struct rum_softc *sc = device_get_softc(self); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic; + + /* stop all USB transfers */ + usbd_transfer_unsetup(sc->sc_xfer, RUM_N_TRANSFER); + + /* free TX list, if any */ + RUM_LOCK(sc); + rum_unsetup_tx_list(sc); + RUM_UNLOCK(sc); + + if (ifp) { + ic = ifp->if_l2com; + ieee80211_ifdetach(ic); + if_free(ifp); + } + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static usb_error_t +rum_do_request(struct rum_softc *sc, + struct usb_device_request *req, void *data) +{ + usb_error_t err; + int ntries = 10; + + while (ntries--) { + err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, + req, data, 0, NULL, 250 /* ms */); + if (err == 0) + break; + + DPRINTFN(1, "Control request failed, %s (retrying)\n", + usbd_errstr(err)); + if (rum_pause(sc, hz / 100)) + break; + } + return (err); +} + +static struct ieee80211vap * +rum_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, + enum ieee80211_opmode opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct rum_softc *sc = ic->ic_ifp->if_softc; + struct rum_vap *rvp; + struct ieee80211vap *vap; + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return NULL; + rvp = (struct rum_vap *) malloc(sizeof(struct rum_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (rvp == NULL) + return NULL; + vap = &rvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + rvp->newstate = vap->iv_newstate; + vap->iv_newstate = rum_newstate; + + usb_callout_init_mtx(&rvp->ratectl_ch, &sc->sc_mtx, 0); + TASK_INIT(&rvp->ratectl_task, 0, rum_ratectl_task, rvp); + ieee80211_ratectl_init(vap); + ieee80211_ratectl_setinterval(vap, 1000 /* 1 sec */); + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status); + ic->ic_opmode = opmode; + return vap; +} + +static void +rum_vap_delete(struct ieee80211vap *vap) +{ + struct rum_vap *rvp = RUM_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + + usb_callout_drain(&rvp->ratectl_ch); + ieee80211_draintask(ic, &rvp->ratectl_task); + ieee80211_ratectl_deinit(vap); + ieee80211_vap_detach(vap); + free(rvp, M_80211_VAP); +} + +static void +rum_tx_free(struct rum_tx_data *data, int txerr) +{ + struct rum_softc *sc = data->sc; + + if (data->m != NULL) { + if (data->m->m_flags & M_TXCB) + ieee80211_process_callback(data->ni, data->m, + txerr ? ETIMEDOUT : 0); + m_freem(data->m); + data->m = NULL; + + ieee80211_free_node(data->ni); + data->ni = NULL; + } + STAILQ_INSERT_TAIL(&sc->tx_free, data, next); + sc->tx_nfree++; +} + +static void +rum_setup_tx_list(struct rum_softc *sc) +{ + struct rum_tx_data *data; + int i; + + sc->tx_nfree = 0; + STAILQ_INIT(&sc->tx_q); + STAILQ_INIT(&sc->tx_free); + + for (i = 0; i < RUM_TX_LIST_COUNT; i++) { + data = &sc->tx_data[i]; + + data->sc = sc; + STAILQ_INSERT_TAIL(&sc->tx_free, data, next); + sc->tx_nfree++; + } +} + +static void +rum_unsetup_tx_list(struct rum_softc *sc) +{ + struct rum_tx_data *data; + int i; + + /* make sure any subsequent use of the queues will fail */ + sc->tx_nfree = 0; + STAILQ_INIT(&sc->tx_q); + STAILQ_INIT(&sc->tx_free); + + /* free up all node references and mbufs */ + for (i = 0; i < RUM_TX_LIST_COUNT; i++) { + data = &sc->tx_data[i]; + + if (data->m != NULL) { + m_freem(data->m); + data->m = NULL; + } + if (data->ni != NULL) { + ieee80211_free_node(data->ni); + data->ni = NULL; + } + } +} + +static int +rum_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct rum_vap *rvp = RUM_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + struct rum_softc *sc = ic->ic_ifp->if_softc; + const struct ieee80211_txparam *tp; + enum ieee80211_state ostate; + struct ieee80211_node *ni; + uint32_t tmp; + + ostate = vap->iv_state; + DPRINTF("%s -> %s\n", + ieee80211_state_name[ostate], + ieee80211_state_name[nstate]); + + IEEE80211_UNLOCK(ic); + RUM_LOCK(sc); + usb_callout_stop(&rvp->ratectl_ch); + + switch (nstate) { + case IEEE80211_S_INIT: + if (ostate == IEEE80211_S_RUN) { + /* abort TSF synchronization */ + tmp = rum_read(sc, RT2573_TXRX_CSR9); + rum_write(sc, RT2573_TXRX_CSR9, tmp & ~0x00ffffff); + } + break; + + case IEEE80211_S_RUN: + ni = ieee80211_ref_node(vap->iv_bss); + + if (vap->iv_opmode != IEEE80211_M_MONITOR) { + rum_update_slot(ic->ic_ifp); + rum_enable_mrr(sc); + rum_set_txpreamble(sc); + rum_set_basicrates(sc); + IEEE80211_ADDR_COPY(sc->sc_bssid, ni->ni_bssid); + rum_set_bssid(sc, sc->sc_bssid); + } + + if (vap->iv_opmode == IEEE80211_M_HOSTAP || + vap->iv_opmode == IEEE80211_M_IBSS) + rum_prepare_beacon(sc, vap); + + if (vap->iv_opmode != IEEE80211_M_MONITOR) + rum_enable_tsf_sync(sc); + else + rum_enable_tsf(sc); + + /* enable automatic rate adaptation */ + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) + rum_ratectl_start(sc, ni); + ieee80211_free_node(ni); + break; + default: + break; + } + RUM_UNLOCK(sc); + IEEE80211_LOCK(ic); + return (rvp->newstate(vap, nstate, arg)); +} + +static void +rum_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct rum_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211vap *vap; + struct rum_tx_data *data; + struct mbuf *m; + struct usb_page_cache *pc; + unsigned int len; + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete, %d bytes\n", actlen); + + /* free resources */ + data = usbd_xfer_get_priv(xfer); + rum_tx_free(data, 0); + usbd_xfer_set_priv(xfer, NULL); + + ifp->if_opackets++; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + data = STAILQ_FIRST(&sc->tx_q); + if (data) { + STAILQ_REMOVE_HEAD(&sc->tx_q, next); + m = data->m; + + if (m->m_pkthdr.len > (MCLBYTES + RT2573_TX_DESC_SIZE)) { + DPRINTFN(0, "data overflow, %u bytes\n", + m->m_pkthdr.len); + m->m_pkthdr.len = (MCLBYTES + RT2573_TX_DESC_SIZE); + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &data->desc, RT2573_TX_DESC_SIZE); + usbd_m_copy_in(pc, RT2573_TX_DESC_SIZE, m, 0, + m->m_pkthdr.len); + + vap = data->ni->ni_vap; + if (ieee80211_radiotap_active_vap(vap)) { + struct rum_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = data->rate; + tap->wt_antenna = sc->tx_ant; + + ieee80211_radiotap_tx(vap, m); + } + + /* align end on a 4-bytes boundary */ + len = (RT2573_TX_DESC_SIZE + m->m_pkthdr.len + 3) & ~3; + if ((len % 64) == 0) + len += 4; + + DPRINTFN(11, "sending frame len=%u xferlen=%u\n", + m->m_pkthdr.len, len); + + usbd_xfer_set_frame_len(xfer, 0, len); + usbd_xfer_set_priv(xfer, data); + + usbd_transfer_submit(xfer); + } + RUM_UNLOCK(sc); + rum_start(ifp); + RUM_LOCK(sc); + break; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usbd_errstr(error)); + + ifp->if_oerrors++; + data = usbd_xfer_get_priv(xfer); + if (data != NULL) { + rum_tx_free(data, error); + usbd_xfer_set_priv(xfer, NULL); + } + + if (error != USB_ERR_CANCELLED) { + if (error == USB_ERR_TIMEOUT) + device_printf(sc->sc_dev, "device timeout\n"); + + /* + * Try to clear stall first, also if other + * errors occur, hence clearing stall + * introduces a 50 ms delay: + */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +rum_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct rum_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211_node *ni; + struct mbuf *m = NULL; + struct usb_page_cache *pc; + uint32_t flags; + uint8_t rssi = 0; + int len; + + usbd_xfer_status(xfer, &len, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTFN(15, "rx done, actlen=%d\n", len); + + if (len < RT2573_RX_DESC_SIZE + IEEE80211_MIN_LEN) { + DPRINTF("%s: xfer too short %d\n", + device_get_nameunit(sc->sc_dev), len); + ifp->if_ierrors++; + goto tr_setup; + } + + len -= RT2573_RX_DESC_SIZE; + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &sc->sc_rx_desc, RT2573_RX_DESC_SIZE); + + rssi = rum_get_rssi(sc, sc->sc_rx_desc.rssi); + flags = le32toh(sc->sc_rx_desc.flags); + if (flags & RT2573_RX_CRC_ERROR) { + /* + * This should not happen since we did not + * request to receive those frames when we + * filled RUM_TXRX_CSR2: + */ + DPRINTFN(5, "PHY or CRC error\n"); + ifp->if_ierrors++; + goto tr_setup; + } + + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (m == NULL) { + DPRINTF("could not allocate mbuf\n"); + ifp->if_ierrors++; + goto tr_setup; + } + usbd_copy_out(pc, RT2573_RX_DESC_SIZE, + mtod(m, uint8_t *), len); + + /* finalize mbuf */ + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = (flags >> 16) & 0xfff; + + if (ieee80211_radiotap_active(ic)) { + struct rum_rx_radiotap_header *tap = &sc->sc_rxtap; + + /* XXX read tsf */ + tap->wr_flags = 0; + tap->wr_rate = ieee80211_plcp2rate(sc->sc_rx_desc.rate, + (flags & RT2573_RX_OFDM) ? + IEEE80211_T_OFDM : IEEE80211_T_CCK); + tap->wr_antsignal = RT2573_NOISE_FLOOR + rssi; + tap->wr_antnoise = RT2573_NOISE_FLOOR; + tap->wr_antenna = sc->rx_ant; + } + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! That is why we do the + * "ieee80211_input" here, and not some lines up! + */ + RUM_UNLOCK(sc); + if (m) { + ni = ieee80211_find_rxnode(ic, + mtod(m, struct ieee80211_frame_min *)); + if (ni != NULL) { + (void) ieee80211_input(ni, m, rssi, + RT2573_NOISE_FLOOR); + ieee80211_free_node(ni); + } else + (void) ieee80211_input_all(ic, m, rssi, + RT2573_NOISE_FLOOR); + } + if ((ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0 && + !IFQ_IS_EMPTY(&ifp->if_snd)) + rum_start(ifp); + RUM_LOCK(sc); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static uint8_t +rum_plcp_signal(int rate) +{ + switch (rate) { + /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ + case 12: return 0xb; + case 18: return 0xf; + case 24: return 0xa; + case 36: return 0xe; + case 48: return 0x9; + case 72: return 0xd; + case 96: return 0x8; + case 108: return 0xc; + + /* CCK rates (NB: not IEEE std, device-specific) */ + case 2: return 0x0; + case 4: return 0x1; + case 11: return 0x2; + case 22: return 0x3; + } + return 0xff; /* XXX unsupported/unknown rate */ +} + +static void +rum_setup_tx_desc(struct rum_softc *sc, struct rum_tx_desc *desc, + uint32_t flags, uint16_t xflags, int len, int rate) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint16_t plcp_length; + int remainder; + + desc->flags = htole32(flags); + desc->flags |= htole32(RT2573_TX_VALID); + desc->flags |= htole32(len << 16); + + desc->xflags = htole16(xflags); + + desc->wme = htole16(RT2573_QID(0) | RT2573_AIFSN(2) | + RT2573_LOGCWMIN(4) | RT2573_LOGCWMAX(10)); + + /* setup PLCP fields */ + desc->plcp_signal = rum_plcp_signal(rate); + desc->plcp_service = 4; + + len += IEEE80211_CRC_LEN; + if (ieee80211_rate2phytype(ic->ic_rt, rate) == IEEE80211_T_OFDM) { + desc->flags |= htole32(RT2573_TX_OFDM); + + plcp_length = len & 0xfff; + desc->plcp_length_hi = plcp_length >> 6; + desc->plcp_length_lo = plcp_length & 0x3f; + } else { + plcp_length = (16 * len + rate - 1) / rate; + if (rate == 22) { + remainder = (16 * len) % 22; + if (remainder != 0 && remainder < 7) + desc->plcp_service |= RT2573_PLCP_LENGEXT; + } + desc->plcp_length_hi = plcp_length >> 8; + desc->plcp_length_lo = plcp_length & 0xff; + + if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) + desc->plcp_signal |= 0x08; + } +} + +static int +rum_sendprot(struct rum_softc *sc, + const struct mbuf *m, struct ieee80211_node *ni, int prot, int rate) +{ + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_frame *wh; + struct rum_tx_data *data; + struct mbuf *mprot; + int protrate, ackrate, pktlen, flags, isshort; + uint16_t dur; + + RUM_LOCK_ASSERT(sc, MA_OWNED); + KASSERT(prot == IEEE80211_PROT_RTSCTS || prot == IEEE80211_PROT_CTSONLY, + ("protection %d", prot)); + + wh = mtod(m, const struct ieee80211_frame *); + pktlen = m->m_pkthdr.len + IEEE80211_CRC_LEN; + + protrate = ieee80211_ctl_rate(ic->ic_rt, rate); + ackrate = ieee80211_ack_rate(ic->ic_rt, rate); + + isshort = (ic->ic_flags & IEEE80211_F_SHPREAMBLE) != 0; + dur = ieee80211_compute_duration(ic->ic_rt, pktlen, rate, isshort) + + ieee80211_ack_duration(ic->ic_rt, rate, isshort); + flags = RT2573_TX_MORE_FRAG; + if (prot == IEEE80211_PROT_RTSCTS) { + /* NB: CTS is the same size as an ACK */ + dur += ieee80211_ack_duration(ic->ic_rt, rate, isshort); + flags |= RT2573_TX_NEED_ACK; + mprot = ieee80211_alloc_rts(ic, wh->i_addr1, wh->i_addr2, dur); + } else { + mprot = ieee80211_alloc_cts(ic, ni->ni_vap->iv_myaddr, dur); + } + if (mprot == NULL) { + /* XXX stat + msg */ + return (ENOBUFS); + } + data = STAILQ_FIRST(&sc->tx_free); + STAILQ_REMOVE_HEAD(&sc->tx_free, next); + sc->tx_nfree--; + + data->m = mprot; + data->ni = ieee80211_ref_node(ni); + data->rate = protrate; + rum_setup_tx_desc(sc, &data->desc, flags, 0, mprot->m_pkthdr.len, protrate); + + STAILQ_INSERT_TAIL(&sc->tx_q, data, next); + usbd_transfer_start(sc->sc_xfer[RUM_BULK_WR]); + + return 0; +} + +static int +rum_tx_mgt(struct rum_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct rum_tx_data *data; + struct ieee80211_frame *wh; + const struct ieee80211_txparam *tp; + struct ieee80211_key *k; + uint32_t flags = 0; + uint16_t dur; + + RUM_LOCK_ASSERT(sc, MA_OWNED); + + data = STAILQ_FIRST(&sc->tx_free); + STAILQ_REMOVE_HEAD(&sc->tx_free, next); + sc->tx_nfree--; + + wh = mtod(m0, struct ieee80211_frame *); + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + m_freem(m0); + return ENOBUFS; + } + wh = mtod(m0, struct ieee80211_frame *); + } + + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + flags |= RT2573_TX_NEED_ACK; + + dur = ieee80211_ack_duration(ic->ic_rt, tp->mgmtrate, + ic->ic_flags & IEEE80211_F_SHPREAMBLE); + *(uint16_t *)wh->i_dur = htole16(dur); + + /* tell hardware to add timestamp for probe responses */ + if ((wh->i_fc[0] & + (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) == + (IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_RESP)) + flags |= RT2573_TX_TIMESTAMP; + } + + data->m = m0; + data->ni = ni; + data->rate = tp->mgmtrate; + + rum_setup_tx_desc(sc, &data->desc, flags, 0, m0->m_pkthdr.len, tp->mgmtrate); + + DPRINTFN(10, "sending mgt frame len=%d rate=%d\n", + m0->m_pkthdr.len + (int)RT2573_TX_DESC_SIZE, tp->mgmtrate); + + STAILQ_INSERT_TAIL(&sc->tx_q, data, next); + usbd_transfer_start(sc->sc_xfer[RUM_BULK_WR]); + + return (0); +} + +static int +rum_tx_raw(struct rum_softc *sc, struct mbuf *m0, struct ieee80211_node *ni, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct rum_tx_data *data; + uint32_t flags; + int rate, error; + + RUM_LOCK_ASSERT(sc, MA_OWNED); + KASSERT(params != NULL, ("no raw xmit params")); + + rate = params->ibp_rate0; + if (!ieee80211_isratevalid(ic->ic_rt, rate)) { + m_freem(m0); + return EINVAL; + } + flags = 0; + if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0) + flags |= RT2573_TX_NEED_ACK; + if (params->ibp_flags & (IEEE80211_BPF_RTS|IEEE80211_BPF_CTS)) { + error = rum_sendprot(sc, m0, ni, + params->ibp_flags & IEEE80211_BPF_RTS ? + IEEE80211_PROT_RTSCTS : IEEE80211_PROT_CTSONLY, + rate); + if (error || sc->tx_nfree == 0) { + m_freem(m0); + return ENOBUFS; + } + flags |= RT2573_TX_LONG_RETRY | RT2573_TX_IFS_SIFS; + } + + data = STAILQ_FIRST(&sc->tx_free); + STAILQ_REMOVE_HEAD(&sc->tx_free, next); + sc->tx_nfree--; + + data->m = m0; + data->ni = ni; + data->rate = rate; + + /* XXX need to setup descriptor ourself */ + rum_setup_tx_desc(sc, &data->desc, flags, 0, m0->m_pkthdr.len, rate); + + DPRINTFN(10, "sending raw frame len=%u rate=%u\n", + m0->m_pkthdr.len, rate); + + STAILQ_INSERT_TAIL(&sc->tx_q, data, next); + usbd_transfer_start(sc->sc_xfer[RUM_BULK_WR]); + + return 0; +} + +static int +rum_tx_data(struct rum_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct rum_tx_data *data; + struct ieee80211_frame *wh; + const struct ieee80211_txparam *tp; + struct ieee80211_key *k; + uint32_t flags = 0; + uint16_t dur; + int error, rate; + + RUM_LOCK_ASSERT(sc, MA_OWNED); + + wh = mtod(m0, struct ieee80211_frame *); + + tp = &vap->iv_txparms[ieee80211_chan2mode(ni->ni_chan)]; + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) + rate = tp->mcastrate; + else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) + rate = tp->ucastrate; + else + rate = ni->ni_txrate; + + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + m_freem(m0); + return ENOBUFS; + } + + /* packet header may have moved, reset our local pointer */ + wh = mtod(m0, struct ieee80211_frame *); + } + + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + int prot = IEEE80211_PROT_NONE; + if (m0->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) + prot = IEEE80211_PROT_RTSCTS; + else if ((ic->ic_flags & IEEE80211_F_USEPROT) && + ieee80211_rate2phytype(ic->ic_rt, rate) == IEEE80211_T_OFDM) + prot = ic->ic_protmode; + if (prot != IEEE80211_PROT_NONE) { + error = rum_sendprot(sc, m0, ni, prot, rate); + if (error || sc->tx_nfree == 0) { + m_freem(m0); + return ENOBUFS; + } + flags |= RT2573_TX_LONG_RETRY | RT2573_TX_IFS_SIFS; + } + } + + data = STAILQ_FIRST(&sc->tx_free); + STAILQ_REMOVE_HEAD(&sc->tx_free, next); + sc->tx_nfree--; + + data->m = m0; + data->ni = ni; + data->rate = rate; + + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + flags |= RT2573_TX_NEED_ACK; + flags |= RT2573_TX_MORE_FRAG; + + dur = ieee80211_ack_duration(ic->ic_rt, rate, + ic->ic_flags & IEEE80211_F_SHPREAMBLE); + *(uint16_t *)wh->i_dur = htole16(dur); + } + + rum_setup_tx_desc(sc, &data->desc, flags, 0, m0->m_pkthdr.len, rate); + + DPRINTFN(10, "sending frame len=%d rate=%d\n", + m0->m_pkthdr.len + (int)RT2573_TX_DESC_SIZE, rate); + + STAILQ_INSERT_TAIL(&sc->tx_q, data, next); + usbd_transfer_start(sc->sc_xfer[RUM_BULK_WR]); + + return 0; +} + +static void +rum_start(struct ifnet *ifp) +{ + struct rum_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + struct mbuf *m; + + RUM_LOCK(sc); + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + RUM_UNLOCK(sc); + return; + } + for (;;) { + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + if (sc->tx_nfree < RUM_TX_MINFREE) { + IFQ_DRV_PREPEND(&ifp->if_snd, m); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + break; + } + ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; + if (rum_tx_data(sc, m, ni) != 0) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + break; + } + } + RUM_UNLOCK(sc); +} + +static int +rum_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct rum_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *) data; + int error = 0, startall = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + RUM_LOCK(sc); + if (ifp->if_flags & IFF_UP) { + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + rum_init_locked(sc); + startall = 1; + } else + rum_setpromisc(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + rum_stop(sc); + } + RUM_UNLOCK(sc); + if (startall) + ieee80211_start_all(ic); + break; + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + case SIOCGIFADDR: + error = ether_ioctl(ifp, cmd, data); + break; + default: + error = EINVAL; + break; + } + return error; +} + +static void +rum_eeprom_read(struct rum_softc *sc, uint16_t addr, void *buf, int len) +{ + struct usb_device_request req; + usb_error_t error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RT2573_READ_EEPROM; + USETW(req.wValue, 0); + USETW(req.wIndex, addr); + USETW(req.wLength, len); + + error = rum_do_request(sc, &req, buf); + if (error != 0) { + device_printf(sc->sc_dev, "could not read EEPROM: %s\n", + usbd_errstr(error)); + } +} + +static uint32_t +rum_read(struct rum_softc *sc, uint16_t reg) +{ + uint32_t val; + + rum_read_multi(sc, reg, &val, sizeof val); + + return le32toh(val); +} + +static void +rum_read_multi(struct rum_softc *sc, uint16_t reg, void *buf, int len) +{ + struct usb_device_request req; + usb_error_t error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RT2573_READ_MULTI_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, len); + + error = rum_do_request(sc, &req, buf); + if (error != 0) { + device_printf(sc->sc_dev, + "could not multi read MAC register: %s\n", + usbd_errstr(error)); + } +} + +static usb_error_t +rum_write(struct rum_softc *sc, uint16_t reg, uint32_t val) +{ + uint32_t tmp = htole32(val); + + return (rum_write_multi(sc, reg, &tmp, sizeof tmp)); +} + +static usb_error_t +rum_write_multi(struct rum_softc *sc, uint16_t reg, void *buf, size_t len) +{ + struct usb_device_request req; + usb_error_t error; + int offset; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RT2573_WRITE_MULTI_MAC; + USETW(req.wValue, 0); + + /* write at most 64 bytes at a time */ + for (offset = 0; offset < len; offset += 64) { + USETW(req.wIndex, reg + offset); + USETW(req.wLength, MIN(len - offset, 64)); + + error = rum_do_request(sc, &req, (char *)buf + offset); + if (error != 0) { + device_printf(sc->sc_dev, + "could not multi write MAC register: %s\n", + usbd_errstr(error)); + return (error); + } + } + + return (USB_ERR_NORMAL_COMPLETION); +} + +static void +rum_bbp_write(struct rum_softc *sc, uint8_t reg, uint8_t val) +{ + uint32_t tmp; + int ntries; + + DPRINTFN(2, "reg=0x%08x\n", reg); + + for (ntries = 0; ntries < 100; ntries++) { + if (!(rum_read(sc, RT2573_PHY_CSR3) & RT2573_BBP_BUSY)) + break; + if (rum_pause(sc, hz / 100)) + break; + } + if (ntries == 100) { + device_printf(sc->sc_dev, "could not write to BBP\n"); + return; + } + + tmp = RT2573_BBP_BUSY | (reg & 0x7f) << 8 | val; + rum_write(sc, RT2573_PHY_CSR3, tmp); +} + +static uint8_t +rum_bbp_read(struct rum_softc *sc, uint8_t reg) +{ + uint32_t val; + int ntries; + + DPRINTFN(2, "reg=0x%08x\n", reg); + + for (ntries = 0; ntries < 100; ntries++) { + if (!(rum_read(sc, RT2573_PHY_CSR3) & RT2573_BBP_BUSY)) + break; + if (rum_pause(sc, hz / 100)) + break; + } + if (ntries == 100) { + device_printf(sc->sc_dev, "could not read BBP\n"); + return 0; + } + + val = RT2573_BBP_BUSY | RT2573_BBP_READ | reg << 8; + rum_write(sc, RT2573_PHY_CSR3, val); + + for (ntries = 0; ntries < 100; ntries++) { + val = rum_read(sc, RT2573_PHY_CSR3); + if (!(val & RT2573_BBP_BUSY)) + return val & 0xff; + if (rum_pause(sc, hz / 100)) + break; + } + + device_printf(sc->sc_dev, "could not read BBP\n"); + return 0; +} + +static void +rum_rf_write(struct rum_softc *sc, uint8_t reg, uint32_t val) +{ + uint32_t tmp; + int ntries; + + for (ntries = 0; ntries < 100; ntries++) { + if (!(rum_read(sc, RT2573_PHY_CSR4) & RT2573_RF_BUSY)) + break; + if (rum_pause(sc, hz / 100)) + break; + } + if (ntries == 100) { + device_printf(sc->sc_dev, "could not write to RF\n"); + return; + } + + tmp = RT2573_RF_BUSY | RT2573_RF_20BIT | (val & 0xfffff) << 2 | + (reg & 3); + rum_write(sc, RT2573_PHY_CSR4, tmp); + + /* remember last written value in sc */ + sc->rf_regs[reg] = val; + + DPRINTFN(15, "RF R[%u] <- 0x%05x\n", reg & 3, val & 0xfffff); +} + +static void +rum_select_antenna(struct rum_softc *sc) +{ + uint8_t bbp4, bbp77; + uint32_t tmp; + + bbp4 = rum_bbp_read(sc, 4); + bbp77 = rum_bbp_read(sc, 77); + + /* TBD */ + + /* make sure Rx is disabled before switching antenna */ + tmp = rum_read(sc, RT2573_TXRX_CSR0); + rum_write(sc, RT2573_TXRX_CSR0, tmp | RT2573_DISABLE_RX); + + rum_bbp_write(sc, 4, bbp4); + rum_bbp_write(sc, 77, bbp77); + + rum_write(sc, RT2573_TXRX_CSR0, tmp); +} + +/* + * Enable multi-rate retries for frames sent at OFDM rates. + * In 802.11b/g mode, allow fallback to CCK rates. + */ +static void +rum_enable_mrr(struct rum_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t tmp; + + tmp = rum_read(sc, RT2573_TXRX_CSR4); + + tmp &= ~RT2573_MRR_CCK_FALLBACK; + if (!IEEE80211_IS_CHAN_5GHZ(ic->ic_bsschan)) + tmp |= RT2573_MRR_CCK_FALLBACK; + tmp |= RT2573_MRR_ENABLED; + + rum_write(sc, RT2573_TXRX_CSR4, tmp); +} + +static void +rum_set_txpreamble(struct rum_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t tmp; + + tmp = rum_read(sc, RT2573_TXRX_CSR4); + + tmp &= ~RT2573_SHORT_PREAMBLE; + if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) + tmp |= RT2573_SHORT_PREAMBLE; + + rum_write(sc, RT2573_TXRX_CSR4, tmp); +} + +static void +rum_set_basicrates(struct rum_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + /* update basic rate set */ + if (ic->ic_curmode == IEEE80211_MODE_11B) { + /* 11b basic rates: 1, 2Mbps */ + rum_write(sc, RT2573_TXRX_CSR5, 0x3); + } else if (IEEE80211_IS_CHAN_5GHZ(ic->ic_bsschan)) { + /* 11a basic rates: 6, 12, 24Mbps */ + rum_write(sc, RT2573_TXRX_CSR5, 0x150); + } else { + /* 11b/g basic rates: 1, 2, 5.5, 11Mbps */ + rum_write(sc, RT2573_TXRX_CSR5, 0xf); + } +} + +/* + * Reprogram MAC/BBP to switch to a new band. Values taken from the reference + * driver. + */ +static void +rum_select_band(struct rum_softc *sc, struct ieee80211_channel *c) +{ + uint8_t bbp17, bbp35, bbp96, bbp97, bbp98, bbp104; + uint32_t tmp; + + /* update all BBP registers that depend on the band */ + bbp17 = 0x20; bbp96 = 0x48; bbp104 = 0x2c; + bbp35 = 0x50; bbp97 = 0x48; bbp98 = 0x48; + if (IEEE80211_IS_CHAN_5GHZ(c)) { + bbp17 += 0x08; bbp96 += 0x10; bbp104 += 0x0c; + bbp35 += 0x10; bbp97 += 0x10; bbp98 += 0x10; + } + if ((IEEE80211_IS_CHAN_2GHZ(c) && sc->ext_2ghz_lna) || + (IEEE80211_IS_CHAN_5GHZ(c) && sc->ext_5ghz_lna)) { + bbp17 += 0x10; bbp96 += 0x10; bbp104 += 0x10; + } + + sc->bbp17 = bbp17; + rum_bbp_write(sc, 17, bbp17); + rum_bbp_write(sc, 96, bbp96); + rum_bbp_write(sc, 104, bbp104); + + if ((IEEE80211_IS_CHAN_2GHZ(c) && sc->ext_2ghz_lna) || + (IEEE80211_IS_CHAN_5GHZ(c) && sc->ext_5ghz_lna)) { + rum_bbp_write(sc, 75, 0x80); + rum_bbp_write(sc, 86, 0x80); + rum_bbp_write(sc, 88, 0x80); + } + + rum_bbp_write(sc, 35, bbp35); + rum_bbp_write(sc, 97, bbp97); + rum_bbp_write(sc, 98, bbp98); + + tmp = rum_read(sc, RT2573_PHY_CSR0); + tmp &= ~(RT2573_PA_PE_2GHZ | RT2573_PA_PE_5GHZ); + if (IEEE80211_IS_CHAN_2GHZ(c)) + tmp |= RT2573_PA_PE_2GHZ; + else + tmp |= RT2573_PA_PE_5GHZ; + rum_write(sc, RT2573_PHY_CSR0, tmp); +} + +static void +rum_set_chan(struct rum_softc *sc, struct ieee80211_channel *c) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + const struct rfprog *rfprog; + uint8_t bbp3, bbp94 = RT2573_BBPR94_DEFAULT; + int8_t power; + int i, chan; + + chan = ieee80211_chan2ieee(ic, c); + if (chan == 0 || chan == IEEE80211_CHAN_ANY) + return; + + /* select the appropriate RF settings based on what EEPROM says */ + rfprog = (sc->rf_rev == RT2573_RF_5225 || + sc->rf_rev == RT2573_RF_2527) ? rum_rf5225 : rum_rf5226; + + /* find the settings for this channel (we know it exists) */ + for (i = 0; rfprog[i].chan != chan; i++); + + power = sc->txpow[i]; + if (power < 0) { + bbp94 += power; + power = 0; + } else if (power > 31) { + bbp94 += power - 31; + power = 31; + } + + /* + * If we are switching from the 2GHz band to the 5GHz band or + * vice-versa, BBP registers need to be reprogrammed. + */ + if (c->ic_flags != ic->ic_curchan->ic_flags) { + rum_select_band(sc, c); + rum_select_antenna(sc); + } + ic->ic_curchan = c; + + rum_rf_write(sc, RT2573_RF1, rfprog[i].r1); + rum_rf_write(sc, RT2573_RF2, rfprog[i].r2); + rum_rf_write(sc, RT2573_RF3, rfprog[i].r3 | power << 7); + rum_rf_write(sc, RT2573_RF4, rfprog[i].r4 | sc->rffreq << 10); + + rum_rf_write(sc, RT2573_RF1, rfprog[i].r1); + rum_rf_write(sc, RT2573_RF2, rfprog[i].r2); + rum_rf_write(sc, RT2573_RF3, rfprog[i].r3 | power << 7 | 1); + rum_rf_write(sc, RT2573_RF4, rfprog[i].r4 | sc->rffreq << 10); + + rum_rf_write(sc, RT2573_RF1, rfprog[i].r1); + rum_rf_write(sc, RT2573_RF2, rfprog[i].r2); + rum_rf_write(sc, RT2573_RF3, rfprog[i].r3 | power << 7); + rum_rf_write(sc, RT2573_RF4, rfprog[i].r4 | sc->rffreq << 10); + + rum_pause(sc, hz / 100); + + /* enable smart mode for MIMO-capable RFs */ + bbp3 = rum_bbp_read(sc, 3); + + bbp3 &= ~RT2573_SMART_MODE; + if (sc->rf_rev == RT2573_RF_5225 || sc->rf_rev == RT2573_RF_2527) + bbp3 |= RT2573_SMART_MODE; + + rum_bbp_write(sc, 3, bbp3); + + if (bbp94 != RT2573_BBPR94_DEFAULT) + rum_bbp_write(sc, 94, bbp94); + + /* give the chip some extra time to do the switchover */ + rum_pause(sc, hz / 100); +} + +/* + * Enable TSF synchronization and tell h/w to start sending beacons for IBSS + * and HostAP operating modes. + */ +static void +rum_enable_tsf_sync(struct rum_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + uint32_t tmp; + + if (vap->iv_opmode != IEEE80211_M_STA) { + /* + * Change default 16ms TBTT adjustment to 8ms. + * Must be done before enabling beacon generation. + */ + rum_write(sc, RT2573_TXRX_CSR10, 1 << 12 | 8); + } + + tmp = rum_read(sc, RT2573_TXRX_CSR9) & 0xff000000; + + /* set beacon interval (in 1/16ms unit) */ + tmp |= vap->iv_bss->ni_intval * 16; + + tmp |= RT2573_TSF_TICKING | RT2573_ENABLE_TBTT; + if (vap->iv_opmode == IEEE80211_M_STA) + tmp |= RT2573_TSF_MODE(1); + else + tmp |= RT2573_TSF_MODE(2) | RT2573_GENERATE_BEACON; + + rum_write(sc, RT2573_TXRX_CSR9, tmp); +} + +static void +rum_enable_tsf(struct rum_softc *sc) +{ + rum_write(sc, RT2573_TXRX_CSR9, + (rum_read(sc, RT2573_TXRX_CSR9) & 0xff000000) | + RT2573_TSF_TICKING | RT2573_TSF_MODE(2)); +} + +static void +rum_update_slot(struct ifnet *ifp) +{ + struct rum_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + uint8_t slottime; + uint32_t tmp; + + slottime = (ic->ic_flags & IEEE80211_F_SHSLOT) ? 9 : 20; + + tmp = rum_read(sc, RT2573_MAC_CSR9); + tmp = (tmp & ~0xff) | slottime; + rum_write(sc, RT2573_MAC_CSR9, tmp); + + DPRINTF("setting slot time to %uus\n", slottime); +} + +static void +rum_set_bssid(struct rum_softc *sc, const uint8_t *bssid) +{ + uint32_t tmp; + + tmp = bssid[0] | bssid[1] << 8 | bssid[2] << 16 | bssid[3] << 24; + rum_write(sc, RT2573_MAC_CSR4, tmp); + + tmp = bssid[4] | bssid[5] << 8 | RT2573_ONE_BSSID << 16; + rum_write(sc, RT2573_MAC_CSR5, tmp); +} + +static void +rum_set_macaddr(struct rum_softc *sc, const uint8_t *addr) +{ + uint32_t tmp; + + tmp = addr[0] | addr[1] << 8 | addr[2] << 16 | addr[3] << 24; + rum_write(sc, RT2573_MAC_CSR2, tmp); + + tmp = addr[4] | addr[5] << 8 | 0xff << 16; + rum_write(sc, RT2573_MAC_CSR3, tmp); +} + +static void +rum_setpromisc(struct rum_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + uint32_t tmp; + + tmp = rum_read(sc, RT2573_TXRX_CSR0); + + tmp &= ~RT2573_DROP_NOT_TO_ME; + if (!(ifp->if_flags & IFF_PROMISC)) + tmp |= RT2573_DROP_NOT_TO_ME; + + rum_write(sc, RT2573_TXRX_CSR0, tmp); + + DPRINTF("%s promiscuous mode\n", (ifp->if_flags & IFF_PROMISC) ? + "entering" : "leaving"); +} + +static void +rum_update_promisc(struct ifnet *ifp) +{ + struct rum_softc *sc = ifp->if_softc; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + RUM_LOCK(sc); + rum_setpromisc(sc); + RUM_UNLOCK(sc); +} + +static void +rum_update_mcast(struct ifnet *ifp) +{ + static int warning_printed; + + if (warning_printed == 0) { + if_printf(ifp, "need to implement %s\n", __func__); + warning_printed = 1; + } +} + +static const char * +rum_get_rf(int rev) +{ + switch (rev) { + case RT2573_RF_2527: return "RT2527 (MIMO XR)"; + case RT2573_RF_2528: return "RT2528"; + case RT2573_RF_5225: return "RT5225 (MIMO XR)"; + case RT2573_RF_5226: return "RT5226"; + default: return "unknown"; + } +} + +static void +rum_read_eeprom(struct rum_softc *sc) +{ + uint16_t val; +#ifdef RUM_DEBUG + int i; +#endif + + /* read MAC address */ + rum_eeprom_read(sc, RT2573_EEPROM_ADDRESS, sc->sc_bssid, 6); + + rum_eeprom_read(sc, RT2573_EEPROM_ANTENNA, &val, 2); + val = le16toh(val); + sc->rf_rev = (val >> 11) & 0x1f; + sc->hw_radio = (val >> 10) & 0x1; + sc->rx_ant = (val >> 4) & 0x3; + sc->tx_ant = (val >> 2) & 0x3; + sc->nb_ant = val & 0x3; + + DPRINTF("RF revision=%d\n", sc->rf_rev); + + rum_eeprom_read(sc, RT2573_EEPROM_CONFIG2, &val, 2); + val = le16toh(val); + sc->ext_5ghz_lna = (val >> 6) & 0x1; + sc->ext_2ghz_lna = (val >> 4) & 0x1; + + DPRINTF("External 2GHz LNA=%d\nExternal 5GHz LNA=%d\n", + sc->ext_2ghz_lna, sc->ext_5ghz_lna); + + rum_eeprom_read(sc, RT2573_EEPROM_RSSI_2GHZ_OFFSET, &val, 2); + val = le16toh(val); + if ((val & 0xff) != 0xff) + sc->rssi_2ghz_corr = (int8_t)(val & 0xff); /* signed */ + + /* Only [-10, 10] is valid */ + if (sc->rssi_2ghz_corr < -10 || sc->rssi_2ghz_corr > 10) + sc->rssi_2ghz_corr = 0; + + rum_eeprom_read(sc, RT2573_EEPROM_RSSI_5GHZ_OFFSET, &val, 2); + val = le16toh(val); + if ((val & 0xff) != 0xff) + sc->rssi_5ghz_corr = (int8_t)(val & 0xff); /* signed */ + + /* Only [-10, 10] is valid */ + if (sc->rssi_5ghz_corr < -10 || sc->rssi_5ghz_corr > 10) + sc->rssi_5ghz_corr = 0; + + if (sc->ext_2ghz_lna) + sc->rssi_2ghz_corr -= 14; + if (sc->ext_5ghz_lna) + sc->rssi_5ghz_corr -= 14; + + DPRINTF("RSSI 2GHz corr=%d\nRSSI 5GHz corr=%d\n", + sc->rssi_2ghz_corr, sc->rssi_5ghz_corr); + + rum_eeprom_read(sc, RT2573_EEPROM_FREQ_OFFSET, &val, 2); + val = le16toh(val); + if ((val & 0xff) != 0xff) + sc->rffreq = val & 0xff; + + DPRINTF("RF freq=%d\n", sc->rffreq); + + /* read Tx power for all a/b/g channels */ + rum_eeprom_read(sc, RT2573_EEPROM_TXPOWER, sc->txpow, 14); + /* XXX default Tx power for 802.11a channels */ + memset(sc->txpow + 14, 24, sizeof (sc->txpow) - 14); +#ifdef RUM_DEBUG + for (i = 0; i < 14; i++) + DPRINTF("Channel=%d Tx power=%d\n", i + 1, sc->txpow[i]); +#endif + + /* read default values for BBP registers */ + rum_eeprom_read(sc, RT2573_EEPROM_BBP_BASE, sc->bbp_prom, 2 * 16); +#ifdef RUM_DEBUG + for (i = 0; i < 14; i++) { + if (sc->bbp_prom[i].reg == 0 || sc->bbp_prom[i].reg == 0xff) + continue; + DPRINTF("BBP R%d=%02x\n", sc->bbp_prom[i].reg, + sc->bbp_prom[i].val); + } +#endif +} + +static int +rum_bbp_init(struct rum_softc *sc) +{ +#define N(a) (sizeof (a) / sizeof ((a)[0])) + int i, ntries; + + /* wait for BBP to be ready */ + for (ntries = 0; ntries < 100; ntries++) { + const uint8_t val = rum_bbp_read(sc, 0); + if (val != 0 && val != 0xff) + break; + if (rum_pause(sc, hz / 100)) + break; + } + if (ntries == 100) { + device_printf(sc->sc_dev, "timeout waiting for BBP\n"); + return EIO; + } + + /* initialize BBP registers to default values */ + for (i = 0; i < N(rum_def_bbp); i++) + rum_bbp_write(sc, rum_def_bbp[i].reg, rum_def_bbp[i].val); + + /* write vendor-specific BBP values (from EEPROM) */ + for (i = 0; i < 16; i++) { + if (sc->bbp_prom[i].reg == 0 || sc->bbp_prom[i].reg == 0xff) + continue; + rum_bbp_write(sc, sc->bbp_prom[i].reg, sc->bbp_prom[i].val); + } + + return 0; +#undef N +} + +static void +rum_init_locked(struct rum_softc *sc) +{ +#define N(a) (sizeof (a) / sizeof ((a)[0])) + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t tmp; + usb_error_t error; + int i, ntries; + + RUM_LOCK_ASSERT(sc, MA_OWNED); + + rum_stop(sc); + + /* initialize MAC registers to default values */ + for (i = 0; i < N(rum_def_mac); i++) + rum_write(sc, rum_def_mac[i].reg, rum_def_mac[i].val); + + /* set host ready */ + rum_write(sc, RT2573_MAC_CSR1, 3); + rum_write(sc, RT2573_MAC_CSR1, 0); + + /* wait for BBP/RF to wakeup */ + for (ntries = 0; ntries < 100; ntries++) { + if (rum_read(sc, RT2573_MAC_CSR12) & 8) + break; + rum_write(sc, RT2573_MAC_CSR12, 4); /* force wakeup */ + if (rum_pause(sc, hz / 100)) + break; + } + if (ntries == 100) { + device_printf(sc->sc_dev, + "timeout waiting for BBP/RF to wakeup\n"); + goto fail; + } + + if ((error = rum_bbp_init(sc)) != 0) + goto fail; + + /* select default channel */ + rum_select_band(sc, ic->ic_curchan); + rum_select_antenna(sc); + rum_set_chan(sc, ic->ic_curchan); + + /* clear STA registers */ + rum_read_multi(sc, RT2573_STA_CSR0, sc->sta, sizeof sc->sta); + + rum_set_macaddr(sc, IF_LLADDR(ifp)); + + /* initialize ASIC */ + rum_write(sc, RT2573_MAC_CSR1, 4); + + /* + * Allocate Tx and Rx xfer queues. + */ + rum_setup_tx_list(sc); + + /* update Rx filter */ + tmp = rum_read(sc, RT2573_TXRX_CSR0) & 0xffff; + + tmp |= RT2573_DROP_PHY_ERROR | RT2573_DROP_CRC_ERROR; + if (ic->ic_opmode != IEEE80211_M_MONITOR) { + tmp |= RT2573_DROP_CTL | RT2573_DROP_VER_ERROR | + RT2573_DROP_ACKCTS; + if (ic->ic_opmode != IEEE80211_M_HOSTAP) + tmp |= RT2573_DROP_TODS; + if (!(ifp->if_flags & IFF_PROMISC)) + tmp |= RT2573_DROP_NOT_TO_ME; + } + rum_write(sc, RT2573_TXRX_CSR0, tmp); + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + usbd_xfer_set_stall(sc->sc_xfer[RUM_BULK_WR]); + usbd_transfer_start(sc->sc_xfer[RUM_BULK_RD]); + return; + +fail: rum_stop(sc); +#undef N +} + +static void +rum_init(void *priv) +{ + struct rum_softc *sc = priv; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + RUM_LOCK(sc); + rum_init_locked(sc); + RUM_UNLOCK(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ieee80211_start_all(ic); /* start all vap's */ +} + +static void +rum_stop(struct rum_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + uint32_t tmp; + + RUM_LOCK_ASSERT(sc, MA_OWNED); + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + RUM_UNLOCK(sc); + + /* + * Drain the USB transfers, if not already drained: + */ + usbd_transfer_drain(sc->sc_xfer[RUM_BULK_WR]); + usbd_transfer_drain(sc->sc_xfer[RUM_BULK_RD]); + + RUM_LOCK(sc); + + rum_unsetup_tx_list(sc); + + /* disable Rx */ + tmp = rum_read(sc, RT2573_TXRX_CSR0); + rum_write(sc, RT2573_TXRX_CSR0, tmp | RT2573_DISABLE_RX); + + /* reset ASIC */ + rum_write(sc, RT2573_MAC_CSR1, 3); + rum_write(sc, RT2573_MAC_CSR1, 0); +} + +static void +rum_load_microcode(struct rum_softc *sc, const uint8_t *ucode, size_t size) +{ + struct usb_device_request req; + uint16_t reg = RT2573_MCU_CODE_BASE; + usb_error_t err; + + /* copy firmware image into NIC */ + for (; size >= 4; reg += 4, ucode += 4, size -= 4) { + err = rum_write(sc, reg, UGETDW(ucode)); + if (err) { + /* firmware already loaded ? */ + device_printf(sc->sc_dev, "Firmware load " + "failure! (ignored)\n"); + break; + } + } + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RT2573_MCU_CNTL; + USETW(req.wValue, RT2573_MCU_RUN); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + err = rum_do_request(sc, &req, NULL); + if (err != 0) { + device_printf(sc->sc_dev, "could not run firmware: %s\n", + usbd_errstr(err)); + } + + /* give the chip some time to boot */ + rum_pause(sc, hz / 8); +} + +static void +rum_prepare_beacon(struct rum_softc *sc, struct ieee80211vap *vap) +{ + struct ieee80211com *ic = vap->iv_ic; + const struct ieee80211_txparam *tp; + struct rum_tx_desc desc; + struct mbuf *m0; + + if (vap->iv_bss->ni_chan == IEEE80211_CHAN_ANYC) + return; + + m0 = ieee80211_beacon_alloc(vap->iv_bss, &RUM_VAP(vap)->bo); + if (m0 == NULL) { + return; + } + + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_bsschan)]; + rum_setup_tx_desc(sc, &desc, RT2573_TX_TIMESTAMP, RT2573_TX_HWSEQ, + m0->m_pkthdr.len, tp->mgmtrate); + + /* copy the first 24 bytes of Tx descriptor into NIC memory */ + rum_write_multi(sc, RT2573_HW_BEACON_BASE0, (uint8_t *)&desc, 24); + + /* copy beacon header and payload into NIC memory */ + rum_write_multi(sc, RT2573_HW_BEACON_BASE0 + 24, mtod(m0, uint8_t *), + m0->m_pkthdr.len); + + m_freem(m0); +} + +static int +rum_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ifnet *ifp = ni->ni_ic->ic_ifp; + struct rum_softc *sc = ifp->if_softc; + + RUM_LOCK(sc); + /* prevent management frames from being sent if we're not ready */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + RUM_UNLOCK(sc); + m_freem(m); + ieee80211_free_node(ni); + return ENETDOWN; + } + if (sc->tx_nfree < RUM_TX_MINFREE) { + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + RUM_UNLOCK(sc); + m_freem(m); + ieee80211_free_node(ni); + return EIO; + } + + ifp->if_opackets++; + + if (params == NULL) { + /* + * Legacy path; interpret frame contents to decide + * precisely how to send the frame. + */ + if (rum_tx_mgt(sc, m, ni) != 0) + goto bad; + } else { + /* + * Caller supplied explicit parameters to use in + * sending the frame. + */ + if (rum_tx_raw(sc, m, ni, params) != 0) + goto bad; + } + RUM_UNLOCK(sc); + + return 0; +bad: + ifp->if_oerrors++; + RUM_UNLOCK(sc); + ieee80211_free_node(ni); + return EIO; +} + +static void +rum_ratectl_start(struct rum_softc *sc, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct rum_vap *rvp = RUM_VAP(vap); + + /* clear statistic registers (STA_CSR0 to STA_CSR5) */ + rum_read_multi(sc, RT2573_STA_CSR0, sc->sta, sizeof sc->sta); + + usb_callout_reset(&rvp->ratectl_ch, hz, rum_ratectl_timeout, rvp); +} + +static void +rum_ratectl_timeout(void *arg) +{ + struct rum_vap *rvp = arg; + struct ieee80211vap *vap = &rvp->vap; + struct ieee80211com *ic = vap->iv_ic; + + ieee80211_runtask(ic, &rvp->ratectl_task); +} + +static void +rum_ratectl_task(void *arg, int pending) +{ + struct rum_vap *rvp = arg; + struct ieee80211vap *vap = &rvp->vap; + struct ieee80211com *ic = vap->iv_ic; + struct ifnet *ifp = ic->ic_ifp; + struct rum_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + int ok, fail; + int sum, retrycnt; + + RUM_LOCK(sc); + /* read and clear statistic registers (STA_CSR0 to STA_CSR10) */ + rum_read_multi(sc, RT2573_STA_CSR0, sc->sta, sizeof(sc->sta)); + + ok = (le32toh(sc->sta[4]) >> 16) + /* TX ok w/o retry */ + (le32toh(sc->sta[5]) & 0xffff); /* TX ok w/ retry */ + fail = (le32toh(sc->sta[5]) >> 16); /* TX retry-fail count */ + sum = ok+fail; + retrycnt = (le32toh(sc->sta[5]) & 0xffff) + fail; + + ni = ieee80211_ref_node(vap->iv_bss); + ieee80211_ratectl_tx_update(vap, ni, &sum, &ok, &retrycnt); + (void) ieee80211_ratectl_rate(ni, NULL, 0); + ieee80211_free_node(ni); + + ifp->if_oerrors += fail; /* count TX retry-fail as Tx errors */ + + usb_callout_reset(&rvp->ratectl_ch, hz, rum_ratectl_timeout, rvp); + RUM_UNLOCK(sc); +} + +static void +rum_scan_start(struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct rum_softc *sc = ifp->if_softc; + uint32_t tmp; + + RUM_LOCK(sc); + /* abort TSF synchronization */ + tmp = rum_read(sc, RT2573_TXRX_CSR9); + rum_write(sc, RT2573_TXRX_CSR9, tmp & ~0x00ffffff); + rum_set_bssid(sc, ifp->if_broadcastaddr); + RUM_UNLOCK(sc); + +} + +static void +rum_scan_end(struct ieee80211com *ic) +{ + struct rum_softc *sc = ic->ic_ifp->if_softc; + + RUM_LOCK(sc); + rum_enable_tsf_sync(sc); + rum_set_bssid(sc, sc->sc_bssid); + RUM_UNLOCK(sc); + +} + +static void +rum_set_channel(struct ieee80211com *ic) +{ + struct rum_softc *sc = ic->ic_ifp->if_softc; + + RUM_LOCK(sc); + rum_set_chan(sc, ic->ic_curchan); + RUM_UNLOCK(sc); +} + +static int +rum_get_rssi(struct rum_softc *sc, uint8_t raw) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + int lna, agc, rssi; + + lna = (raw >> 5) & 0x3; + agc = raw & 0x1f; + + if (lna == 0) { + /* + * No RSSI mapping + * + * NB: Since RSSI is relative to noise floor, -1 is + * adequate for caller to know error happened. + */ + return -1; + } + + rssi = (2 * agc) - RT2573_NOISE_FLOOR; + + if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { + rssi += sc->rssi_2ghz_corr; + + if (lna == 1) + rssi -= 64; + else if (lna == 2) + rssi -= 74; + else if (lna == 3) + rssi -= 90; + } else { + rssi += sc->rssi_5ghz_corr; + + if (!sc->ext_5ghz_lna && lna != 1) + rssi += 4; + + if (lna == 1) + rssi -= 64; + else if (lna == 2) + rssi -= 86; + else if (lna == 3) + rssi -= 100; + } + return rssi; +} + +static int +rum_pause(struct rum_softc *sc, int timeout) +{ + + usb_pause_mtx(&sc->sc_mtx, timeout); + return (0); +} + +static device_method_t rum_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, rum_match), + DEVMETHOD(device_attach, rum_attach), + DEVMETHOD(device_detach, rum_detach), + + { 0, 0 } +}; + +static driver_t rum_driver = { + .name = "rum", + .methods = rum_methods, + .size = sizeof(struct rum_softc), +}; + +static devclass_t rum_devclass; + +DRIVER_MODULE(rum, uhub, rum_driver, rum_devclass, NULL, 0); +MODULE_DEPEND(rum, wlan, 1, 1, 1); +MODULE_DEPEND(rum, usb, 1, 1, 1); +MODULE_VERSION(rum, 1); diff --git a/sys/bus/u4b/wlan/if_rumfw.h b/sys/bus/u4b/wlan/if_rumfw.h new file mode 100644 index 0000000000..0f08674451 --- /dev/null +++ b/sys/bus/u4b/wlan/if_rumfw.h @@ -0,0 +1,213 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005-2006, Ralink Technology, Corp. + * Paul Lin + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file contains the loadable 8051 microcode for the Ralink RT2573 + * chipset. + */ + +static const uint8_t rt2573_ucode[] = { + 0x02, 0x13, 0x25, 0x12, 0x10, 0xd9, 0x02, 0x12, 0x58, 0x02, 0x13, + 0x58, 0x02, 0x13, 0x5a, 0xc0, 0xd0, 0x75, 0xd0, 0x18, 0x12, 0x13, + 0x5c, 0xd0, 0xd0, 0x22, 0x02, 0x14, 0x5c, 0x02, 0x14, 0xe7, 0xed, + 0x4c, 0x70, 0x44, 0x90, 0x01, 0xa8, 0x74, 0x80, 0xf0, 0xef, 0x30, + 0xe5, 0x07, 0xe4, 0x90, 0x00, 0x0f, 0xf0, 0x80, 0x2c, 0xe5, 0x40, + 0x24, 0xc0, 0x60, 0x13, 0x24, 0xc0, 0x60, 0x16, 0x24, 0xc0, 0x60, + 0x19, 0x24, 0xc0, 0x70, 0x1a, 0xe4, 0x90, 0x00, 0x0b, 0xf0, 0x80, + 0x13, 0xe4, 0x90, 0x00, 0x13, 0xf0, 0x80, 0x0c, 0xe4, 0x90, 0x00, + 0x1b, 0xf0, 0x80, 0x05, 0xe4, 0x90, 0x00, 0x23, 0xf0, 0xe4, 0x90, + 0x01, 0xa8, 0xf0, 0xd3, 0x22, 0x90, 0x02, 0x02, 0xed, 0xf0, 0x90, + 0x02, 0x01, 0xef, 0xf0, 0xd3, 0x22, 0xef, 0x24, 0xc0, 0x60, 0x1f, + 0x24, 0xc0, 0x60, 0x2e, 0x24, 0xc0, 0x60, 0x3d, 0x24, 0xc0, 0x70, + 0x53, 0x90, 0x00, 0x0b, 0xe0, 0x30, 0xe1, 0x02, 0xc3, 0x22, 0x90, + 0x00, 0x09, 0xe0, 0xfe, 0x90, 0x00, 0x08, 0x80, 0x37, 0x90, 0x00, + 0x13, 0xe0, 0x30, 0xe1, 0x02, 0xc3, 0x22, 0x90, 0x00, 0x11, 0xe0, + 0xfe, 0x90, 0x00, 0x10, 0x80, 0x24, 0x90, 0x00, 0x1b, 0xe0, 0x30, + 0xe1, 0x02, 0xc3, 0x22, 0x90, 0x00, 0x19, 0xe0, 0xfe, 0x90, 0x00, + 0x18, 0x80, 0x11, 0x90, 0x00, 0x23, 0xe0, 0x30, 0xe1, 0x02, 0xc3, + 0x22, 0x90, 0x00, 0x21, 0xe0, 0xfe, 0x90, 0x00, 0x20, 0xe0, 0xfd, + 0xee, 0xf5, 0x37, 0xed, 0xf5, 0x38, 0xd3, 0x22, 0x30, 0x09, 0x20, + 0x20, 0x04, 0x0b, 0x90, 0x02, 0x08, 0xe0, 0x54, 0x0f, 0x70, 0x03, + 0x02, 0x12, 0x57, 0xc2, 0x09, 0x90, 0x02, 0x00, 0xe0, 0x44, 0x04, + 0xf0, 0x74, 0x04, 0x12, 0x0c, 0x3a, 0xc2, 0x04, 0xc2, 0x07, 0x90, + 0x02, 0x01, 0xe0, 0x30, 0xe0, 0x03, 0x00, 0x80, 0xf6, 0x90, 0x03, + 0x26, 0xe0, 0x20, 0xe2, 0x03, 0x02, 0x12, 0x57, 0x90, 0x02, 0x08, + 0xe0, 0x70, 0x1b, 0x20, 0x07, 0x03, 0x02, 0x12, 0x57, 0x90, 0x03, + 0x12, 0xe0, 0x64, 0x22, 0x60, 0x03, 0x02, 0x12, 0x57, 0xd2, 0x09, + 0xc2, 0x07, 0x74, 0x02, 0x12, 0x0c, 0x3a, 0x22, 0x90, 0x02, 0x03, + 0xe0, 0x30, 0xe4, 0x47, 0x20, 0x06, 0x44, 0xe5, 0x3c, 0x60, 0x34, + 0xe5, 0x40, 0x24, 0xc0, 0x60, 0x14, 0x24, 0xc0, 0x60, 0x18, 0x24, + 0xc0, 0x60, 0x1c, 0x24, 0xc0, 0x70, 0x22, 0x90, 0x00, 0x0b, 0xe0, + 0x30, 0xe1, 0x1b, 0x22, 0x90, 0x00, 0x13, 0xe0, 0x30, 0xe1, 0x13, + 0x22, 0x90, 0x00, 0x1b, 0xe0, 0x30, 0xe1, 0x0b, 0x22, 0x90, 0x00, + 0x23, 0xe0, 0x30, 0xe1, 0x03, 0x02, 0x12, 0x57, 0x90, 0x02, 0x03, + 0x74, 0x01, 0xf0, 0x00, 0xe0, 0x54, 0xc0, 0xf5, 0x40, 0xe5, 0x40, + 0x24, 0xc0, 0x60, 0x20, 0x24, 0xc0, 0x60, 0x30, 0x24, 0xc0, 0x60, + 0x40, 0x24, 0xc0, 0x70, 0x56, 0x90, 0x00, 0x0b, 0xe0, 0x30, 0xe1, + 0x03, 0x02, 0x12, 0x57, 0x90, 0x00, 0x09, 0xe0, 0xfe, 0x90, 0x00, + 0x08, 0x80, 0x3a, 0x90, 0x00, 0x13, 0xe0, 0x30, 0xe1, 0x03, 0x02, + 0x12, 0x57, 0x90, 0x00, 0x11, 0xe0, 0xfe, 0x90, 0x00, 0x10, 0x80, + 0x26, 0x90, 0x00, 0x1b, 0xe0, 0x30, 0xe1, 0x03, 0x02, 0x12, 0x57, + 0x90, 0x00, 0x19, 0xe0, 0xfe, 0x90, 0x00, 0x18, 0x80, 0x12, 0x90, + 0x00, 0x23, 0xe0, 0x30, 0xe1, 0x03, 0x02, 0x12, 0x57, 0x90, 0x00, + 0x21, 0xe0, 0xfe, 0x90, 0x00, 0x20, 0xe0, 0xfd, 0xee, 0xf5, 0x37, + 0xed, 0xf5, 0x38, 0x90, 0x03, 0x27, 0x74, 0x82, 0xf0, 0x90, 0x02, + 0x01, 0xe5, 0x40, 0xf0, 0x90, 0x02, 0x06, 0xe0, 0xf5, 0x3c, 0xc3, + 0xe5, 0x38, 0x95, 0x3a, 0xe5, 0x37, 0x95, 0x39, 0x50, 0x21, 0xe5, + 0x40, 0x44, 0x05, 0xff, 0xe5, 0x37, 0xa2, 0xe7, 0x13, 0xfc, 0xe5, + 0x38, 0x13, 0xfd, 0x12, 0x10, 0x20, 0xe5, 0x3c, 0x30, 0xe2, 0x04, + 0xd2, 0x06, 0x80, 0x02, 0xc2, 0x06, 0x53, 0x3c, 0x01, 0x22, 0x30, + 0x0b, 0x07, 0xe4, 0x90, 0x02, 0x02, 0xf0, 0x80, 0x06, 0x90, 0x02, + 0x02, 0x74, 0x20, 0xf0, 0xe5, 0x40, 0x44, 0x01, 0x90, 0x02, 0x01, + 0xf0, 0x90, 0x02, 0x01, 0xe0, 0x30, 0xe0, 0x03, 0x00, 0x80, 0xf6, + 0x90, 0x03, 0x27, 0x74, 0x02, 0xf0, 0xaf, 0x40, 0x12, 0x10, 0x74, + 0x40, 0xa5, 0x00, 0x80, 0xf6, 0x22, 0x90, 0x7f, 0xf8, 0xe0, 0xb4, + 0x02, 0x03, 0x12, 0x16, 0x38, 0x90, 0x02, 0x01, 0xe0, 0x30, 0xe0, + 0x03, 0x00, 0x80, 0xf6, 0x90, 0x03, 0x26, 0xe0, 0x20, 0xe1, 0x07, + 0xe5, 0x3b, 0x70, 0x03, 0x02, 0x13, 0x24, 0xe5, 0x3b, 0x70, 0x15, + 0x90, 0x03, 0x24, 0xe0, 0x75, 0xf0, 0x40, 0xa4, 0xf5, 0x36, 0x85, + 0xf0, 0x35, 0x75, 0x24, 0x83, 0x75, 0x3b, 0x01, 0x80, 0x03, 0x75, + 0x24, 0x03, 0xd3, 0xe5, 0x36, 0x95, 0x3a, 0xe5, 0x35, 0x95, 0x39, + 0x40, 0x36, 0x90, 0x02, 0x01, 0xe0, 0x30, 0xe0, 0x03, 0x00, 0x80, + 0xf6, 0x90, 0x03, 0x27, 0xe5, 0x24, 0xf0, 0x90, 0x00, 0x0f, 0xe0, + 0x30, 0xe1, 0x04, 0x30, 0x0e, 0xf6, 0x22, 0x30, 0x0b, 0x07, 0xe4, + 0x90, 0x02, 0x02, 0xf0, 0x80, 0x06, 0x90, 0x02, 0x02, 0x74, 0x20, + 0xf0, 0x90, 0x02, 0x01, 0x74, 0x21, 0xf0, 0x75, 0x24, 0x03, 0x80, + 0x3d, 0xe5, 0x35, 0xa2, 0xe7, 0x13, 0xfe, 0xe5, 0x36, 0x13, 0xfd, + 0xac, 0x06, 0x90, 0x02, 0x01, 0xe0, 0x30, 0xe0, 0x03, 0x00, 0x80, + 0xf6, 0x90, 0x03, 0x27, 0xe5, 0x24, 0xf0, 0x90, 0x00, 0x0f, 0xe0, + 0x30, 0xe1, 0x04, 0x30, 0x0e, 0xf6, 0x22, 0x7f, 0x25, 0x12, 0x10, + 0x20, 0xe5, 0x36, 0xb5, 0x3a, 0x08, 0xe5, 0x35, 0xb5, 0x39, 0x03, + 0x00, 0x80, 0x04, 0xe4, 0xf5, 0x3b, 0x22, 0xc3, 0xe5, 0x36, 0x95, + 0x3a, 0xf5, 0x36, 0xe5, 0x35, 0x95, 0x39, 0xf5, 0x35, 0x02, 0x12, + 0x96, 0x22, 0x75, 0xa8, 0x0f, 0x90, 0x03, 0x06, 0x74, 0x01, 0xf0, + 0x90, 0x03, 0x07, 0xf0, 0x90, 0x03, 0x08, 0x04, 0xf0, 0x90, 0x03, + 0x09, 0x74, 0x6c, 0xf0, 0x90, 0x03, 0x0a, 0x74, 0xff, 0xf0, 0x90, + 0x03, 0x02, 0x74, 0x1f, 0xf0, 0x90, 0x03, 0x00, 0x74, 0x04, 0xf0, + 0x90, 0x03, 0x25, 0x74, 0x31, 0xf0, 0xd2, 0xaf, 0x22, 0x00, 0x22, + 0x00, 0x22, 0x90, 0x03, 0x05, 0xe0, 0x30, 0xe0, 0x0b, 0xe0, 0x44, + 0x01, 0xf0, 0x30, 0x09, 0x02, 0xd2, 0x04, 0xc2, 0x07, 0x22, 0x8d, + 0x24, 0xa9, 0x07, 0x90, 0x7f, 0xfc, 0xe0, 0x75, 0x25, 0x00, 0xf5, + 0x26, 0xa3, 0xe0, 0x75, 0x27, 0x00, 0xf5, 0x28, 0xa3, 0xe0, 0xff, + 0xa3, 0xe0, 0xfd, 0xe9, 0x30, 0xe5, 0x14, 0x54, 0xc0, 0x60, 0x05, + 0x43, 0x05, 0x03, 0x80, 0x03, 0x53, 0x05, 0xfc, 0xef, 0x54, 0x3f, + 0x44, 0x40, 0xff, 0x80, 0x06, 0x53, 0x07, 0x3f, 0x53, 0x05, 0xf0, + 0xe5, 0x24, 0x30, 0xe0, 0x05, 0x43, 0x05, 0x10, 0x80, 0x03, 0x53, + 0x05, 0xef, 0x90, 0x7f, 0xfc, 0xe5, 0x26, 0xf0, 0xa3, 0xe5, 0x28, + 0xf0, 0xa3, 0xef, 0xf0, 0xa3, 0xed, 0xf0, 0x22, 0x8f, 0x24, 0xa9, + 0x05, 0x90, 0x7f, 0xfc, 0xe0, 0x75, 0x25, 0x00, 0xf5, 0x26, 0xa3, + 0xe0, 0x75, 0x27, 0x00, 0xf5, 0x28, 0xa3, 0xe0, 0xff, 0xa3, 0xe0, + 0xfd, 0xe5, 0x24, 0x30, 0xe5, 0x0b, 0x43, 0x05, 0x0f, 0xef, 0x54, + 0x3f, 0x44, 0x40, 0xff, 0x80, 0x06, 0x53, 0x05, 0xf0, 0x53, 0x07, + 0x3f, 0xe9, 0x30, 0xe0, 0x05, 0x43, 0x05, 0x10, 0x80, 0x03, 0x53, + 0x05, 0xef, 0x90, 0x7f, 0xfc, 0xe5, 0x26, 0xf0, 0xa3, 0xe5, 0x28, + 0xf0, 0xa3, 0xef, 0xf0, 0xa3, 0xed, 0xf0, 0x22, 0x90, 0x7f, 0xfc, + 0xe0, 0xf9, 0xa3, 0xe0, 0xfe, 0xa3, 0xe0, 0xfc, 0xa3, 0xe0, 0xfb, + 0xef, 0x30, 0xe5, 0x0b, 0x43, 0x03, 0x0f, 0xec, 0x54, 0x3f, 0x44, + 0x40, 0xfc, 0x80, 0x06, 0x53, 0x03, 0xf0, 0x53, 0x04, 0x3f, 0xed, + 0x30, 0xe0, 0x07, 0xef, 0x54, 0xc0, 0x60, 0x07, 0x80, 0x0a, 0xef, + 0x54, 0xc0, 0x60, 0x05, 0x43, 0x03, 0x10, 0x80, 0x03, 0x53, 0x03, + 0xef, 0x90, 0x7f, 0xfc, 0xe9, 0xf0, 0xa3, 0xee, 0xf0, 0xa3, 0xec, + 0xf0, 0xa3, 0xeb, 0xf0, 0x22, 0xe5, 0x4b, 0xfd, 0x54, 0x1f, 0x90, + 0x7f, 0xf8, 0xf0, 0xe5, 0x4a, 0xf5, 0x09, 0x90, 0x30, 0x38, 0xe0, + 0x90, 0x7f, 0xfc, 0xf0, 0x90, 0x30, 0x39, 0xe0, 0x90, 0x7f, 0xfd, + 0xf0, 0x90, 0x30, 0x3a, 0xe0, 0x90, 0x7f, 0xfe, 0xf0, 0x90, 0x30, + 0x3b, 0xe0, 0x90, 0x7f, 0xff, 0xf0, 0xed, 0x30, 0xe5, 0x0c, 0x54, + 0xc0, 0x60, 0x0d, 0x90, 0x7f, 0xf0, 0xe5, 0x47, 0xf0, 0x80, 0x05, + 0xe4, 0x90, 0x7f, 0xf0, 0xf0, 0x90, 0x7f, 0xf8, 0xe0, 0x14, 0x60, + 0x08, 0x24, 0xfe, 0x60, 0x0d, 0x24, 0x03, 0x80, 0x12, 0xaf, 0x05, + 0xad, 0x09, 0x12, 0x13, 0xc5, 0x80, 0x10, 0xaf, 0x05, 0xad, 0x09, + 0x12, 0x14, 0x12, 0x80, 0x07, 0xaf, 0x05, 0xad, 0x09, 0x12, 0x13, + 0x6f, 0x90, 0x7f, 0xfc, 0xe0, 0x90, 0x30, 0x38, 0xf0, 0x90, 0x7f, + 0xfd, 0xe0, 0x90, 0x30, 0x39, 0xf0, 0x90, 0x7f, 0xfe, 0xe0, 0x90, + 0x30, 0x3a, 0xf0, 0x90, 0x7f, 0xff, 0xe0, 0x90, 0x30, 0x3b, 0xf0, + 0x22, 0xe5, 0x4b, 0x64, 0x01, 0x60, 0x03, 0x02, 0x15, 0x71, 0xf5, + 0x4b, 0xe5, 0x44, 0x45, 0x43, 0x70, 0x03, 0x02, 0x15, 0xa0, 0x12, + 0x0c, 0x14, 0x12, 0x0b, 0x86, 0x50, 0xfb, 0x90, 0x00, 0x00, 0xe0, + 0xf5, 0x25, 0x12, 0x15, 0xb4, 0xc2, 0x92, 0xe4, 0xf5, 0x24, 0xe5, + 0x24, 0xc3, 0x95, 0x25, 0x50, 0x49, 0x7e, 0x00, 0x7f, 0x4c, 0x74, + 0x40, 0x25, 0x24, 0xf5, 0x82, 0xe4, 0x34, 0x01, 0xad, 0x82, 0xfc, + 0x75, 0x2b, 0x02, 0x7b, 0x10, 0x12, 0x07, 0x1e, 0xc2, 0x93, 0x12, + 0x15, 0xa1, 0x7d, 0xa0, 0x12, 0x15, 0xd0, 0xe5, 0x24, 0x54, 0x0f, + 0x24, 0x4c, 0xf8, 0xe6, 0xfd, 0xaf, 0x4b, 0xae, 0x4a, 0x12, 0x15, + 0xd8, 0x05, 0x4b, 0xe5, 0x4b, 0x70, 0x02, 0x05, 0x4a, 0x12, 0x0a, + 0x5f, 0x05, 0x24, 0xe5, 0x24, 0x54, 0x0f, 0x70, 0xd5, 0xd2, 0x93, + 0x80, 0xb0, 0xc3, 0xe5, 0x44, 0x95, 0x25, 0xf5, 0x44, 0xe5, 0x43, + 0x94, 0x00, 0xf5, 0x43, 0x02, 0x14, 0xf2, 0x12, 0x15, 0xb4, 0xc2, + 0x93, 0xc2, 0x92, 0x12, 0x15, 0xa1, 0x7d, 0x80, 0x12, 0x15, 0xd0, + 0x7d, 0xaa, 0x74, 0x55, 0xff, 0xfe, 0x12, 0x15, 0xd8, 0x7d, 0x55, + 0x7f, 0xaa, 0x7e, 0x2a, 0x12, 0x15, 0xd8, 0x7d, 0x30, 0xaf, 0x4b, + 0xae, 0x4a, 0x12, 0x15, 0xd8, 0x12, 0x0a, 0x5f, 0xd2, 0x93, 0x22, + 0x7d, 0xaa, 0x74, 0x55, 0xff, 0xfe, 0x12, 0x15, 0xd8, 0x7d, 0x55, + 0x7f, 0xaa, 0x7e, 0x2a, 0x12, 0x15, 0xd8, 0x22, 0xad, 0x47, 0x7f, + 0x34, 0x7e, 0x30, 0x12, 0x15, 0xd8, 0x7d, 0xff, 0x7f, 0x35, 0x7e, + 0x30, 0x12, 0x15, 0xd8, 0xe4, 0xfd, 0x7f, 0x37, 0x7e, 0x30, 0x12, + 0x15, 0xd8, 0x22, 0x74, 0x55, 0xff, 0xfe, 0x12, 0x15, 0xd8, 0x22, + 0x8f, 0x82, 0x8e, 0x83, 0xed, 0xf0, 0x22, 0xe4, 0xfc, 0x90, 0x7f, + 0xf0, 0xe0, 0xaf, 0x09, 0x14, 0x60, 0x14, 0x14, 0x60, 0x15, 0x14, + 0x60, 0x16, 0x14, 0x60, 0x17, 0x14, 0x60, 0x18, 0x24, 0x05, 0x70, + 0x16, 0xe4, 0xfc, 0x80, 0x12, 0x7c, 0x01, 0x80, 0x0e, 0x7c, 0x03, + 0x80, 0x0a, 0x7c, 0x07, 0x80, 0x06, 0x7c, 0x0f, 0x80, 0x02, 0x7c, + 0x1f, 0xec, 0x6f, 0xf4, 0x54, 0x1f, 0xfc, 0x90, 0x30, 0x34, 0xe0, + 0x54, 0xe0, 0x4c, 0xfd, 0xa3, 0xe0, 0xfc, 0x43, 0x04, 0x1f, 0x7f, + 0x34, 0x7e, 0x30, 0x12, 0x15, 0xd8, 0xad, 0x04, 0x0f, 0x12, 0x15, + 0xd8, 0xe4, 0xfd, 0x7f, 0x37, 0x02, 0x15, 0xd8, 0x02, 0x15, 0xdf, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, + 0x29, 0xe9 +}; diff --git a/sys/bus/u4b/wlan/if_rumreg.h b/sys/bus/u4b/wlan/if_rumreg.h new file mode 100644 index 0000000000..75a51bcd4a --- /dev/null +++ b/sys/bus/u4b/wlan/if_rumreg.h @@ -0,0 +1,235 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005, 2006 Damien Bergamini + * Copyright (c) 2006 Niall O'Higgins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define RT2573_NOISE_FLOOR -95 + +#define RT2573_TX_DESC_SIZE (sizeof (struct rum_tx_desc)) +#define RT2573_RX_DESC_SIZE (sizeof (struct rum_rx_desc)) + +#define RT2573_CONFIG_NO 1 +#define RT2573_IFACE_INDEX 0 + +#define RT2573_MCU_CNTL 0x01 +#define RT2573_WRITE_MAC 0x02 +#define RT2573_READ_MAC 0x03 +#define RT2573_WRITE_MULTI_MAC 0x06 +#define RT2573_READ_MULTI_MAC 0x07 +#define RT2573_READ_EEPROM 0x09 +#define RT2573_WRITE_LED 0x0a + +/* + * Control and status registers. + */ +#define RT2573_AIFSN_CSR 0x0400 +#define RT2573_CWMIN_CSR 0x0404 +#define RT2573_CWMAX_CSR 0x0408 +#define RT2573_MCU_CODE_BASE 0x0800 +#define RT2573_HW_BEACON_BASE0 0x2400 +#define RT2573_MAC_CSR0 0x3000 +#define RT2573_MAC_CSR1 0x3004 +#define RT2573_MAC_CSR2 0x3008 +#define RT2573_MAC_CSR3 0x300c +#define RT2573_MAC_CSR4 0x3010 +#define RT2573_MAC_CSR5 0x3014 +#define RT2573_MAC_CSR6 0x3018 +#define RT2573_MAC_CSR7 0x301c +#define RT2573_MAC_CSR8 0x3020 +#define RT2573_MAC_CSR9 0x3024 +#define RT2573_MAC_CSR10 0x3028 +#define RT2573_MAC_CSR11 0x302c +#define RT2573_MAC_CSR12 0x3030 +#define RT2573_MAC_CSR13 0x3034 +#define RT2573_MAC_CSR14 0x3038 +#define RT2573_MAC_CSR15 0x303c +#define RT2573_TXRX_CSR0 0x3040 +#define RT2573_TXRX_CSR1 0x3044 +#define RT2573_TXRX_CSR2 0x3048 +#define RT2573_TXRX_CSR3 0x304c +#define RT2573_TXRX_CSR4 0x3050 +#define RT2573_TXRX_CSR5 0x3054 +#define RT2573_TXRX_CSR6 0x3058 +#define RT2573_TXRX_CSR7 0x305c +#define RT2573_TXRX_CSR8 0x3060 +#define RT2573_TXRX_CSR9 0x3064 +#define RT2573_TXRX_CSR10 0x3068 +#define RT2573_TXRX_CSR11 0x306c +#define RT2573_TXRX_CSR12 0x3070 +#define RT2573_TXRX_CSR13 0x3074 +#define RT2573_TXRX_CSR14 0x3078 +#define RT2573_TXRX_CSR15 0x307c +#define RT2573_PHY_CSR0 0x3080 +#define RT2573_PHY_CSR1 0x3084 +#define RT2573_PHY_CSR2 0x3088 +#define RT2573_PHY_CSR3 0x308c +#define RT2573_PHY_CSR4 0x3090 +#define RT2573_PHY_CSR5 0x3094 +#define RT2573_PHY_CSR6 0x3098 +#define RT2573_PHY_CSR7 0x309c +#define RT2573_SEC_CSR0 0x30a0 +#define RT2573_SEC_CSR1 0x30a4 +#define RT2573_SEC_CSR2 0x30a8 +#define RT2573_SEC_CSR3 0x30ac +#define RT2573_SEC_CSR4 0x30b0 +#define RT2573_SEC_CSR5 0x30b4 +#define RT2573_STA_CSR0 0x30c0 +#define RT2573_STA_CSR1 0x30c4 +#define RT2573_STA_CSR2 0x30c8 +#define RT2573_STA_CSR3 0x30cc +#define RT2573_STA_CSR4 0x30d0 +#define RT2573_STA_CSR5 0x30d4 + + +/* possible flags for register RT2573_MAC_CSR1 */ +#define RT2573_RESET_ASIC (1 << 0) +#define RT2573_RESET_BBP (1 << 1) +#define RT2573_HOST_READY (1 << 2) + +/* possible flags for register MAC_CSR5 */ +#define RT2573_ONE_BSSID 3 + +/* possible flags for register TXRX_CSR0 */ +/* Tx filter flags are in the low 16 bits */ +#define RT2573_AUTO_TX_SEQ (1 << 15) +/* Rx filter flags are in the high 16 bits */ +#define RT2573_DISABLE_RX (1 << 16) +#define RT2573_DROP_CRC_ERROR (1 << 17) +#define RT2573_DROP_PHY_ERROR (1 << 18) +#define RT2573_DROP_CTL (1 << 19) +#define RT2573_DROP_NOT_TO_ME (1 << 20) +#define RT2573_DROP_TODS (1 << 21) +#define RT2573_DROP_VER_ERROR (1 << 22) +#define RT2573_DROP_MULTICAST (1 << 23) +#define RT2573_DROP_BROADCAST (1 << 24) +#define RT2573_DROP_ACKCTS (1 << 25) + +/* possible flags for register TXRX_CSR4 */ +#define RT2573_SHORT_PREAMBLE (1 << 18) +#define RT2573_MRR_ENABLED (1 << 19) +#define RT2573_MRR_CCK_FALLBACK (1 << 22) + +/* possible flags for register TXRX_CSR9 */ +#define RT2573_TSF_TICKING (1 << 16) +#define RT2573_TSF_MODE(x) (((x) & 0x3) << 17) +/* TBTT stands for Target Beacon Transmission Time */ +#define RT2573_ENABLE_TBTT (1 << 19) +#define RT2573_GENERATE_BEACON (1 << 20) + +/* possible flags for register PHY_CSR0 */ +#define RT2573_PA_PE_2GHZ (1 << 16) +#define RT2573_PA_PE_5GHZ (1 << 17) + +/* possible flags for register PHY_CSR3 */ +#define RT2573_BBP_READ (1 << 15) +#define RT2573_BBP_BUSY (1 << 16) +/* possible flags for register PHY_CSR4 */ +#define RT2573_RF_20BIT (20 << 24) +#define RT2573_RF_BUSY (1 << 31) + +/* LED values */ +#define RT2573_LED_RADIO (1 << 8) +#define RT2573_LED_G (1 << 9) +#define RT2573_LED_A (1 << 10) +#define RT2573_LED_ON 0x1e1e +#define RT2573_LED_OFF 0x0 + +#define RT2573_MCU_RUN (1 << 3) + +#define RT2573_SMART_MODE (1 << 0) + +#define RT2573_BBPR94_DEFAULT 6 + +#define RT2573_BBP_WRITE (1 << 15) + +/* dual-band RF */ +#define RT2573_RF_5226 1 +#define RT2573_RF_5225 3 +/* single-band RF */ +#define RT2573_RF_2528 2 +#define RT2573_RF_2527 4 + +#define RT2573_BBP_VERSION 0 + +struct rum_tx_desc { + uint32_t flags; +#define RT2573_TX_BURST (1 << 0) +#define RT2573_TX_VALID (1 << 1) +#define RT2573_TX_MORE_FRAG (1 << 2) +#define RT2573_TX_NEED_ACK (1 << 3) +#define RT2573_TX_TIMESTAMP (1 << 4) +#define RT2573_TX_OFDM (1 << 5) +#define RT2573_TX_IFS_SIFS (1 << 6) +#define RT2573_TX_LONG_RETRY (1 << 7) + + uint16_t wme; +#define RT2573_QID(v) (v) +#define RT2573_AIFSN(v) ((v) << 4) +#define RT2573_LOGCWMIN(v) ((v) << 8) +#define RT2573_LOGCWMAX(v) ((v) << 12) + + uint16_t xflags; +#define RT2573_TX_HWSEQ (1 << 12) + + uint8_t plcp_signal; + uint8_t plcp_service; +#define RT2573_PLCP_LENGEXT 0x80 + + uint8_t plcp_length_lo; + uint8_t plcp_length_hi; + + uint32_t iv; + uint32_t eiv; + + uint8_t offset; + uint8_t qid; + uint8_t txpower; +#define RT2573_DEFAULT_TXPOWER 0 + + uint8_t reserved; +} __packed; + +struct rum_rx_desc { + uint32_t flags; +#define RT2573_RX_BUSY (1 << 0) +#define RT2573_RX_DROP (1 << 1) +#define RT2573_RX_CRC_ERROR (1 << 6) +#define RT2573_RX_OFDM (1 << 7) + + uint8_t rate; + uint8_t rssi; + uint8_t reserved1; + uint8_t offset; + uint32_t iv; + uint32_t eiv; + uint32_t reserved2[2]; +} __packed; + +#define RT2573_RF1 0 +#define RT2573_RF2 2 +#define RT2573_RF3 1 +#define RT2573_RF4 3 + +#define RT2573_EEPROM_MACBBP 0x0000 +#define RT2573_EEPROM_ADDRESS 0x0004 +#define RT2573_EEPROM_ANTENNA 0x0020 +#define RT2573_EEPROM_CONFIG2 0x0022 +#define RT2573_EEPROM_BBP_BASE 0x0026 +#define RT2573_EEPROM_TXPOWER 0x0046 +#define RT2573_EEPROM_FREQ_OFFSET 0x005e +#define RT2573_EEPROM_RSSI_2GHZ_OFFSET 0x009a +#define RT2573_EEPROM_RSSI_5GHZ_OFFSET 0x009c diff --git a/sys/bus/u4b/wlan/if_rumvar.h b/sys/bus/u4b/wlan/if_rumvar.h new file mode 100644 index 0000000000..f46634c268 --- /dev/null +++ b/sys/bus/u4b/wlan/if_rumvar.h @@ -0,0 +1,134 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005, 2006 Damien Bergamini + * Copyright (c) 2006 Niall O'Higgins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define RUM_TX_LIST_COUNT 8 +#define RUM_TX_MINFREE 2 + +struct rum_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint8_t wr_rate; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + int8_t wr_antsignal; + int8_t wr_antnoise; + uint8_t wr_antenna; +}; + +#define RT2573_RX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + 0) + +struct rum_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint8_t wt_rate; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; + uint8_t wt_antenna; +}; + +#define RT2573_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA)) + +struct rum_softc; + +struct rum_tx_data { + STAILQ_ENTRY(rum_tx_data) next; + struct rum_softc *sc; + struct rum_tx_desc desc; + struct mbuf *m; + struct ieee80211_node *ni; + int rate; +}; +typedef STAILQ_HEAD(, rum_tx_data) rum_txdhead; + +struct rum_vap { + struct ieee80211vap vap; + struct ieee80211_beacon_offsets bo; + struct usb_callout ratectl_ch; + struct task ratectl_task; + + int (*newstate)(struct ieee80211vap *, + enum ieee80211_state, int); +}; +#define RUM_VAP(vap) ((struct rum_vap *)(vap)) + +enum { + RUM_BULK_WR, + RUM_BULK_RD, + RUM_N_TRANSFER = 2, +}; + +struct rum_softc { + struct ifnet *sc_ifp; + device_t sc_dev; + struct usb_device *sc_udev; + + struct usb_xfer *sc_xfer[RUM_N_TRANSFER]; + + uint8_t rf_rev; + uint8_t rffreq; + + struct rum_tx_data tx_data[RUM_TX_LIST_COUNT]; + rum_txdhead tx_q; + rum_txdhead tx_free; + int tx_nfree; + struct rum_rx_desc sc_rx_desc; + + struct mtx sc_mtx; + + uint32_t sta[6]; + uint32_t rf_regs[4]; + uint8_t txpow[44]; + uint8_t sc_bssid[6]; + + struct { + uint8_t val; + uint8_t reg; + } __packed bbp_prom[16]; + + int hw_radio; + int rx_ant; + int tx_ant; + int nb_ant; + int ext_2ghz_lna; + int ext_5ghz_lna; + int rssi_2ghz_corr; + int rssi_5ghz_corr; + uint8_t bbp17; + + struct rum_rx_radiotap_header sc_rxtap; + int sc_rxtap_len; + + struct rum_tx_radiotap_header sc_txtap; + int sc_txtap_len; +}; + +#define RUM_LOCK(sc) mtx_lock(&(sc)->sc_mtx) +#define RUM_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) +#define RUM_LOCK_ASSERT(sc, t) mtx_assert(&(sc)->sc_mtx, t) diff --git a/sys/bus/u4b/wlan/if_run.c b/sys/bus/u4b/wlan/if_run.c new file mode 100644 index 0000000000..0a97c79383 --- /dev/null +++ b/sys/bus/u4b/wlan/if_run.c @@ -0,0 +1,4963 @@ +/*- + * Copyright (c) 2008,2010 Damien Bergamini + * ported to FreeBSD by Akinori Furukoshi + * USB Consulting, Hans Petter Selasky + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Ralink Technology RT2700U/RT2800U/RT3000U chipset driver. + * http://www.ralinktech.com/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR run_debug +#include + +#include +#include + +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) + +#ifdef USB_DEBUG +#define RUN_DEBUG +#endif + +#ifdef RUN_DEBUG +int run_debug = 0; +static SYSCTL_NODE(_hw_usb, OID_AUTO, run, CTLFLAG_RW, 0, "USB run"); +SYSCTL_INT(_hw_usb_run, OID_AUTO, debug, CTLFLAG_RW, &run_debug, 0, + "run debug level"); +#endif + +#define IEEE80211_HAS_ADDR4(wh) \ + (((wh)->i_fc[1] & IEEE80211_FC1_DIR_MASK) == IEEE80211_FC1_DIR_DSTODS) + +/* + * Because of LOR in run_key_delete(), use atomic instead. + * '& RUN_CMDQ_MASQ' is to loop cmdq[]. + */ +#define RUN_CMDQ_GET(c) (atomic_fetchadd_32((c), 1) & RUN_CMDQ_MASQ) + +static const STRUCT_USB_HOST_ID run_devs[] = { +#define RUN_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } + RUN_DEV(ABOCOM, RT2770), + RUN_DEV(ABOCOM, RT2870), + RUN_DEV(ABOCOM, RT3070), + RUN_DEV(ABOCOM, RT3071), + RUN_DEV(ABOCOM, RT3072), + RUN_DEV(ABOCOM2, RT2870_1), + RUN_DEV(ACCTON, RT2770), + RUN_DEV(ACCTON, RT2870_1), + RUN_DEV(ACCTON, RT2870_2), + RUN_DEV(ACCTON, RT2870_3), + RUN_DEV(ACCTON, RT2870_4), + RUN_DEV(ACCTON, RT2870_5), + RUN_DEV(ACCTON, RT3070), + RUN_DEV(ACCTON, RT3070_1), + RUN_DEV(ACCTON, RT3070_2), + RUN_DEV(ACCTON, RT3070_3), + RUN_DEV(ACCTON, RT3070_4), + RUN_DEV(ACCTON, RT3070_5), + RUN_DEV(AIRTIES, RT3070), + RUN_DEV(ALLWIN, RT2070), + RUN_DEV(ALLWIN, RT2770), + RUN_DEV(ALLWIN, RT2870), + RUN_DEV(ALLWIN, RT3070), + RUN_DEV(ALLWIN, RT3071), + RUN_DEV(ALLWIN, RT3072), + RUN_DEV(ALLWIN, RT3572), + RUN_DEV(AMIGO, RT2870_1), + RUN_DEV(AMIGO, RT2870_2), + RUN_DEV(AMIT, CGWLUSB2GNR), + RUN_DEV(AMIT, RT2870_1), + RUN_DEV(AMIT2, RT2870), + RUN_DEV(ASUS, RT2870_1), + RUN_DEV(ASUS, RT2870_2), + RUN_DEV(ASUS, RT2870_3), + RUN_DEV(ASUS, RT2870_4), + RUN_DEV(ASUS, RT2870_5), + RUN_DEV(ASUS, USBN13), + RUN_DEV(ASUS, RT3070_1), + RUN_DEV(ASUS2, USBN11), + RUN_DEV(AZUREWAVE, RT2870_1), + RUN_DEV(AZUREWAVE, RT2870_2), + RUN_DEV(AZUREWAVE, RT3070_1), + RUN_DEV(AZUREWAVE, RT3070_2), + RUN_DEV(AZUREWAVE, RT3070_3), + RUN_DEV(BELKIN, F5D8053V3), + RUN_DEV(BELKIN, F5D8055), + RUN_DEV(BELKIN, F5D8055V2), + RUN_DEV(BELKIN, F6D4050V1), + RUN_DEV(BELKIN, RT2870_1), + RUN_DEV(BELKIN, RT2870_2), + RUN_DEV(CISCOLINKSYS, AE1000), + RUN_DEV(CISCOLINKSYS2, RT3070), + RUN_DEV(CISCOLINKSYS3, RT3070), + RUN_DEV(CONCEPTRONIC2, RT2870_1), + RUN_DEV(CONCEPTRONIC2, RT2870_2), + RUN_DEV(CONCEPTRONIC2, RT2870_3), + RUN_DEV(CONCEPTRONIC2, RT2870_4), + RUN_DEV(CONCEPTRONIC2, RT2870_5), + RUN_DEV(CONCEPTRONIC2, RT2870_6), + RUN_DEV(CONCEPTRONIC2, RT2870_7), + RUN_DEV(CONCEPTRONIC2, RT2870_8), + RUN_DEV(CONCEPTRONIC2, RT3070_1), + RUN_DEV(CONCEPTRONIC2, RT3070_2), + RUN_DEV(CONCEPTRONIC2, VIGORN61), + RUN_DEV(COREGA, CGWLUSB300GNM), + RUN_DEV(COREGA, RT2870_1), + RUN_DEV(COREGA, RT2870_2), + RUN_DEV(COREGA, RT2870_3), + RUN_DEV(COREGA, RT3070), + RUN_DEV(CYBERTAN, RT2870), + RUN_DEV(DLINK, RT2870), + RUN_DEV(DLINK, RT3072), + RUN_DEV(DLINK2, DWA130), + RUN_DEV(DLINK2, RT2870_1), + RUN_DEV(DLINK2, RT2870_2), + RUN_DEV(DLINK2, RT3070_1), + RUN_DEV(DLINK2, RT3070_2), + RUN_DEV(DLINK2, RT3070_3), + RUN_DEV(DLINK2, RT3070_4), + RUN_DEV(DLINK2, RT3070_5), + RUN_DEV(DLINK2, RT3072), + RUN_DEV(DLINK2, RT3072_1), + RUN_DEV(EDIMAX, EW7717), + RUN_DEV(EDIMAX, EW7718), + RUN_DEV(EDIMAX, RT2870_1), + RUN_DEV(ENCORE, RT3070_1), + RUN_DEV(ENCORE, RT3070_2), + RUN_DEV(ENCORE, RT3070_3), + RUN_DEV(GIGABYTE, GNWB31N), + RUN_DEV(GIGABYTE, GNWB32L), + RUN_DEV(GIGABYTE, RT2870_1), + RUN_DEV(GIGASET, RT3070_1), + RUN_DEV(GIGASET, RT3070_2), + RUN_DEV(GUILLEMOT, HWNU300), + RUN_DEV(HAWKING, HWUN2), + RUN_DEV(HAWKING, RT2870_1), + RUN_DEV(HAWKING, RT2870_2), + RUN_DEV(HAWKING, RT3070), + RUN_DEV(IODATA, RT3072_1), + RUN_DEV(IODATA, RT3072_2), + RUN_DEV(IODATA, RT3072_3), + RUN_DEV(IODATA, RT3072_4), + RUN_DEV(LINKSYS4, RT3070), + RUN_DEV(LINKSYS4, WUSB100), + RUN_DEV(LINKSYS4, WUSB54GCV3), + RUN_DEV(LINKSYS4, WUSB600N), + RUN_DEV(LINKSYS4, WUSB600NV2), + RUN_DEV(LOGITEC, RT2870_1), + RUN_DEV(LOGITEC, RT2870_2), + RUN_DEV(LOGITEC, RT2870_3), + RUN_DEV(LOGITEC, LANW300NU2), + RUN_DEV(MELCO, RT2870_1), + RUN_DEV(MELCO, RT2870_2), + RUN_DEV(MELCO, WLIUCAG300N), + RUN_DEV(MELCO, WLIUCG300N), + RUN_DEV(MELCO, WLIUCG301N), + RUN_DEV(MELCO, WLIUCGN), + RUN_DEV(MELCO, WLIUCGNM), + RUN_DEV(MOTOROLA4, RT2770), + RUN_DEV(MOTOROLA4, RT3070), + RUN_DEV(MSI, RT3070_1), + RUN_DEV(MSI, RT3070_2), + RUN_DEV(MSI, RT3070_3), + RUN_DEV(MSI, RT3070_4), + RUN_DEV(MSI, RT3070_5), + RUN_DEV(MSI, RT3070_6), + RUN_DEV(MSI, RT3070_7), + RUN_DEV(MSI, RT3070_8), + RUN_DEV(MSI, RT3070_9), + RUN_DEV(MSI, RT3070_10), + RUN_DEV(MSI, RT3070_11), + RUN_DEV(OVISLINK, RT3072), + RUN_DEV(PARA, RT3070), + RUN_DEV(PEGATRON, RT2870), + RUN_DEV(PEGATRON, RT3070), + RUN_DEV(PEGATRON, RT3070_2), + RUN_DEV(PEGATRON, RT3070_3), + RUN_DEV(PHILIPS, RT2870), + RUN_DEV(PLANEX2, GWUS300MINIS), + RUN_DEV(PLANEX2, GWUSMICRON), + RUN_DEV(PLANEX2, RT2870), + RUN_DEV(PLANEX2, RT3070), + RUN_DEV(QCOM, RT2870), + RUN_DEV(QUANTA, RT3070), + RUN_DEV(RALINK, RT2070), + RUN_DEV(RALINK, RT2770), + RUN_DEV(RALINK, RT2870), + RUN_DEV(RALINK, RT3070), + RUN_DEV(RALINK, RT3071), + RUN_DEV(RALINK, RT3072), + RUN_DEV(RALINK, RT3370), + RUN_DEV(RALINK, RT3572), + RUN_DEV(RALINK, RT8070), + RUN_DEV(SAMSUNG, WIS09ABGN), + RUN_DEV(SAMSUNG2, RT2870_1), + RUN_DEV(SENAO, RT2870_1), + RUN_DEV(SENAO, RT2870_2), + RUN_DEV(SENAO, RT2870_3), + RUN_DEV(SENAO, RT2870_4), + RUN_DEV(SENAO, RT3070), + RUN_DEV(SENAO, RT3071), + RUN_DEV(SENAO, RT3072_1), + RUN_DEV(SENAO, RT3072_2), + RUN_DEV(SENAO, RT3072_3), + RUN_DEV(SENAO, RT3072_4), + RUN_DEV(SENAO, RT3072_5), + RUN_DEV(SITECOMEU, RT2770), + RUN_DEV(SITECOMEU, RT2870_1), + RUN_DEV(SITECOMEU, RT2870_2), + RUN_DEV(SITECOMEU, RT2870_3), + RUN_DEV(SITECOMEU, RT2870_4), + RUN_DEV(SITECOMEU, RT3070), + RUN_DEV(SITECOMEU, RT3070_2), + RUN_DEV(SITECOMEU, RT3070_3), + RUN_DEV(SITECOMEU, RT3070_4), + RUN_DEV(SITECOMEU, RT3071), + RUN_DEV(SITECOMEU, RT3072_1), + RUN_DEV(SITECOMEU, RT3072_2), + RUN_DEV(SITECOMEU, RT3072_3), + RUN_DEV(SITECOMEU, RT3072_4), + RUN_DEV(SITECOMEU, RT3072_5), + RUN_DEV(SITECOMEU, RT3072_6), + RUN_DEV(SITECOMEU, WL608), + RUN_DEV(SPARKLAN, RT2870_1), + RUN_DEV(SPARKLAN, RT3070), + RUN_DEV(SWEEX2, LW153), + RUN_DEV(SWEEX2, LW303), + RUN_DEV(SWEEX2, LW313), + RUN_DEV(TOSHIBA, RT3070), + RUN_DEV(UMEDIA, RT2870_1), + RUN_DEV(ZCOM, RT2870_1), + RUN_DEV(ZCOM, RT2870_2), + RUN_DEV(ZINWELL, RT2870_1), + RUN_DEV(ZINWELL, RT2870_2), + RUN_DEV(ZINWELL, RT3070), + RUN_DEV(ZINWELL, RT3072_1), + RUN_DEV(ZINWELL, RT3072_2), + RUN_DEV(ZYXEL, RT2870_1), + RUN_DEV(ZYXEL, RT2870_2), +#undef RUN_DEV +}; + +static device_probe_t run_match; +static device_attach_t run_attach; +static device_detach_t run_detach; + +static usb_callback_t run_bulk_rx_callback; +static usb_callback_t run_bulk_tx_callback0; +static usb_callback_t run_bulk_tx_callback1; +static usb_callback_t run_bulk_tx_callback2; +static usb_callback_t run_bulk_tx_callback3; +static usb_callback_t run_bulk_tx_callback4; +static usb_callback_t run_bulk_tx_callback5; + +static void run_bulk_tx_callbackN(struct usb_xfer *xfer, + usb_error_t error, unsigned int index); +static struct ieee80211vap *run_vap_create(struct ieee80211com *, + const char [IFNAMSIZ], int, enum ieee80211_opmode, int, + const uint8_t [IEEE80211_ADDR_LEN], + const uint8_t [IEEE80211_ADDR_LEN]); +static void run_vap_delete(struct ieee80211vap *); +static void run_cmdq_cb(void *, int); +static void run_setup_tx_list(struct run_softc *, + struct run_endpoint_queue *); +static void run_unsetup_tx_list(struct run_softc *, + struct run_endpoint_queue *); +static int run_load_microcode(struct run_softc *); +static int run_reset(struct run_softc *); +static usb_error_t run_do_request(struct run_softc *, + struct usb_device_request *, void *); +static int run_read(struct run_softc *, uint16_t, uint32_t *); +static int run_read_region_1(struct run_softc *, uint16_t, uint8_t *, int); +static int run_write_2(struct run_softc *, uint16_t, uint16_t); +static int run_write(struct run_softc *, uint16_t, uint32_t); +static int run_write_region_1(struct run_softc *, uint16_t, + const uint8_t *, int); +static int run_set_region_4(struct run_softc *, uint16_t, uint32_t, int); +static int run_efuse_read_2(struct run_softc *, uint16_t, uint16_t *); +static int run_eeprom_read_2(struct run_softc *, uint16_t, uint16_t *); +static int run_rt2870_rf_write(struct run_softc *, uint8_t, uint32_t); +static int run_rt3070_rf_read(struct run_softc *, uint8_t, uint8_t *); +static int run_rt3070_rf_write(struct run_softc *, uint8_t, uint8_t); +static int run_bbp_read(struct run_softc *, uint8_t, uint8_t *); +static int run_bbp_write(struct run_softc *, uint8_t, uint8_t); +static int run_mcu_cmd(struct run_softc *, uint8_t, uint16_t); +static const char *run_get_rf(int); +static int run_read_eeprom(struct run_softc *); +static struct ieee80211_node *run_node_alloc(struct ieee80211vap *, + const uint8_t mac[IEEE80211_ADDR_LEN]); +static int run_media_change(struct ifnet *); +static int run_newstate(struct ieee80211vap *, enum ieee80211_state, int); +static int run_wme_update(struct ieee80211com *); +static void run_wme_update_cb(void *); +static void run_key_update_begin(struct ieee80211vap *); +static void run_key_update_end(struct ieee80211vap *); +static void run_key_set_cb(void *); +static int run_key_set(struct ieee80211vap *, struct ieee80211_key *, + const uint8_t mac[IEEE80211_ADDR_LEN]); +static void run_key_delete_cb(void *); +static int run_key_delete(struct ieee80211vap *, struct ieee80211_key *); +static void run_ratectl_to(void *); +static void run_ratectl_cb(void *, int); +static void run_drain_fifo(void *); +static void run_iter_func(void *, struct ieee80211_node *); +static void run_newassoc_cb(void *); +static void run_newassoc(struct ieee80211_node *, int); +static void run_rx_frame(struct run_softc *, struct mbuf *, uint32_t); +static void run_tx_free(struct run_endpoint_queue *pq, + struct run_tx_data *, int); +static void run_set_tx_desc(struct run_softc *, struct run_tx_data *); +static int run_tx(struct run_softc *, struct mbuf *, + struct ieee80211_node *); +static int run_tx_mgt(struct run_softc *, struct mbuf *, + struct ieee80211_node *); +static int run_sendprot(struct run_softc *, const struct mbuf *, + struct ieee80211_node *, int, int); +static int run_tx_param(struct run_softc *, struct mbuf *, + struct ieee80211_node *, + const struct ieee80211_bpf_params *); +static int run_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static void run_start(struct ifnet *); +static int run_ioctl(struct ifnet *, u_long, caddr_t); +static void run_set_agc(struct run_softc *, uint8_t); +static void run_select_chan_group(struct run_softc *, int); +static void run_set_rx_antenna(struct run_softc *, int); +static void run_rt2870_set_chan(struct run_softc *, u_int); +static void run_rt3070_set_chan(struct run_softc *, u_int); +static void run_rt3572_set_chan(struct run_softc *, u_int); +static int run_set_chan(struct run_softc *, struct ieee80211_channel *); +static void run_set_channel(struct ieee80211com *); +static void run_scan_start(struct ieee80211com *); +static void run_scan_end(struct ieee80211com *); +static void run_update_beacon(struct ieee80211vap *, int); +static void run_update_beacon_cb(void *); +static void run_updateprot(struct ieee80211com *); +static void run_updateprot_cb(void *); +static void run_usb_timeout_cb(void *); +static void run_reset_livelock(struct run_softc *); +static void run_enable_tsf_sync(struct run_softc *); +static void run_enable_mrr(struct run_softc *); +static void run_set_txpreamble(struct run_softc *); +static void run_set_basicrates(struct run_softc *); +static void run_set_leds(struct run_softc *, uint16_t); +static void run_set_bssid(struct run_softc *, const uint8_t *); +static void run_set_macaddr(struct run_softc *, const uint8_t *); +static void run_updateslot(struct ifnet *); +static void run_updateslot_cb(void *); +static void run_update_mcast(struct ifnet *); +static int8_t run_rssi2dbm(struct run_softc *, uint8_t, uint8_t); +static void run_update_promisc_locked(struct ifnet *); +static void run_update_promisc(struct ifnet *); +static int run_bbp_init(struct run_softc *); +static int run_rt3070_rf_init(struct run_softc *); +static int run_rt3070_filter_calib(struct run_softc *, uint8_t, uint8_t, + uint8_t *); +static void run_rt3070_rf_setup(struct run_softc *); +static int run_txrx_enable(struct run_softc *); +static void run_init(void *); +static void run_init_locked(struct run_softc *); +static void run_stop(void *); +static void run_delay(struct run_softc *, unsigned int); + +static const struct { + uint16_t reg; + uint32_t val; +} rt2870_def_mac[] = { + RT2870_DEF_MAC +}; + +static const struct { + uint8_t reg; + uint8_t val; +} rt2860_def_bbp[] = { + RT2860_DEF_BBP +}; + +static const struct rfprog { + uint8_t chan; + uint32_t r1, r2, r3, r4; +} rt2860_rf2850[] = { + RT2860_RF2850 +}; + +struct { + uint8_t n, r, k; +} rt3070_freqs[] = { + RT3070_RF3052 +}; + +static const struct { + uint8_t reg; + uint8_t val; +} rt3070_def_rf[] = { + RT3070_DEF_RF +},rt3572_def_rf[] = { + RT3572_DEF_RF +}; + +static const struct usb_config run_config[RUN_N_XFER] = { + [RUN_BULK_TX_BE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .ep_index = 0, + .direction = UE_DIR_OUT, + .bufsize = RUN_MAX_TXSZ, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = run_bulk_tx_callback0, + .timeout = 5000, /* ms */ + }, + [RUN_BULK_TX_BK] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .ep_index = 1, + .bufsize = RUN_MAX_TXSZ, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = run_bulk_tx_callback1, + .timeout = 5000, /* ms */ + }, + [RUN_BULK_TX_VI] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .ep_index = 2, + .bufsize = RUN_MAX_TXSZ, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = run_bulk_tx_callback2, + .timeout = 5000, /* ms */ + }, + [RUN_BULK_TX_VO] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .ep_index = 3, + .bufsize = RUN_MAX_TXSZ, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = run_bulk_tx_callback3, + .timeout = 5000, /* ms */ + }, + [RUN_BULK_TX_HCCA] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .ep_index = 4, + .bufsize = RUN_MAX_TXSZ, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,}, + .callback = run_bulk_tx_callback4, + .timeout = 5000, /* ms */ + }, + [RUN_BULK_TX_PRIO] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .ep_index = 5, + .bufsize = RUN_MAX_TXSZ, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,}, + .callback = run_bulk_tx_callback5, + .timeout = 5000, /* ms */ + }, + [RUN_BULK_RX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = RUN_MAX_RXSZ, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = run_bulk_rx_callback, + } +}; + +static int +run_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != 0) + return (ENXIO); + if (uaa->info.bIfaceIndex != RT2860_IFACE_INDEX) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(run_devs, sizeof(run_devs), uaa)); +} + +static int +run_attach(device_t self) +{ + struct run_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + struct ieee80211com *ic; + struct ifnet *ifp; + uint32_t ver; + int i, ntries, error; + uint8_t iface_index, bands; + + device_set_usb_desc(self); + sc->sc_udev = uaa->device; + sc->sc_dev = self; + + mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), + MTX_NETWORK_LOCK, MTX_DEF); + + iface_index = RT2860_IFACE_INDEX; + + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, run_config, RUN_N_XFER, sc, &sc->sc_mtx); + if (error) { + device_printf(self, "could not allocate USB transfers, " + "err=%s\n", usbd_errstr(error)); + goto detach; + } + + RUN_LOCK(sc); + + /* wait for the chip to settle */ + for (ntries = 0; ntries < 100; ntries++) { + if (run_read(sc, RT2860_ASIC_VER_ID, &ver) != 0) { + RUN_UNLOCK(sc); + goto detach; + } + if (ver != 0 && ver != 0xffffffff) + break; + run_delay(sc, 10); + } + if (ntries == 100) { + device_printf(sc->sc_dev, + "timeout waiting for NIC to initialize\n"); + RUN_UNLOCK(sc); + goto detach; + } + sc->mac_ver = ver >> 16; + sc->mac_rev = ver & 0xffff; + + /* retrieve RF rev. no and various other things from EEPROM */ + run_read_eeprom(sc); + + device_printf(sc->sc_dev, + "MAC/BBP RT%04X (rev 0x%04X), RF %s (MIMO %dT%dR), address %s\n", + sc->mac_ver, sc->mac_rev, run_get_rf(sc->rf_rev), + sc->ntxchains, sc->nrxchains, ether_sprintf(sc->sc_bssid)); + + if ((error = run_load_microcode(sc)) != 0) { + device_printf(sc->sc_dev, "could not load 8051 microcode\n"); + RUN_UNLOCK(sc); + goto detach; + } + + RUN_UNLOCK(sc); + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(sc->sc_dev, "can not if_alloc()\n"); + goto detach; + } + ic = ifp->if_l2com; + + ifp->if_softc = sc; + if_initname(ifp, "run", device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_init = run_init; + ifp->if_ioctl = run_ioctl; + ifp->if_start = run_start; + IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); + ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; + IFQ_SET_READY(&ifp->if_snd); + + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */ + + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA | /* station mode supported */ + IEEE80211_C_MONITOR | /* monitor mode supported */ + IEEE80211_C_IBSS | + IEEE80211_C_HOSTAP | + IEEE80211_C_WDS | /* 4-address traffic works */ + IEEE80211_C_MBSS | + IEEE80211_C_SHPREAMBLE | /* short preamble supported */ + IEEE80211_C_SHSLOT | /* short slot time supported */ + IEEE80211_C_WME | /* WME */ + IEEE80211_C_WPA; /* WPA1|WPA2(RSN) */ + + ic->ic_cryptocaps = + IEEE80211_CRYPTO_WEP | + IEEE80211_CRYPTO_AES_CCM | + IEEE80211_CRYPTO_TKIPMIC | + IEEE80211_CRYPTO_TKIP; + + ic->ic_flags |= IEEE80211_F_DATAPAD; + ic->ic_flags_ext |= IEEE80211_FEXT_SWBMISS; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + ieee80211_init_channels(ic, NULL, &bands); + + /* + * Do this by own because h/w supports + * more channels than ieee80211_init_channels() + */ + if (sc->rf_rev == RT2860_RF_2750 || + sc->rf_rev == RT2860_RF_2850 || + sc->rf_rev == RT3070_RF_3052) { + /* set supported .11a rates */ + for (i = 14; i < nitems(rt2860_rf2850); i++) { + uint8_t chan = rt2860_rf2850[i].chan; + ic->ic_channels[ic->ic_nchans].ic_freq = + ieee80211_ieee2mhz(chan, IEEE80211_CHAN_A); + ic->ic_channels[ic->ic_nchans].ic_ieee = chan; + ic->ic_channels[ic->ic_nchans].ic_flags = IEEE80211_CHAN_A; + ic->ic_channels[ic->ic_nchans].ic_extieee = 0; + ic->ic_nchans++; + } + } + + ieee80211_ifattach(ic, sc->sc_bssid); + + ic->ic_scan_start = run_scan_start; + ic->ic_scan_end = run_scan_end; + ic->ic_set_channel = run_set_channel; + ic->ic_node_alloc = run_node_alloc; + ic->ic_newassoc = run_newassoc; + ic->ic_updateslot = run_updateslot; + ic->ic_update_mcast = run_update_mcast; + ic->ic_wme.wme_update = run_wme_update; + ic->ic_raw_xmit = run_raw_xmit; + ic->ic_update_promisc = run_update_promisc; + + ic->ic_vap_create = run_vap_create; + ic->ic_vap_delete = run_vap_delete; + + ieee80211_radiotap_attach(ic, + &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), + RUN_TX_RADIOTAP_PRESENT, + &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), + RUN_RX_RADIOTAP_PRESENT); + + TASK_INIT(&sc->cmdq_task, 0, run_cmdq_cb, sc); + TASK_INIT(&sc->ratectl_task, 0, run_ratectl_cb, sc); + callout_init((struct callout *)&sc->ratectl_ch, 1); + + if (bootverbose) + ieee80211_announce(ic); + + return (0); + +detach: + run_detach(self); + return (ENXIO); +} + +static int +run_detach(device_t self) +{ + struct run_softc *sc = device_get_softc(self); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic; + int i; + + /* stop all USB transfers */ + usbd_transfer_unsetup(sc->sc_xfer, RUN_N_XFER); + + RUN_LOCK(sc); + + sc->ratectl_run = RUN_RATECTL_OFF; + sc->cmdq_run = sc->cmdq_key_set = RUN_CMDQ_ABORT; + + /* free TX list, if any */ + for (i = 0; i != RUN_EP_QUEUES; i++) + run_unsetup_tx_list(sc, &sc->sc_epq[i]); + RUN_UNLOCK(sc); + + if (ifp) { + ic = ifp->if_l2com; + /* drain tasks */ + usb_callout_drain(&sc->ratectl_ch); + ieee80211_draintask(ic, &sc->cmdq_task); + ieee80211_draintask(ic, &sc->ratectl_task); + ieee80211_ifdetach(ic); + if_free(ifp); + } + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static struct ieee80211vap * +run_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, + enum ieee80211_opmode opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct ifnet *ifp = ic->ic_ifp; + struct run_softc *sc = ifp->if_softc; + struct run_vap *rvp; + struct ieee80211vap *vap; + int i; + + if (sc->rvp_cnt >= RUN_VAP_MAX) { + if_printf(ifp, "number of VAPs maxed out\n"); + return (NULL); + } + + switch (opmode) { + case IEEE80211_M_STA: + /* enable s/w bmiss handling for sta mode */ + flags |= IEEE80211_CLONE_NOBEACONS; + /* fall though */ + case IEEE80211_M_IBSS: + case IEEE80211_M_MONITOR: + case IEEE80211_M_HOSTAP: + case IEEE80211_M_MBSS: + /* other than WDS vaps, only one at a time */ + if (!TAILQ_EMPTY(&ic->ic_vaps)) + return (NULL); + break; + case IEEE80211_M_WDS: + TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next){ + if(vap->iv_opmode != IEEE80211_M_HOSTAP) + continue; + /* WDS vap's always share the local mac address. */ + flags &= ~IEEE80211_CLONE_BSSID; + break; + } + if (vap == NULL) { + if_printf(ifp, "wds only supported in ap mode\n"); + return (NULL); + } + break; + default: + if_printf(ifp, "unknown opmode %d\n", opmode); + return (NULL); + } + + rvp = (struct run_vap *) malloc(sizeof(struct run_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (rvp == NULL) + return (NULL); + vap = &rvp->vap; + ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid, mac); + + vap->iv_key_update_begin = run_key_update_begin; + vap->iv_key_update_end = run_key_update_end; + vap->iv_update_beacon = run_update_beacon; + vap->iv_max_aid = RT2870_WCID_MAX; + /* + * To delete the right key from h/w, we need wcid. + * Luckily, there is unused space in ieee80211_key{}, wk_pad, + * and matching wcid will be written into there. So, cast + * some spells to remove 'const' from ieee80211_key{} + */ + vap->iv_key_delete = (void *)run_key_delete; + vap->iv_key_set = (void *)run_key_set; + + /* override state transition machine */ + rvp->newstate = vap->iv_newstate; + vap->iv_newstate = run_newstate; + + ieee80211_ratectl_init(vap); + ieee80211_ratectl_setinterval(vap, 1000 /* 1 sec */); + + /* complete setup */ + ieee80211_vap_attach(vap, run_media_change, ieee80211_media_status); + + /* make sure id is always unique */ + for (i = 0; i < RUN_VAP_MAX; i++) { + if((sc->rvp_bmap & 1 << i) == 0){ + sc->rvp_bmap |= 1 << i; + rvp->rvp_id = i; + break; + } + } + if (sc->rvp_cnt++ == 0) + ic->ic_opmode = opmode; + + if (opmode == IEEE80211_M_HOSTAP) + sc->cmdq_run = RUN_CMDQ_GO; + + DPRINTF("rvp_id=%d bmap=%x rvp_cnt=%d\n", + rvp->rvp_id, sc->rvp_bmap, sc->rvp_cnt); + + return (vap); +} + +static void +run_vap_delete(struct ieee80211vap *vap) +{ + struct run_vap *rvp = RUN_VAP(vap); + struct ifnet *ifp; + struct ieee80211com *ic; + struct run_softc *sc; + uint8_t rvp_id; + + if (vap == NULL) + return; + + ic = vap->iv_ic; + ifp = ic->ic_ifp; + + sc = ifp->if_softc; + + RUN_LOCK(sc); + + m_freem(rvp->beacon_mbuf); + rvp->beacon_mbuf = NULL; + + rvp_id = rvp->rvp_id; + sc->ratectl_run &= ~(1 << rvp_id); + sc->rvp_bmap &= ~(1 << rvp_id); + run_set_region_4(sc, RT2860_SKEY(rvp_id, 0), 0, 128); + run_set_region_4(sc, RT2860_BCN_BASE(rvp_id), 0, 512); + --sc->rvp_cnt; + + DPRINTF("vap=%p rvp_id=%d bmap=%x rvp_cnt=%d\n", + vap, rvp_id, sc->rvp_bmap, sc->rvp_cnt); + + RUN_UNLOCK(sc); + + ieee80211_ratectl_deinit(vap); + ieee80211_vap_detach(vap); + free(rvp, M_80211_VAP); +} + +/* + * There are numbers of functions need to be called in context thread. + * Rather than creating taskqueue event for each of those functions, + * here is all-for-one taskqueue callback function. This function + * gurantees deferred functions are executed in the same order they + * were enqueued. + * '& RUN_CMDQ_MASQ' is to loop cmdq[]. + */ +static void +run_cmdq_cb(void *arg, int pending) +{ + struct run_softc *sc = arg; + uint8_t i; + + /* call cmdq[].func locked */ + RUN_LOCK(sc); + for (i = sc->cmdq_exec; sc->cmdq[i].func && pending; + i = sc->cmdq_exec, pending--) { + DPRINTFN(6, "cmdq_exec=%d pending=%d\n", i, pending); + if (sc->cmdq_run == RUN_CMDQ_GO) { + /* + * If arg0 is NULL, callback func needs more + * than one arg. So, pass ptr to cmdq struct. + */ + if (sc->cmdq[i].arg0) + sc->cmdq[i].func(sc->cmdq[i].arg0); + else + sc->cmdq[i].func(&sc->cmdq[i]); + } + sc->cmdq[i].arg0 = NULL; + sc->cmdq[i].func = NULL; + sc->cmdq_exec++; + sc->cmdq_exec &= RUN_CMDQ_MASQ; + } + RUN_UNLOCK(sc); +} + +static void +run_setup_tx_list(struct run_softc *sc, struct run_endpoint_queue *pq) +{ + struct run_tx_data *data; + + memset(pq, 0, sizeof(*pq)); + + STAILQ_INIT(&pq->tx_qh); + STAILQ_INIT(&pq->tx_fh); + + for (data = &pq->tx_data[0]; + data < &pq->tx_data[RUN_TX_RING_COUNT]; data++) { + data->sc = sc; + STAILQ_INSERT_TAIL(&pq->tx_fh, data, next); + } + pq->tx_nfree = RUN_TX_RING_COUNT; +} + +static void +run_unsetup_tx_list(struct run_softc *sc, struct run_endpoint_queue *pq) +{ + struct run_tx_data *data; + + /* make sure any subsequent use of the queues will fail */ + pq->tx_nfree = 0; + STAILQ_INIT(&pq->tx_fh); + STAILQ_INIT(&pq->tx_qh); + + /* free up all node references and mbufs */ + for (data = &pq->tx_data[0]; + data < &pq->tx_data[RUN_TX_RING_COUNT]; data++) { + if (data->m != NULL) { + m_freem(data->m); + data->m = NULL; + } + if (data->ni != NULL) { + ieee80211_free_node(data->ni); + data->ni = NULL; + } + } +} + +static int +run_load_microcode(struct run_softc *sc) +{ + usb_device_request_t req; + const struct firmware *fw; + const u_char *base; + uint32_t tmp; + int ntries, error; + const uint64_t *temp; + uint64_t bytes; + + RUN_UNLOCK(sc); + fw = firmware_get("runfw"); + RUN_LOCK(sc); + if (fw == NULL) { + device_printf(sc->sc_dev, + "failed loadfirmware of file %s\n", "runfw"); + return ENOENT; + } + + if (fw->datasize != 8192) { + device_printf(sc->sc_dev, + "invalid firmware size (should be 8KB)\n"); + error = EINVAL; + goto fail; + } + + /* + * RT3071/RT3072 use a different firmware + * run-rt2870 (8KB) contains both, + * first half (4KB) is for rt2870, + * last half is for rt3071. + */ + base = fw->data; + if ((sc->mac_ver) != 0x2860 && + (sc->mac_ver) != 0x2872 && + (sc->mac_ver) != 0x3070) { + base += 4096; + } + + /* cheap sanity check */ + temp = fw->data; + bytes = *temp; + if (bytes != be64toh(0xffffff0210280210)) { + device_printf(sc->sc_dev, "firmware checksum failed\n"); + error = EINVAL; + goto fail; + } + + run_read(sc, RT2860_ASIC_VER_ID, &tmp); + /* write microcode image */ + run_write_region_1(sc, RT2870_FW_BASE, base, 4096); + run_write(sc, RT2860_H2M_MAILBOX_CID, 0xffffffff); + run_write(sc, RT2860_H2M_MAILBOX_STATUS, 0xffffffff); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RT2870_RESET; + USETW(req.wValue, 8); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + if ((error = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)) + != 0) { + device_printf(sc->sc_dev, "firmware reset failed\n"); + goto fail; + } + + run_delay(sc, 10); + + run_write(sc, RT2860_H2M_MAILBOX, 0); + if ((error = run_mcu_cmd(sc, RT2860_MCU_CMD_RFRESET, 0)) != 0) + goto fail; + + /* wait until microcontroller is ready */ + for (ntries = 0; ntries < 1000; ntries++) { + if ((error = run_read(sc, RT2860_SYS_CTRL, &tmp)) != 0) { + goto fail; + } + if (tmp & RT2860_MCU_READY) + break; + run_delay(sc, 10); + } + if (ntries == 1000) { + device_printf(sc->sc_dev, + "timeout waiting for MCU to initialize\n"); + error = ETIMEDOUT; + goto fail; + } + device_printf(sc->sc_dev, "firmware %s loaded\n", + (base == fw->data) ? "RT2870" : "RT3071"); + +fail: + firmware_put(fw, FIRMWARE_UNLOAD); + return (error); +} + +int +run_reset(struct run_softc *sc) +{ + usb_device_request_t req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RT2870_RESET; + USETW(req.wValue, 1); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); +} + +static usb_error_t +run_do_request(struct run_softc *sc, + struct usb_device_request *req, void *data) +{ + usb_error_t err; + int ntries = 10; + + RUN_LOCK_ASSERT(sc, MA_OWNED); + + while (ntries--) { + err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, + req, data, 0, NULL, 250 /* ms */); + if (err == 0) + break; + DPRINTFN(1, "Control request failed, %s (retrying)\n", + usbd_errstr(err)); + run_delay(sc, 10); + } + return (err); +} + +static int +run_read(struct run_softc *sc, uint16_t reg, uint32_t *val) +{ + uint32_t tmp; + int error; + + error = run_read_region_1(sc, reg, (uint8_t *)&tmp, sizeof tmp); + if (error == 0) + *val = le32toh(tmp); + else + *val = 0xffffffff; + return (error); +} + +static int +run_read_region_1(struct run_softc *sc, uint16_t reg, uint8_t *buf, int len) +{ + usb_device_request_t req; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RT2870_READ_REGION_1; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, len); + + return (run_do_request(sc, &req, buf)); +} + +static int +run_write_2(struct run_softc *sc, uint16_t reg, uint16_t val) +{ + usb_device_request_t req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RT2870_WRITE_2; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + return (run_do_request(sc, &req, NULL)); +} + +static int +run_write(struct run_softc *sc, uint16_t reg, uint32_t val) +{ + int error; + + if ((error = run_write_2(sc, reg, val & 0xffff)) == 0) + error = run_write_2(sc, reg + 2, val >> 16); + return (error); +} + +static int +run_write_region_1(struct run_softc *sc, uint16_t reg, const uint8_t *buf, + int len) +{ +#if 1 + int i, error = 0; + /* + * NB: the WRITE_REGION_1 command is not stable on RT2860. + * We thus issue multiple WRITE_2 commands instead. + */ + KASSERT((len & 1) == 0, ("run_write_region_1: Data too long.\n")); + for (i = 0; i < len && error == 0; i += 2) + error = run_write_2(sc, reg + i, buf[i] | buf[i + 1] << 8); + return (error); +#else + usb_device_request_t req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RT2870_WRITE_REGION_1; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, len); + return (run_do_request(sc, &req, buf)); +#endif +} + +static int +run_set_region_4(struct run_softc *sc, uint16_t reg, uint32_t val, int len) +{ + int i, error = 0; + + KASSERT((len & 3) == 0, ("run_set_region_4: Invalid data length.\n")); + for (i = 0; i < len && error == 0; i += 4) + error = run_write(sc, reg + i, val); + return (error); +} + +/* Read 16-bit from eFUSE ROM (RT3070 only.) */ +static int +run_efuse_read_2(struct run_softc *sc, uint16_t addr, uint16_t *val) +{ + uint32_t tmp; + uint16_t reg; + int error, ntries; + + if ((error = run_read(sc, RT3070_EFUSE_CTRL, &tmp)) != 0) + return (error); + + addr *= 2; + /*- + * Read one 16-byte block into registers EFUSE_DATA[0-3]: + * DATA0: F E D C + * DATA1: B A 9 8 + * DATA2: 7 6 5 4 + * DATA3: 3 2 1 0 + */ + tmp &= ~(RT3070_EFSROM_MODE_MASK | RT3070_EFSROM_AIN_MASK); + tmp |= (addr & ~0xf) << RT3070_EFSROM_AIN_SHIFT | RT3070_EFSROM_KICK; + run_write(sc, RT3070_EFUSE_CTRL, tmp); + for (ntries = 0; ntries < 100; ntries++) { + if ((error = run_read(sc, RT3070_EFUSE_CTRL, &tmp)) != 0) + return (error); + if (!(tmp & RT3070_EFSROM_KICK)) + break; + run_delay(sc, 2); + } + if (ntries == 100) + return (ETIMEDOUT); + + if ((tmp & RT3070_EFUSE_AOUT_MASK) == RT3070_EFUSE_AOUT_MASK) { + *val = 0xffff; /* address not found */ + return (0); + } + /* determine to which 32-bit register our 16-bit word belongs */ + reg = RT3070_EFUSE_DATA3 - (addr & 0xc); + if ((error = run_read(sc, reg, &tmp)) != 0) + return (error); + + *val = (addr & 2) ? tmp >> 16 : tmp & 0xffff; + return (0); +} + +static int +run_eeprom_read_2(struct run_softc *sc, uint16_t addr, uint16_t *val) +{ + usb_device_request_t req; + uint16_t tmp; + int error; + + addr *= 2; + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RT2870_EEPROM_READ; + USETW(req.wValue, 0); + USETW(req.wIndex, addr); + USETW(req.wLength, sizeof tmp); + + error = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, &tmp); + if (error == 0) + *val = le16toh(tmp); + else + *val = 0xffff; + return (error); +} + +static __inline int +run_srom_read(struct run_softc *sc, uint16_t addr, uint16_t *val) +{ + /* either eFUSE ROM or EEPROM */ + return sc->sc_srom_read(sc, addr, val); +} + +static int +run_rt2870_rf_write(struct run_softc *sc, uint8_t reg, uint32_t val) +{ + uint32_t tmp; + int error, ntries; + + for (ntries = 0; ntries < 10; ntries++) { + if ((error = run_read(sc, RT2860_RF_CSR_CFG0, &tmp)) != 0) + return (error); + if (!(tmp & RT2860_RF_REG_CTRL)) + break; + } + if (ntries == 10) + return (ETIMEDOUT); + + /* RF registers are 24-bit on the RT2860 */ + tmp = RT2860_RF_REG_CTRL | 24 << RT2860_RF_REG_WIDTH_SHIFT | + (val & 0x3fffff) << 2 | (reg & 3); + return (run_write(sc, RT2860_RF_CSR_CFG0, tmp)); +} + +static int +run_rt3070_rf_read(struct run_softc *sc, uint8_t reg, uint8_t *val) +{ + uint32_t tmp; + int error, ntries; + + for (ntries = 0; ntries < 100; ntries++) { + if ((error = run_read(sc, RT3070_RF_CSR_CFG, &tmp)) != 0) + return (error); + if (!(tmp & RT3070_RF_KICK)) + break; + } + if (ntries == 100) + return (ETIMEDOUT); + + tmp = RT3070_RF_KICK | reg << 8; + if ((error = run_write(sc, RT3070_RF_CSR_CFG, tmp)) != 0) + return (error); + + for (ntries = 0; ntries < 100; ntries++) { + if ((error = run_read(sc, RT3070_RF_CSR_CFG, &tmp)) != 0) + return (error); + if (!(tmp & RT3070_RF_KICK)) + break; + } + if (ntries == 100) + return (ETIMEDOUT); + + *val = tmp & 0xff; + return (0); +} + +static int +run_rt3070_rf_write(struct run_softc *sc, uint8_t reg, uint8_t val) +{ + uint32_t tmp; + int error, ntries; + + for (ntries = 0; ntries < 10; ntries++) { + if ((error = run_read(sc, RT3070_RF_CSR_CFG, &tmp)) != 0) + return (error); + if (!(tmp & RT3070_RF_KICK)) + break; + } + if (ntries == 10) + return (ETIMEDOUT); + + tmp = RT3070_RF_WRITE | RT3070_RF_KICK | reg << 8 | val; + return (run_write(sc, RT3070_RF_CSR_CFG, tmp)); +} + +static int +run_bbp_read(struct run_softc *sc, uint8_t reg, uint8_t *val) +{ + uint32_t tmp; + int ntries, error; + + for (ntries = 0; ntries < 10; ntries++) { + if ((error = run_read(sc, RT2860_BBP_CSR_CFG, &tmp)) != 0) + return (error); + if (!(tmp & RT2860_BBP_CSR_KICK)) + break; + } + if (ntries == 10) + return (ETIMEDOUT); + + tmp = RT2860_BBP_CSR_READ | RT2860_BBP_CSR_KICK | reg << 8; + if ((error = run_write(sc, RT2860_BBP_CSR_CFG, tmp)) != 0) + return (error); + + for (ntries = 0; ntries < 10; ntries++) { + if ((error = run_read(sc, RT2860_BBP_CSR_CFG, &tmp)) != 0) + return (error); + if (!(tmp & RT2860_BBP_CSR_KICK)) + break; + } + if (ntries == 10) + return (ETIMEDOUT); + + *val = tmp & 0xff; + return (0); +} + +static int +run_bbp_write(struct run_softc *sc, uint8_t reg, uint8_t val) +{ + uint32_t tmp; + int ntries, error; + + for (ntries = 0; ntries < 10; ntries++) { + if ((error = run_read(sc, RT2860_BBP_CSR_CFG, &tmp)) != 0) + return (error); + if (!(tmp & RT2860_BBP_CSR_KICK)) + break; + } + if (ntries == 10) + return (ETIMEDOUT); + + tmp = RT2860_BBP_CSR_KICK | reg << 8 | val; + return (run_write(sc, RT2860_BBP_CSR_CFG, tmp)); +} + +/* + * Send a command to the 8051 microcontroller unit. + */ +static int +run_mcu_cmd(struct run_softc *sc, uint8_t cmd, uint16_t arg) +{ + uint32_t tmp; + int error, ntries; + + for (ntries = 0; ntries < 100; ntries++) { + if ((error = run_read(sc, RT2860_H2M_MAILBOX, &tmp)) != 0) + return error; + if (!(tmp & RT2860_H2M_BUSY)) + break; + } + if (ntries == 100) + return ETIMEDOUT; + + tmp = RT2860_H2M_BUSY | RT2860_TOKEN_NO_INTR << 16 | arg; + if ((error = run_write(sc, RT2860_H2M_MAILBOX, tmp)) == 0) + error = run_write(sc, RT2860_HOST_CMD, cmd); + return (error); +} + +/* + * Add `delta' (signed) to each 4-bit sub-word of a 32-bit word. + * Used to adjust per-rate Tx power registers. + */ +static __inline uint32_t +b4inc(uint32_t b32, int8_t delta) +{ + int8_t i, b4; + + for (i = 0; i < 8; i++) { + b4 = b32 & 0xf; + b4 += delta; + if (b4 < 0) + b4 = 0; + else if (b4 > 0xf) + b4 = 0xf; + b32 = b32 >> 4 | b4 << 28; + } + return (b32); +} + +static const char * +run_get_rf(int rev) +{ + switch (rev) { + case RT2860_RF_2820: return "RT2820"; + case RT2860_RF_2850: return "RT2850"; + case RT2860_RF_2720: return "RT2720"; + case RT2860_RF_2750: return "RT2750"; + case RT3070_RF_3020: return "RT3020"; + case RT3070_RF_2020: return "RT2020"; + case RT3070_RF_3021: return "RT3021"; + case RT3070_RF_3022: return "RT3022"; + case RT3070_RF_3052: return "RT3052"; + } + return ("unknown"); +} + +int +run_read_eeprom(struct run_softc *sc) +{ + int8_t delta_2ghz, delta_5ghz; + uint32_t tmp; + uint16_t val; + int ridx, ant, i; + + /* check whether the ROM is eFUSE ROM or EEPROM */ + sc->sc_srom_read = run_eeprom_read_2; + if (sc->mac_ver >= 0x3070) { + run_read(sc, RT3070_EFUSE_CTRL, &tmp); + DPRINTF("EFUSE_CTRL=0x%08x\n", tmp); + if (tmp & RT3070_SEL_EFUSE) + sc->sc_srom_read = run_efuse_read_2; + } + + /* read ROM version */ + run_srom_read(sc, RT2860_EEPROM_VERSION, &val); + DPRINTF("EEPROM rev=%d, FAE=%d\n", val & 0xff, val >> 8); + + /* read MAC address */ + run_srom_read(sc, RT2860_EEPROM_MAC01, &val); + sc->sc_bssid[0] = val & 0xff; + sc->sc_bssid[1] = val >> 8; + run_srom_read(sc, RT2860_EEPROM_MAC23, &val); + sc->sc_bssid[2] = val & 0xff; + sc->sc_bssid[3] = val >> 8; + run_srom_read(sc, RT2860_EEPROM_MAC45, &val); + sc->sc_bssid[4] = val & 0xff; + sc->sc_bssid[5] = val >> 8; + + /* read vender BBP settings */ + for (i = 0; i < 10; i++) { + run_srom_read(sc, RT2860_EEPROM_BBP_BASE + i, &val); + sc->bbp[i].val = val & 0xff; + sc->bbp[i].reg = val >> 8; + DPRINTF("BBP%d=0x%02x\n", sc->bbp[i].reg, sc->bbp[i].val); + } + if (sc->mac_ver >= 0x3071) { + /* read vendor RF settings */ + for (i = 0; i < 10; i++) { + run_srom_read(sc, RT3071_EEPROM_RF_BASE + i, &val); + sc->rf[i].val = val & 0xff; + sc->rf[i].reg = val >> 8; + DPRINTF("RF%d=0x%02x\n", sc->rf[i].reg, + sc->rf[i].val); + } + } + + /* read RF frequency offset from EEPROM */ + run_srom_read(sc, RT2860_EEPROM_FREQ_LEDS, &val); + sc->freq = ((val & 0xff) != 0xff) ? val & 0xff : 0; + DPRINTF("EEPROM freq offset %d\n", sc->freq & 0xff); + + if (val >> 8 != 0xff) { + /* read LEDs operating mode */ + sc->leds = val >> 8; + run_srom_read(sc, RT2860_EEPROM_LED1, &sc->led[0]); + run_srom_read(sc, RT2860_EEPROM_LED2, &sc->led[1]); + run_srom_read(sc, RT2860_EEPROM_LED3, &sc->led[2]); + } else { + /* broken EEPROM, use default settings */ + sc->leds = 0x01; + sc->led[0] = 0x5555; + sc->led[1] = 0x2221; + sc->led[2] = 0x5627; /* differs from RT2860 */ + } + DPRINTF("EEPROM LED mode=0x%02x, LEDs=0x%04x/0x%04x/0x%04x\n", + sc->leds, sc->led[0], sc->led[1], sc->led[2]); + + /* read RF information */ + run_srom_read(sc, RT2860_EEPROM_ANTENNA, &val); + if (val == 0xffff) { + DPRINTF("invalid EEPROM antenna info, using default\n"); + if (sc->mac_ver == 0x3572) { + /* default to RF3052 2T2R */ + sc->rf_rev = RT3070_RF_3052; + sc->ntxchains = 2; + sc->nrxchains = 2; + } else if (sc->mac_ver >= 0x3070) { + /* default to RF3020 1T1R */ + sc->rf_rev = RT3070_RF_3020; + sc->ntxchains = 1; + sc->nrxchains = 1; + } else { + /* default to RF2820 1T2R */ + sc->rf_rev = RT2860_RF_2820; + sc->ntxchains = 1; + sc->nrxchains = 2; + } + } else { + sc->rf_rev = (val >> 8) & 0xf; + sc->ntxchains = (val >> 4) & 0xf; + sc->nrxchains = val & 0xf; + } + DPRINTF("EEPROM RF rev=0x%02x chains=%dT%dR\n", + sc->rf_rev, sc->ntxchains, sc->nrxchains); + + /* check if RF supports automatic Tx access gain control */ + run_srom_read(sc, RT2860_EEPROM_CONFIG, &val); + DPRINTF("EEPROM CFG 0x%04x\n", val); + /* check if driver should patch the DAC issue */ + if ((val >> 8) != 0xff) + sc->patch_dac = (val >> 15) & 1; + if ((val & 0xff) != 0xff) { + sc->ext_5ghz_lna = (val >> 3) & 1; + sc->ext_2ghz_lna = (val >> 2) & 1; + /* check if RF supports automatic Tx access gain control */ + sc->calib_2ghz = sc->calib_5ghz = (val >> 1) & 1; + /* check if we have a hardware radio switch */ + sc->rfswitch = val & 1; + } + + /* read power settings for 2GHz channels */ + for (i = 0; i < 14; i += 2) { + run_srom_read(sc, RT2860_EEPROM_PWR2GHZ_BASE1 + i / 2, &val); + sc->txpow1[i + 0] = (int8_t)(val & 0xff); + sc->txpow1[i + 1] = (int8_t)(val >> 8); + + run_srom_read(sc, RT2860_EEPROM_PWR2GHZ_BASE2 + i / 2, &val); + sc->txpow2[i + 0] = (int8_t)(val & 0xff); + sc->txpow2[i + 1] = (int8_t)(val >> 8); + } + /* fix broken Tx power entries */ + for (i = 0; i < 14; i++) { + if (sc->txpow1[i] < 0 || sc->txpow1[i] > 31) + sc->txpow1[i] = 5; + if (sc->txpow2[i] < 0 || sc->txpow2[i] > 31) + sc->txpow2[i] = 5; + DPRINTF("chan %d: power1=%d, power2=%d\n", + rt2860_rf2850[i].chan, sc->txpow1[i], sc->txpow2[i]); + } + /* read power settings for 5GHz channels */ + for (i = 0; i < 40; i += 2) { + run_srom_read(sc, RT2860_EEPROM_PWR5GHZ_BASE1 + i / 2, &val); + sc->txpow1[i + 14] = (int8_t)(val & 0xff); + sc->txpow1[i + 15] = (int8_t)(val >> 8); + + run_srom_read(sc, RT2860_EEPROM_PWR5GHZ_BASE2 + i / 2, &val); + sc->txpow2[i + 14] = (int8_t)(val & 0xff); + sc->txpow2[i + 15] = (int8_t)(val >> 8); + } + /* fix broken Tx power entries */ + for (i = 0; i < 40; i++) { + if (sc->txpow1[14 + i] < -7 || sc->txpow1[14 + i] > 15) + sc->txpow1[14 + i] = 5; + if (sc->txpow2[14 + i] < -7 || sc->txpow2[14 + i] > 15) + sc->txpow2[14 + i] = 5; + DPRINTF("chan %d: power1=%d, power2=%d\n", + rt2860_rf2850[14 + i].chan, sc->txpow1[14 + i], + sc->txpow2[14 + i]); + } + + /* read Tx power compensation for each Tx rate */ + run_srom_read(sc, RT2860_EEPROM_DELTAPWR, &val); + delta_2ghz = delta_5ghz = 0; + if ((val & 0xff) != 0xff && (val & 0x80)) { + delta_2ghz = val & 0xf; + if (!(val & 0x40)) /* negative number */ + delta_2ghz = -delta_2ghz; + } + val >>= 8; + if ((val & 0xff) != 0xff && (val & 0x80)) { + delta_5ghz = val & 0xf; + if (!(val & 0x40)) /* negative number */ + delta_5ghz = -delta_5ghz; + } + DPRINTF("power compensation=%d (2GHz), %d (5GHz)\n", + delta_2ghz, delta_5ghz); + + for (ridx = 0; ridx < 5; ridx++) { + uint32_t reg; + + run_srom_read(sc, RT2860_EEPROM_RPWR + ridx * 2, &val); + reg = val; + run_srom_read(sc, RT2860_EEPROM_RPWR + ridx * 2 + 1, &val); + reg |= (uint32_t)val << 16; + + sc->txpow20mhz[ridx] = reg; + sc->txpow40mhz_2ghz[ridx] = b4inc(reg, delta_2ghz); + sc->txpow40mhz_5ghz[ridx] = b4inc(reg, delta_5ghz); + + DPRINTF("ridx %d: power 20MHz=0x%08x, 40MHz/2GHz=0x%08x, " + "40MHz/5GHz=0x%08x\n", ridx, sc->txpow20mhz[ridx], + sc->txpow40mhz_2ghz[ridx], sc->txpow40mhz_5ghz[ridx]); + } + + /* read RSSI offsets and LNA gains from EEPROM */ + run_srom_read(sc, RT2860_EEPROM_RSSI1_2GHZ, &val); + sc->rssi_2ghz[0] = val & 0xff; /* Ant A */ + sc->rssi_2ghz[1] = val >> 8; /* Ant B */ + run_srom_read(sc, RT2860_EEPROM_RSSI2_2GHZ, &val); + if (sc->mac_ver >= 0x3070) { + /* + * On RT3070 chips (limited to 2 Rx chains), this ROM + * field contains the Tx mixer gain for the 2GHz band. + */ + if ((val & 0xff) != 0xff) + sc->txmixgain_2ghz = val & 0x7; + DPRINTF("tx mixer gain=%u (2GHz)\n", sc->txmixgain_2ghz); + } else + sc->rssi_2ghz[2] = val & 0xff; /* Ant C */ + sc->lna[2] = val >> 8; /* channel group 2 */ + + run_srom_read(sc, RT2860_EEPROM_RSSI1_5GHZ, &val); + sc->rssi_5ghz[0] = val & 0xff; /* Ant A */ + sc->rssi_5ghz[1] = val >> 8; /* Ant B */ + run_srom_read(sc, RT2860_EEPROM_RSSI2_5GHZ, &val); + if (sc->mac_ver == 0x3572) { + /* + * On RT3572 chips (limited to 2 Rx chains), this ROM + * field contains the Tx mixer gain for the 5GHz band. + */ + if ((val & 0xff) != 0xff) + sc->txmixgain_5ghz = val & 0x7; + DPRINTF("tx mixer gain=%u (5GHz)\n", sc->txmixgain_5ghz); + } else + sc->rssi_5ghz[2] = val & 0xff; /* Ant C */ + sc->lna[3] = val >> 8; /* channel group 3 */ + + run_srom_read(sc, RT2860_EEPROM_LNA, &val); + sc->lna[0] = val & 0xff; /* channel group 0 */ + sc->lna[1] = val >> 8; /* channel group 1 */ + + /* fix broken 5GHz LNA entries */ + if (sc->lna[2] == 0 || sc->lna[2] == 0xff) { + DPRINTF("invalid LNA for channel group %d\n", 2); + sc->lna[2] = sc->lna[1]; + } + if (sc->lna[3] == 0 || sc->lna[3] == 0xff) { + DPRINTF("invalid LNA for channel group %d\n", 3); + sc->lna[3] = sc->lna[1]; + } + + /* fix broken RSSI offset entries */ + for (ant = 0; ant < 3; ant++) { + if (sc->rssi_2ghz[ant] < -10 || sc->rssi_2ghz[ant] > 10) { + DPRINTF("invalid RSSI%d offset: %d (2GHz)\n", + ant + 1, sc->rssi_2ghz[ant]); + sc->rssi_2ghz[ant] = 0; + } + if (sc->rssi_5ghz[ant] < -10 || sc->rssi_5ghz[ant] > 10) { + DPRINTF("invalid RSSI%d offset: %d (5GHz)\n", + ant + 1, sc->rssi_5ghz[ant]); + sc->rssi_5ghz[ant] = 0; + } + } + return (0); +} + +static struct ieee80211_node * +run_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + return malloc(sizeof (struct run_node), M_DEVBUF, M_NOWAIT | M_ZERO); +} + +static int +run_media_change(struct ifnet *ifp) +{ + struct ieee80211vap *vap = ifp->if_softc; + struct ieee80211com *ic = vap->iv_ic; + const struct ieee80211_txparam *tp; + struct run_softc *sc = ic->ic_ifp->if_softc; + uint8_t rate, ridx; + int error; + + RUN_LOCK(sc); + + error = ieee80211_media_change(ifp); + if (error != ENETRESET) { + RUN_UNLOCK(sc); + return (error); + } + + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) { + struct ieee80211_node *ni; + struct run_node *rn; + + rate = ic->ic_sup_rates[ic->ic_curmode]. + rs_rates[tp->ucastrate] & IEEE80211_RATE_VAL; + for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++) + if (rt2860_rates[ridx].rate == rate) + break; + ni = ieee80211_ref_node(vap->iv_bss); + rn = (struct run_node *)ni; + rn->fix_ridx = ridx; + DPRINTF("rate=%d, fix_ridx=%d\n", rate, rn->fix_ridx); + ieee80211_free_node(ni); + } + +#if 0 + if ((ifp->if_flags & IFF_UP) && + (ifp->if_drv_flags & IFF_DRV_RUNNING)){ + run_init_locked(sc); + } +#endif + + RUN_UNLOCK(sc); + + return (0); +} + +static int +run_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + const struct ieee80211_txparam *tp; + struct ieee80211com *ic = vap->iv_ic; + struct run_softc *sc = ic->ic_ifp->if_softc; + struct run_vap *rvp = RUN_VAP(vap); + enum ieee80211_state ostate; + uint32_t sta[3]; + uint32_t tmp; + uint8_t ratectl; + uint8_t restart_ratectl = 0; + uint8_t bid = 1 << rvp->rvp_id; + + ostate = vap->iv_state; + DPRINTF("%s -> %s\n", + ieee80211_state_name[ostate], + ieee80211_state_name[nstate]); + + IEEE80211_UNLOCK(ic); + RUN_LOCK(sc); + + ratectl = sc->ratectl_run; /* remember current state */ + sc->ratectl_run = RUN_RATECTL_OFF; + usb_callout_stop(&sc->ratectl_ch); + + if (ostate == IEEE80211_S_RUN) { + /* turn link LED off */ + run_set_leds(sc, RT2860_LED_RADIO); + } + + switch (nstate) { + case IEEE80211_S_INIT: + restart_ratectl = 1; + + if (ostate != IEEE80211_S_RUN) + break; + + ratectl &= ~bid; + sc->runbmap &= ~bid; + + /* abort TSF synchronization if there is no vap running */ + if (--sc->running == 0) { + run_read(sc, RT2860_BCN_TIME_CFG, &tmp); + run_write(sc, RT2860_BCN_TIME_CFG, + tmp & ~(RT2860_BCN_TX_EN | RT2860_TSF_TIMER_EN | + RT2860_TBTT_TIMER_EN)); + } + break; + + case IEEE80211_S_RUN: + if (!(sc->runbmap & bid)) { + if(sc->running++) + restart_ratectl = 1; + sc->runbmap |= bid; + } + + m_freem(rvp->beacon_mbuf); + rvp->beacon_mbuf = NULL; + + switch (vap->iv_opmode) { + case IEEE80211_M_HOSTAP: + case IEEE80211_M_MBSS: + sc->ap_running |= bid; + ic->ic_opmode = vap->iv_opmode; + run_update_beacon_cb(vap); + break; + case IEEE80211_M_IBSS: + sc->adhoc_running |= bid; + if (!sc->ap_running) + ic->ic_opmode = vap->iv_opmode; + run_update_beacon_cb(vap); + break; + case IEEE80211_M_STA: + sc->sta_running |= bid; + if (!sc->ap_running && !sc->adhoc_running) + ic->ic_opmode = vap->iv_opmode; + + /* read statistic counters (clear on read) */ + run_read_region_1(sc, RT2860_TX_STA_CNT0, + (uint8_t *)sta, sizeof sta); + + break; + default: + ic->ic_opmode = vap->iv_opmode; + break; + } + + if (vap->iv_opmode != IEEE80211_M_MONITOR) { + struct ieee80211_node *ni; + + run_updateslot(ic->ic_ifp); + run_enable_mrr(sc); + run_set_txpreamble(sc); + run_set_basicrates(sc); + ni = ieee80211_ref_node(vap->iv_bss); + IEEE80211_ADDR_COPY(sc->sc_bssid, ni->ni_bssid); + run_set_bssid(sc, ni->ni_bssid); + ieee80211_free_node(ni); + run_enable_tsf_sync(sc); + + /* enable automatic rate adaptation */ + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) + ratectl |= bid; + } + + /* turn link LED on */ + run_set_leds(sc, RT2860_LED_RADIO | + (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan) ? + RT2860_LED_LINK_2GHZ : RT2860_LED_LINK_5GHZ)); + + break; + default: + DPRINTFN(6, "undefined case\n"); + break; + } + + /* restart amrr for running VAPs */ + if ((sc->ratectl_run = ratectl) && restart_ratectl) + usb_callout_reset(&sc->ratectl_ch, hz, run_ratectl_to, sc); + + RUN_UNLOCK(sc); + IEEE80211_LOCK(ic); + + return(rvp->newstate(vap, nstate, arg)); +} + +/* ARGSUSED */ +static void +run_wme_update_cb(void *arg) +{ + struct ieee80211com *ic = arg; + struct run_softc *sc = ic->ic_ifp->if_softc; + struct ieee80211_wme_state *wmesp = &ic->ic_wme; + int aci, error = 0; + + RUN_LOCK_ASSERT(sc, MA_OWNED); + + /* update MAC TX configuration registers */ + for (aci = 0; aci < WME_NUM_AC; aci++) { + error = run_write(sc, RT2860_EDCA_AC_CFG(aci), + wmesp->wme_params[aci].wmep_logcwmax << 16 | + wmesp->wme_params[aci].wmep_logcwmin << 12 | + wmesp->wme_params[aci].wmep_aifsn << 8 | + wmesp->wme_params[aci].wmep_txopLimit); + if (error) goto err; + } + + /* update SCH/DMA registers too */ + error = run_write(sc, RT2860_WMM_AIFSN_CFG, + wmesp->wme_params[WME_AC_VO].wmep_aifsn << 12 | + wmesp->wme_params[WME_AC_VI].wmep_aifsn << 8 | + wmesp->wme_params[WME_AC_BK].wmep_aifsn << 4 | + wmesp->wme_params[WME_AC_BE].wmep_aifsn); + if (error) goto err; + error = run_write(sc, RT2860_WMM_CWMIN_CFG, + wmesp->wme_params[WME_AC_VO].wmep_logcwmin << 12 | + wmesp->wme_params[WME_AC_VI].wmep_logcwmin << 8 | + wmesp->wme_params[WME_AC_BK].wmep_logcwmin << 4 | + wmesp->wme_params[WME_AC_BE].wmep_logcwmin); + if (error) goto err; + error = run_write(sc, RT2860_WMM_CWMAX_CFG, + wmesp->wme_params[WME_AC_VO].wmep_logcwmax << 12 | + wmesp->wme_params[WME_AC_VI].wmep_logcwmax << 8 | + wmesp->wme_params[WME_AC_BK].wmep_logcwmax << 4 | + wmesp->wme_params[WME_AC_BE].wmep_logcwmax); + if (error) goto err; + error = run_write(sc, RT2860_WMM_TXOP0_CFG, + wmesp->wme_params[WME_AC_BK].wmep_txopLimit << 16 | + wmesp->wme_params[WME_AC_BE].wmep_txopLimit); + if (error) goto err; + error = run_write(sc, RT2860_WMM_TXOP1_CFG, + wmesp->wme_params[WME_AC_VO].wmep_txopLimit << 16 | + wmesp->wme_params[WME_AC_VI].wmep_txopLimit); + +err: + if (error) + DPRINTF("WME update failed\n"); + + return; +} + +static int +run_wme_update(struct ieee80211com *ic) +{ + struct run_softc *sc = ic->ic_ifp->if_softc; + + /* sometime called wothout lock */ + if (mtx_owned(&ic->ic_comlock.mtx)) { + uint32_t i = RUN_CMDQ_GET(&sc->cmdq_store); + DPRINTF("cmdq_store=%d\n", i); + sc->cmdq[i].func = run_wme_update_cb; + sc->cmdq[i].arg0 = ic; + ieee80211_runtask(ic, &sc->cmdq_task); + return (0); + } + + RUN_LOCK(sc); + run_wme_update_cb(ic); + RUN_UNLOCK(sc); + + /* return whatever, upper layer desn't care anyway */ + return (0); +} + +static void +run_key_update_begin(struct ieee80211vap *vap) +{ + /* + * To avoid out-of-order events, both run_key_set() and + * _delete() are deferred and handled by run_cmdq_cb(). + * So, there is nothing we need to do here. + */ +} + +static void +run_key_update_end(struct ieee80211vap *vap) +{ + /* null */ +} + +static void +run_key_set_cb(void *arg) +{ + struct run_cmdq *cmdq = arg; + struct ieee80211vap *vap = cmdq->arg1; + struct ieee80211_key *k = cmdq->k; + struct ieee80211com *ic = vap->iv_ic; + struct run_softc *sc = ic->ic_ifp->if_softc; + struct ieee80211_node *ni; + uint32_t attr; + uint16_t base, associd; + uint8_t mode, wcid, iv[8]; + + RUN_LOCK_ASSERT(sc, MA_OWNED); + + if (vap->iv_opmode == IEEE80211_M_HOSTAP) + ni = ieee80211_find_vap_node(&ic->ic_sta, vap, cmdq->mac); + else + ni = vap->iv_bss; + associd = (ni != NULL) ? ni->ni_associd : 0; + + /* map net80211 cipher to RT2860 security mode */ + switch (k->wk_cipher->ic_cipher) { + case IEEE80211_CIPHER_WEP: + if(k->wk_keylen < 8) + mode = RT2860_MODE_WEP40; + else + mode = RT2860_MODE_WEP104; + break; + case IEEE80211_CIPHER_TKIP: + mode = RT2860_MODE_TKIP; + break; + case IEEE80211_CIPHER_AES_CCM: + mode = RT2860_MODE_AES_CCMP; + break; + default: + DPRINTF("undefined case\n"); + return; + } + + DPRINTFN(1, "associd=%x, keyix=%d, mode=%x, type=%s, tx=%s, rx=%s\n", + associd, k->wk_keyix, mode, + (k->wk_flags & IEEE80211_KEY_GROUP) ? "group" : "pairwise", + (k->wk_flags & IEEE80211_KEY_XMIT) ? "on" : "off", + (k->wk_flags & IEEE80211_KEY_RECV) ? "on" : "off"); + + if (k->wk_flags & IEEE80211_KEY_GROUP) { + wcid = 0; /* NB: update WCID0 for group keys */ + base = RT2860_SKEY(RUN_VAP(vap)->rvp_id, k->wk_keyix); + } else { + wcid = RUN_AID2WCID(associd); + base = RT2860_PKEY(wcid); + } + + if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_TKIP) { + if(run_write_region_1(sc, base, k->wk_key, 16)) + return; + if(run_write_region_1(sc, base + 16, &k->wk_key[16], 8)) /* wk_txmic */ + return; + if(run_write_region_1(sc, base + 24, &k->wk_key[24], 8)) /* wk_rxmic */ + return; + } else { + /* roundup len to 16-bit: XXX fix write_region_1() instead */ + if(run_write_region_1(sc, base, k->wk_key, (k->wk_keylen + 1) & ~1)) + return; + } + + if (!(k->wk_flags & IEEE80211_KEY_GROUP) || + (k->wk_flags & (IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV))) { + /* set initial packet number in IV+EIV */ + if (k->wk_cipher == IEEE80211_CIPHER_WEP) { + memset(iv, 0, sizeof iv); + iv[3] = vap->iv_def_txkey << 6; + } else { + if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_TKIP) { + iv[0] = k->wk_keytsc >> 8; + iv[1] = (iv[0] | 0x20) & 0x7f; + iv[2] = k->wk_keytsc; + } else /* CCMP */ { + iv[0] = k->wk_keytsc; + iv[1] = k->wk_keytsc >> 8; + iv[2] = 0; + } + iv[3] = k->wk_keyix << 6 | IEEE80211_WEP_EXTIV; + iv[4] = k->wk_keytsc >> 16; + iv[5] = k->wk_keytsc >> 24; + iv[6] = k->wk_keytsc >> 32; + iv[7] = k->wk_keytsc >> 40; + } + if (run_write_region_1(sc, RT2860_IVEIV(wcid), iv, 8)) + return; + } + + if (k->wk_flags & IEEE80211_KEY_GROUP) { + /* install group key */ + if (run_read(sc, RT2860_SKEY_MODE_0_7, &attr)) + return; + attr &= ~(0xf << (k->wk_keyix * 4)); + attr |= mode << (k->wk_keyix * 4); + if (run_write(sc, RT2860_SKEY_MODE_0_7, attr)) + return; + } else { + /* install pairwise key */ + if (run_read(sc, RT2860_WCID_ATTR(wcid), &attr)) + return; + attr = (attr & ~0xf) | (mode << 1) | RT2860_RX_PKEY_EN; + if (run_write(sc, RT2860_WCID_ATTR(wcid), attr)) + return; + } + + /* TODO create a pass-thru key entry? */ + + /* need wcid to delete the right key later */ + k->wk_pad = wcid; +} + +/* + * Don't have to be deferred, but in order to keep order of + * execution, i.e. with run_key_delete(), defer this and let + * run_cmdq_cb() maintain the order. + * + * return 0 on error + */ +static int +run_key_set(struct ieee80211vap *vap, struct ieee80211_key *k, + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct ieee80211com *ic = vap->iv_ic; + struct run_softc *sc = ic->ic_ifp->if_softc; + uint32_t i; + + i = RUN_CMDQ_GET(&sc->cmdq_store); + DPRINTF("cmdq_store=%d\n", i); + sc->cmdq[i].func = run_key_set_cb; + sc->cmdq[i].arg0 = NULL; + sc->cmdq[i].arg1 = vap; + sc->cmdq[i].k = k; + IEEE80211_ADDR_COPY(sc->cmdq[i].mac, mac); + ieee80211_runtask(ic, &sc->cmdq_task); + + /* + * To make sure key will be set when hostapd + * calls iv_key_set() before if_init(). + */ + if (vap->iv_opmode == IEEE80211_M_HOSTAP) { + RUN_LOCK(sc); + sc->cmdq_key_set = RUN_CMDQ_GO; + RUN_UNLOCK(sc); + } + + return (1); +} + +/* + * If wlan is destroyed without being brought down i.e. without + * wlan down or wpa_cli terminate, this function is called after + * vap is gone. Don't refer it. + */ +static void +run_key_delete_cb(void *arg) +{ + struct run_cmdq *cmdq = arg; + struct run_softc *sc = cmdq->arg1; + struct ieee80211_key *k = &cmdq->key; + uint32_t attr; + uint8_t wcid; + + RUN_LOCK_ASSERT(sc, MA_OWNED); + + if (k->wk_flags & IEEE80211_KEY_GROUP) { + /* remove group key */ + DPRINTF("removing group key\n"); + run_read(sc, RT2860_SKEY_MODE_0_7, &attr); + attr &= ~(0xf << (k->wk_keyix * 4)); + run_write(sc, RT2860_SKEY_MODE_0_7, attr); + } else { + /* remove pairwise key */ + DPRINTF("removing key for wcid %x\n", k->wk_pad); + /* matching wcid was written to wk_pad in run_key_set() */ + wcid = k->wk_pad; + run_read(sc, RT2860_WCID_ATTR(wcid), &attr); + attr &= ~0xf; + run_write(sc, RT2860_WCID_ATTR(wcid), attr); + run_set_region_4(sc, RT2860_WCID_ENTRY(wcid), 0, 8); + } + + k->wk_pad = 0; +} + +/* + * return 0 on error + */ +static int +run_key_delete(struct ieee80211vap *vap, struct ieee80211_key *k) +{ + struct ieee80211com *ic = vap->iv_ic; + struct run_softc *sc = ic->ic_ifp->if_softc; + struct ieee80211_key *k0; + uint32_t i; + + /* + * When called back, key might be gone. So, make a copy + * of some values need to delete keys before deferring. + * But, because of LOR with node lock, cannot use lock here. + * So, use atomic instead. + */ + i = RUN_CMDQ_GET(&sc->cmdq_store); + DPRINTF("cmdq_store=%d\n", i); + sc->cmdq[i].func = run_key_delete_cb; + sc->cmdq[i].arg0 = NULL; + sc->cmdq[i].arg1 = sc; + k0 = &sc->cmdq[i].key; + k0->wk_flags = k->wk_flags; + k0->wk_keyix = k->wk_keyix; + /* matching wcid was written to wk_pad in run_key_set() */ + k0->wk_pad = k->wk_pad; + ieee80211_runtask(ic, &sc->cmdq_task); + return (1); /* return fake success */ + +} + +static void +run_ratectl_to(void *arg) +{ + struct run_softc *sc = arg; + + /* do it in a process context, so it can go sleep */ + ieee80211_runtask(sc->sc_ifp->if_l2com, &sc->ratectl_task); + /* next timeout will be rescheduled in the callback task */ +} + +/* ARGSUSED */ +static void +run_ratectl_cb(void *arg, int pending) +{ + struct run_softc *sc = arg; + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + + if (vap == NULL) + return; + + if (sc->rvp_cnt <= 1 && vap->iv_opmode == IEEE80211_M_STA) + run_iter_func(sc, vap->iv_bss); + else { + /* + * run_reset_livelock() doesn't do anything with AMRR, + * but Ralink wants us to call it every 1 sec. So, we + * piggyback here rather than creating another callout. + * Livelock may occur only in HOSTAP or IBSS mode + * (when h/w is sending beacons). + */ + RUN_LOCK(sc); + run_reset_livelock(sc); + /* just in case, there are some stats to drain */ + run_drain_fifo(sc); + RUN_UNLOCK(sc); + ieee80211_iterate_nodes(&ic->ic_sta, run_iter_func, sc); + } + + if(sc->ratectl_run != RUN_RATECTL_OFF) + usb_callout_reset(&sc->ratectl_ch, hz, run_ratectl_to, sc); +} + +static void +run_drain_fifo(void *arg) +{ + struct run_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + uint32_t stat; + uint16_t (*wstat)[3]; + uint8_t wcid, mcs, pid; + int8_t retry; + + RUN_LOCK_ASSERT(sc, MA_OWNED); + + for (;;) { + /* drain Tx status FIFO (maxsize = 16) */ + run_read(sc, RT2860_TX_STAT_FIFO, &stat); + DPRINTFN(4, "tx stat 0x%08x\n", stat); + if (!(stat & RT2860_TXQ_VLD)) + break; + + wcid = (stat >> RT2860_TXQ_WCID_SHIFT) & 0xff; + + /* if no ACK was requested, no feedback is available */ + if (!(stat & RT2860_TXQ_ACKREQ) || wcid > RT2870_WCID_MAX || + wcid == 0) + continue; + + /* + * Even though each stat is Tx-complete-status like format, + * the device can poll stats. Because there is no guarantee + * that the referring node is still around when read the stats. + * So that, if we use ieee80211_ratectl_tx_update(), we will + * have hard time not to refer already freed node. + * + * To eliminate such page faults, we poll stats in softc. + * Then, update the rates later with ieee80211_ratectl_tx_update(). + */ + wstat = &(sc->wcid_stats[wcid]); + (*wstat)[RUN_TXCNT]++; + if (stat & RT2860_TXQ_OK) + (*wstat)[RUN_SUCCESS]++; + else + ifp->if_oerrors++; + /* + * Check if there were retries, ie if the Tx success rate is + * different from the requested rate. Note that it works only + * because we do not allow rate fallback from OFDM to CCK. + */ + mcs = (stat >> RT2860_TXQ_MCS_SHIFT) & 0x7f; + pid = (stat >> RT2860_TXQ_PID_SHIFT) & 0xf; + if ((retry = pid -1 - mcs) > 0) { + (*wstat)[RUN_TXCNT] += retry; + (*wstat)[RUN_RETRY] += retry; + } + } + DPRINTFN(3, "count=%d\n", sc->fifo_cnt); + + sc->fifo_cnt = 0; +} + +static void +run_iter_func(void *arg, struct ieee80211_node *ni) +{ + struct run_softc *sc = arg; + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct run_node *rn = (void *)ni; + union run_stats sta[2]; + uint16_t (*wstat)[3]; + int txcnt, success, retrycnt, error; + + RUN_LOCK(sc); + + if (sc->rvp_cnt <= 1 && (vap->iv_opmode == IEEE80211_M_IBSS || + vap->iv_opmode == IEEE80211_M_STA)) { + /* read statistic counters (clear on read) and update AMRR state */ + error = run_read_region_1(sc, RT2860_TX_STA_CNT0, (uint8_t *)sta, + sizeof sta); + if (error != 0) + goto fail; + + /* count failed TX as errors */ + ifp->if_oerrors += le16toh(sta[0].error.fail); + + retrycnt = le16toh(sta[1].tx.retry); + success = le16toh(sta[1].tx.success); + txcnt = retrycnt + success + le16toh(sta[0].error.fail); + + DPRINTFN(3, "retrycnt=%d success=%d failcnt=%d\n", + retrycnt, success, le16toh(sta[0].error.fail)); + } else { + wstat = &(sc->wcid_stats[RUN_AID2WCID(ni->ni_associd)]); + + if (wstat == &(sc->wcid_stats[0]) || + wstat > &(sc->wcid_stats[RT2870_WCID_MAX])) + goto fail; + + txcnt = (*wstat)[RUN_TXCNT]; + success = (*wstat)[RUN_SUCCESS]; + retrycnt = (*wstat)[RUN_RETRY]; + DPRINTFN(3, "retrycnt=%d txcnt=%d success=%d\n", + retrycnt, txcnt, success); + + memset(wstat, 0, sizeof(*wstat)); + } + + ieee80211_ratectl_tx_update(vap, ni, &txcnt, &success, &retrycnt); + rn->amrr_ridx = ieee80211_ratectl_rate(ni, NULL, 0); + +fail: + RUN_UNLOCK(sc); + + DPRINTFN(3, "ridx=%d\n", rn->amrr_ridx); +} + +static void +run_newassoc_cb(void *arg) +{ + struct run_cmdq *cmdq = arg; + struct ieee80211_node *ni = cmdq->arg1; + struct run_softc *sc = ni->ni_vap->iv_ic->ic_ifp->if_softc; + uint8_t wcid = cmdq->wcid; + + RUN_LOCK_ASSERT(sc, MA_OWNED); + + run_write_region_1(sc, RT2860_WCID_ENTRY(wcid), + ni->ni_macaddr, IEEE80211_ADDR_LEN); + + memset(&(sc->wcid_stats[wcid]), 0, sizeof(sc->wcid_stats[wcid])); +} + +static void +run_newassoc(struct ieee80211_node *ni, int isnew) +{ + struct run_node *rn = (void *)ni; + struct ieee80211_rateset *rs = &ni->ni_rates; + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = vap->iv_ic; + struct run_softc *sc = ic->ic_ifp->if_softc; + uint8_t rate; + uint8_t ridx; + uint8_t wcid = RUN_AID2WCID(ni->ni_associd); + int i, j; + + if (wcid > RT2870_WCID_MAX) { + device_printf(sc->sc_dev, "wcid=%d out of range\n", wcid); + return; + } + + /* only interested in true associations */ + if (isnew && ni->ni_associd != 0) { + + /* + * This function could is called though timeout function. + * Need to defer. + */ + uint32_t cnt = RUN_CMDQ_GET(&sc->cmdq_store); + DPRINTF("cmdq_store=%d\n", cnt); + sc->cmdq[cnt].func = run_newassoc_cb; + sc->cmdq[cnt].arg0 = NULL; + sc->cmdq[cnt].arg1 = ni; + sc->cmdq[cnt].wcid = wcid; + ieee80211_runtask(ic, &sc->cmdq_task); + } + + DPRINTF("new assoc isnew=%d associd=%x addr=%s\n", + isnew, ni->ni_associd, ether_sprintf(ni->ni_macaddr)); + + for (i = 0; i < rs->rs_nrates; i++) { + rate = rs->rs_rates[i] & IEEE80211_RATE_VAL; + /* convert 802.11 rate to hardware rate index */ + for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++) + if (rt2860_rates[ridx].rate == rate) + break; + rn->ridx[i] = ridx; + /* determine rate of control response frames */ + for (j = i; j >= 0; j--) { + if ((rs->rs_rates[j] & IEEE80211_RATE_BASIC) && + rt2860_rates[rn->ridx[i]].phy == + rt2860_rates[rn->ridx[j]].phy) + break; + } + if (j >= 0) { + rn->ctl_ridx[i] = rn->ridx[j]; + } else { + /* no basic rate found, use mandatory one */ + rn->ctl_ridx[i] = rt2860_rates[ridx].ctl_ridx; + } + DPRINTF("rate=0x%02x ridx=%d ctl_ridx=%d\n", + rs->rs_rates[i], rn->ridx[i], rn->ctl_ridx[i]); + } + rate = vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)].mgmtrate; + for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++) + if (rt2860_rates[ridx].rate == rate) + break; + rn->mgt_ridx = ridx; + DPRINTF("rate=%d, mgmt_ridx=%d\n", rate, rn->mgt_ridx); + + usb_callout_reset(&sc->ratectl_ch, hz, run_ratectl_to, sc); +} + +/* + * Return the Rx chain with the highest RSSI for a given frame. + */ +static __inline uint8_t +run_maxrssi_chain(struct run_softc *sc, const struct rt2860_rxwi *rxwi) +{ + uint8_t rxchain = 0; + + if (sc->nrxchains > 1) { + if (rxwi->rssi[1] > rxwi->rssi[rxchain]) + rxchain = 1; + if (sc->nrxchains > 2) + if (rxwi->rssi[2] > rxwi->rssi[rxchain]) + rxchain = 2; + } + return (rxchain); +} + +static void +run_rx_frame(struct run_softc *sc, struct mbuf *m, uint32_t dmalen) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211_frame *wh; + struct ieee80211_node *ni; + struct rt2870_rxd *rxd; + struct rt2860_rxwi *rxwi; + uint32_t flags; + uint16_t len, phy; + uint8_t ant, rssi; + int8_t nf; + + rxwi = mtod(m, struct rt2860_rxwi *); + len = le16toh(rxwi->len) & 0xfff; + if (__predict_false(len > dmalen)) { + m_freem(m); + ifp->if_ierrors++; + DPRINTF("bad RXWI length %u > %u\n", len, dmalen); + return; + } + /* Rx descriptor is located at the end */ + rxd = (struct rt2870_rxd *)(mtod(m, caddr_t) + dmalen); + flags = le32toh(rxd->flags); + + if (__predict_false(flags & (RT2860_RX_CRCERR | RT2860_RX_ICVERR))) { + m_freem(m); + ifp->if_ierrors++; + DPRINTF("%s error.\n", (flags & RT2860_RX_CRCERR)?"CRC":"ICV"); + return; + } + + m->m_data += sizeof(struct rt2860_rxwi); + m->m_pkthdr.len = m->m_len -= sizeof(struct rt2860_rxwi); + + wh = mtod(m, struct ieee80211_frame *); + + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + wh->i_fc[1] &= ~IEEE80211_FC1_WEP; + m->m_flags |= M_WEP; + } + + if (flags & RT2860_RX_L2PAD) { + DPRINTFN(8, "received RT2860_RX_L2PAD frame\n"); + len += 2; + } + + ni = ieee80211_find_rxnode(ic, + mtod(m, struct ieee80211_frame_min *)); + + if (__predict_false(flags & RT2860_RX_MICERR)) { + /* report MIC failures to net80211 for TKIP */ + if (ni != NULL) + ieee80211_notify_michael_failure(ni->ni_vap, wh, rxwi->keyidx); + m_freem(m); + ifp->if_ierrors++; + DPRINTF("MIC error. Someone is lying.\n"); + return; + } + + ant = run_maxrssi_chain(sc, rxwi); + rssi = rxwi->rssi[ant]; + nf = run_rssi2dbm(sc, rssi, ant); + + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = len; + + if (ni != NULL) { + (void)ieee80211_input(ni, m, rssi, nf); + ieee80211_free_node(ni); + } else { + (void)ieee80211_input_all(ic, m, rssi, nf); + } + + if (__predict_false(ieee80211_radiotap_active(ic))) { + struct run_rx_radiotap_header *tap = &sc->sc_rxtap; + + tap->wr_flags = 0; + tap->wr_chan_freq = htole16(ic->ic_bsschan->ic_freq); + tap->wr_chan_flags = htole16(ic->ic_bsschan->ic_flags); + tap->wr_antsignal = rssi; + tap->wr_antenna = ant; + tap->wr_dbm_antsignal = run_rssi2dbm(sc, rssi, ant); + tap->wr_rate = 2; /* in case it can't be found below */ + phy = le16toh(rxwi->phy); + switch (phy & RT2860_PHY_MODE) { + case RT2860_PHY_CCK: + switch ((phy & RT2860_PHY_MCS) & ~RT2860_PHY_SHPRE) { + case 0: tap->wr_rate = 2; break; + case 1: tap->wr_rate = 4; break; + case 2: tap->wr_rate = 11; break; + case 3: tap->wr_rate = 22; break; + } + if (phy & RT2860_PHY_SHPRE) + tap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; + break; + case RT2860_PHY_OFDM: + switch (phy & RT2860_PHY_MCS) { + case 0: tap->wr_rate = 12; break; + case 1: tap->wr_rate = 18; break; + case 2: tap->wr_rate = 24; break; + case 3: tap->wr_rate = 36; break; + case 4: tap->wr_rate = 48; break; + case 5: tap->wr_rate = 72; break; + case 6: tap->wr_rate = 96; break; + case 7: tap->wr_rate = 108; break; + } + break; + } + } +} + +static void +run_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct run_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m = NULL; + struct mbuf *m0; + uint32_t dmalen; + int xferlen; + + usbd_xfer_status(xfer, &xferlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTFN(15, "rx done, actlen=%d\n", xferlen); + + if (xferlen < sizeof (uint32_t) + + sizeof (struct rt2860_rxwi) + sizeof (struct rt2870_rxd)) { + DPRINTF("xfer too short %d\n", xferlen); + goto tr_setup; + } + + m = sc->rx_m; + sc->rx_m = NULL; + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + if (sc->rx_m == NULL) { + sc->rx_m = m_getjcl(M_DONTWAIT, MT_DATA, M_PKTHDR, + MJUMPAGESIZE /* xfer can be bigger than MCLBYTES */); + } + if (sc->rx_m == NULL) { + DPRINTF("could not allocate mbuf - idle with stall\n"); + ifp->if_ierrors++; + usbd_xfer_set_stall(xfer); + usbd_xfer_set_frames(xfer, 0); + } else { + /* + * Directly loading a mbuf cluster into DMA to + * save some data copying. This works because + * there is only one cluster. + */ + usbd_xfer_set_frame_data(xfer, 0, + mtod(sc->rx_m, caddr_t), RUN_MAX_RXSZ); + usbd_xfer_set_frames(xfer, 1); + } + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + + if (error == USB_ERR_TIMEOUT) + device_printf(sc->sc_dev, "device timeout\n"); + + ifp->if_ierrors++; + + goto tr_setup; + } + if (sc->rx_m != NULL) { + m_freem(sc->rx_m); + sc->rx_m = NULL; + } + break; + } + + if (m == NULL) + return; + + /* inputting all the frames must be last */ + + RUN_UNLOCK(sc); + + m->m_pkthdr.len = m->m_len = xferlen; + + /* HW can aggregate multiple 802.11 frames in a single USB xfer */ + for(;;) { + dmalen = le32toh(*mtod(m, uint32_t *)) & 0xffff; + + if ((dmalen == 0) || ((dmalen & 3) != 0)) { + DPRINTF("bad DMA length %u\n", dmalen); + break; + } + if ((dmalen + 8) > xferlen) { + DPRINTF("bad DMA length %u > %d\n", + dmalen + 8, xferlen); + break; + } + + /* If it is the last one or a single frame, we won't copy. */ + if ((xferlen -= dmalen + 8) <= 8) { + /* trim 32-bit DMA-len header */ + m->m_data += 4; + m->m_pkthdr.len = m->m_len -= 4; + run_rx_frame(sc, m, dmalen); + break; + } + + /* copy aggregated frames to another mbuf */ + m0 = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (__predict_false(m0 == NULL)) { + DPRINTF("could not allocate mbuf\n"); + ifp->if_ierrors++; + break; + } + m_copydata(m, 4 /* skip 32-bit DMA-len header */, + dmalen + sizeof(struct rt2870_rxd), mtod(m0, caddr_t)); + m0->m_pkthdr.len = m0->m_len = + dmalen + sizeof(struct rt2870_rxd); + run_rx_frame(sc, m0, dmalen); + + /* update data ptr */ + m->m_data += dmalen + 8; + m->m_pkthdr.len = m->m_len -= dmalen + 8; + } + + RUN_LOCK(sc); +} + +static void +run_tx_free(struct run_endpoint_queue *pq, + struct run_tx_data *data, int txerr) +{ + if (data->m != NULL) { + if (data->m->m_flags & M_TXCB) + ieee80211_process_callback(data->ni, data->m, + txerr ? ETIMEDOUT : 0); + m_freem(data->m); + data->m = NULL; + + if (data->ni == NULL) { + DPRINTF("no node\n"); + } else { + ieee80211_free_node(data->ni); + data->ni = NULL; + } + } + + STAILQ_INSERT_TAIL(&pq->tx_fh, data, next); + pq->tx_nfree++; +} + +static void +run_bulk_tx_callbackN(struct usb_xfer *xfer, usb_error_t error, unsigned int index) +{ + struct run_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct run_tx_data *data; + struct ieee80211vap *vap = NULL; + struct usb_page_cache *pc; + struct run_endpoint_queue *pq = &sc->sc_epq[index]; + struct mbuf *m; + usb_frlength_t size; + int actlen; + int sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete: %d " + "bytes @ index %d\n", actlen, index); + + data = usbd_xfer_get_priv(xfer); + + run_tx_free(pq, data, 0); + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + usbd_xfer_set_priv(xfer, NULL); + + ifp->if_opackets++; + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + data = STAILQ_FIRST(&pq->tx_qh); + if (data == NULL) + break; + + STAILQ_REMOVE_HEAD(&pq->tx_qh, next); + + m = data->m; + if ((m->m_pkthdr.len + + sizeof(data->desc) + 3 + 8) > RUN_MAX_TXSZ) { + DPRINTF("data overflow, %u bytes\n", + m->m_pkthdr.len); + + ifp->if_oerrors++; + + run_tx_free(pq, data, 1); + + goto tr_setup; + } + + pc = usbd_xfer_get_frame(xfer, 0); + size = sizeof(data->desc); + usbd_copy_in(pc, 0, &data->desc, size); + usbd_m_copy_in(pc, size, m, 0, m->m_pkthdr.len); + size += m->m_pkthdr.len; + /* + * Align end on a 4-byte boundary, pad 8 bytes (CRC + + * 4-byte padding), and be sure to zero those trailing + * bytes: + */ + usbd_frame_zero(pc, size, ((-size) & 3) + 8); + size += ((-size) & 3) + 8; + + vap = data->ni->ni_vap; + if (ieee80211_radiotap_active_vap(vap)) { + struct run_tx_radiotap_header *tap = &sc->sc_txtap; + struct rt2860_txwi *txwi = + (struct rt2860_txwi *)(&data->desc + sizeof(struct rt2870_txd)); + + tap->wt_flags = 0; + tap->wt_rate = rt2860_rates[data->ridx].rate; + tap->wt_chan_freq = htole16(vap->iv_bss->ni_chan->ic_freq); + tap->wt_chan_flags = htole16(vap->iv_bss->ni_chan->ic_flags); + tap->wt_hwqueue = index; + if (le16toh(txwi->phy) & RT2860_PHY_SHPRE) + tap->wt_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; + + ieee80211_radiotap_tx(vap, m); + } + + DPRINTFN(11, "sending frame len=%u/%u @ index %d\n", + m->m_pkthdr.len, size, index); + + usbd_xfer_set_frame_len(xfer, 0, size); + usbd_xfer_set_priv(xfer, data); + + usbd_transfer_submit(xfer); + + RUN_UNLOCK(sc); + run_start(ifp); + RUN_LOCK(sc); + + break; + + default: + DPRINTF("USB transfer error, %s\n", + usbd_errstr(error)); + + data = usbd_xfer_get_priv(xfer); + + ifp->if_oerrors++; + + if (data != NULL) { + if(data->ni != NULL) + vap = data->ni->ni_vap; + run_tx_free(pq, data, error); + usbd_xfer_set_priv(xfer, NULL); + } + if (vap == NULL) + vap = TAILQ_FIRST(&ic->ic_vaps); + + if (error != USB_ERR_CANCELLED) { + if (error == USB_ERR_TIMEOUT) { + device_printf(sc->sc_dev, "device timeout\n"); + uint32_t i = RUN_CMDQ_GET(&sc->cmdq_store); + DPRINTF("cmdq_store=%d\n", i); + sc->cmdq[i].func = run_usb_timeout_cb; + sc->cmdq[i].arg0 = vap; + ieee80211_runtask(ic, &sc->cmdq_task); + } + + /* + * Try to clear stall first, also if other + * errors occur, hence clearing stall + * introduces a 50 ms delay: + */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +run_bulk_tx_callback0(struct usb_xfer *xfer, usb_error_t error) +{ + run_bulk_tx_callbackN(xfer, error, 0); +} + +static void +run_bulk_tx_callback1(struct usb_xfer *xfer, usb_error_t error) +{ + run_bulk_tx_callbackN(xfer, error, 1); +} + +static void +run_bulk_tx_callback2(struct usb_xfer *xfer, usb_error_t error) +{ + run_bulk_tx_callbackN(xfer, error, 2); +} + +static void +run_bulk_tx_callback3(struct usb_xfer *xfer, usb_error_t error) +{ + run_bulk_tx_callbackN(xfer, error, 3); +} + +static void +run_bulk_tx_callback4(struct usb_xfer *xfer, usb_error_t error) +{ + run_bulk_tx_callbackN(xfer, error, 4); +} + +static void +run_bulk_tx_callback5(struct usb_xfer *xfer, usb_error_t error) +{ + run_bulk_tx_callbackN(xfer, error, 5); +} + +static void +run_set_tx_desc(struct run_softc *sc, struct run_tx_data *data) +{ + struct mbuf *m = data->m; + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + struct ieee80211vap *vap = data->ni->ni_vap; + struct ieee80211_frame *wh; + struct rt2870_txd *txd; + struct rt2860_txwi *txwi; + uint16_t xferlen; + uint16_t mcs; + uint8_t ridx = data->ridx; + uint8_t pad; + + /* get MCS code from rate index */ + mcs = rt2860_rates[ridx].mcs; + + xferlen = sizeof(*txwi) + m->m_pkthdr.len; + + /* roundup to 32-bit alignment */ + xferlen = (xferlen + 3) & ~3; + + txd = (struct rt2870_txd *)&data->desc; + txd->len = htole16(xferlen); + + wh = mtod(m, struct ieee80211_frame *); + + /* + * Ether both are true or both are false, the header + * are nicely aligned to 32-bit. So, no L2 padding. + */ + if(IEEE80211_HAS_ADDR4(wh) == IEEE80211_QOS_HAS_SEQ(wh)) + pad = 0; + else + pad = 2; + + /* setup TX Wireless Information */ + txwi = (struct rt2860_txwi *)(txd + 1); + txwi->len = htole16(m->m_pkthdr.len - pad); + if (rt2860_rates[ridx].phy == IEEE80211_T_DS) { + txwi->phy = htole16(RT2860_PHY_CCK); + if (ridx != RT2860_RIDX_CCK1 && + (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) + mcs |= RT2860_PHY_SHPRE; + } else + txwi->phy = htole16(RT2860_PHY_OFDM); + txwi->phy |= htole16(mcs); + + /* check if RTS/CTS or CTS-to-self protection is required */ + if (!IEEE80211_IS_MULTICAST(wh->i_addr1) && + (m->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold || + ((ic->ic_flags & IEEE80211_F_USEPROT) && + rt2860_rates[ridx].phy == IEEE80211_T_OFDM))) + txwi->txop |= RT2860_TX_TXOP_HT; + else + txwi->txop |= RT2860_TX_TXOP_BACKOFF; + + if (vap->iv_opmode != IEEE80211_M_STA && !IEEE80211_QOS_HAS_SEQ(wh)) + txwi->xflags |= RT2860_TX_NSEQ; +} + +/* This function must be called locked */ +static int +run_tx(struct run_softc *sc, struct mbuf *m, struct ieee80211_node *ni) +{ + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211_frame *wh; + struct ieee80211_channel *chan; + const struct ieee80211_txparam *tp; + struct run_node *rn = (void *)ni; + struct run_tx_data *data; + struct rt2870_txd *txd; + struct rt2860_txwi *txwi; + uint16_t qos; + uint16_t dur; + uint16_t qid; + uint8_t type; + uint8_t tid; + uint8_t ridx; + uint8_t ctl_ridx; + uint8_t qflags; + uint8_t xflags = 0; + int hasqos; + + RUN_LOCK_ASSERT(sc, MA_OWNED); + + wh = mtod(m, struct ieee80211_frame *); + + type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; + + /* + * There are 7 bulk endpoints: 1 for RX + * and 6 for TX (4 EDCAs + HCCA + Prio). + * Update 03-14-2009: some devices like the Planex GW-US300MiniS + * seem to have only 4 TX bulk endpoints (Fukaumi Naoki). + */ + if ((hasqos = IEEE80211_QOS_HAS_SEQ(wh))) { + uint8_t *frm; + + if(IEEE80211_HAS_ADDR4(wh)) + frm = ((struct ieee80211_qosframe_addr4 *)wh)->i_qos; + else + frm =((struct ieee80211_qosframe *)wh)->i_qos; + + qos = le16toh(*(const uint16_t *)frm); + tid = qos & IEEE80211_QOS_TID; + qid = TID_TO_WME_AC(tid); + } else { + qos = 0; + tid = 0; + qid = WME_AC_BE; + } + qflags = (qid < 4) ? RT2860_TX_QSEL_EDCA : RT2860_TX_QSEL_HCCA; + + DPRINTFN(8, "qos %d\tqid %d\ttid %d\tqflags %x\n", + qos, qid, tid, qflags); + + chan = (ni->ni_chan != IEEE80211_CHAN_ANYC)?ni->ni_chan:ic->ic_curchan; + tp = &vap->iv_txparms[ieee80211_chan2mode(chan)]; + + /* pickup a rate index */ + if (IEEE80211_IS_MULTICAST(wh->i_addr1) || + type != IEEE80211_FC0_TYPE_DATA) { + ridx = (ic->ic_curmode == IEEE80211_MODE_11A) ? + RT2860_RIDX_OFDM6 : RT2860_RIDX_CCK1; + ctl_ridx = rt2860_rates[ridx].ctl_ridx; + } else { + if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) + ridx = rn->fix_ridx; + else + ridx = rn->amrr_ridx; + ctl_ridx = rt2860_rates[ridx].ctl_ridx; + } + + if (!IEEE80211_IS_MULTICAST(wh->i_addr1) && + (!hasqos || (qos & IEEE80211_QOS_ACKPOLICY) != + IEEE80211_QOS_ACKPOLICY_NOACK)) { + xflags |= RT2860_TX_ACK; + if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) + dur = rt2860_rates[ctl_ridx].sp_ack_dur; + else + dur = rt2860_rates[ctl_ridx].lp_ack_dur; + *(uint16_t *)wh->i_dur = htole16(dur); + } + + /* reserve slots for mgmt packets, just in case */ + if (sc->sc_epq[qid].tx_nfree < 3) { + DPRINTFN(10, "tx ring %d is full\n", qid); + return (-1); + } + + data = STAILQ_FIRST(&sc->sc_epq[qid].tx_fh); + STAILQ_REMOVE_HEAD(&sc->sc_epq[qid].tx_fh, next); + sc->sc_epq[qid].tx_nfree--; + + txd = (struct rt2870_txd *)&data->desc; + txd->flags = qflags; + txwi = (struct rt2860_txwi *)(txd + 1); + txwi->xflags = xflags; + txwi->wcid = IEEE80211_IS_MULTICAST(wh->i_addr1) ? + 0 : RUN_AID2WCID(ni->ni_associd); + /* clear leftover garbage bits */ + txwi->flags = 0; + txwi->txop = 0; + + data->m = m; + data->ni = ni; + data->ridx = ridx; + + run_set_tx_desc(sc, data); + + /* + * The chip keeps track of 2 kind of Tx stats, + * * TX_STAT_FIFO, for per WCID stats, and + * * TX_STA_CNT0 for all-TX-in-one stats. + * + * To use FIFO stats, we need to store MCS into the driver-private + * PacketID field. So that, we can tell whose stats when we read them. + * We add 1 to the MCS because setting the PacketID field to 0 means + * that we don't want feedback in TX_STAT_FIFO. + * And, that's what we want for STA mode, since TX_STA_CNT0 does the job. + * + * FIFO stats doesn't count Tx with WCID 0xff, so we do this in run_tx(). + */ + if (sc->rvp_cnt > 1 || vap->iv_opmode == IEEE80211_M_HOSTAP || + vap->iv_opmode == IEEE80211_M_MBSS) { + uint16_t pid = (rt2860_rates[ridx].mcs + 1) & 0xf; + txwi->len |= htole16(pid << RT2860_TX_PID_SHIFT); + + /* + * Unlike PCI based devices, we don't get any interrupt from + * USB devices, so we simulate FIFO-is-full interrupt here. + * Ralink recomends to drain FIFO stats every 100 ms, but 16 slots + * quickly get fulled. To prevent overflow, increment a counter on + * every FIFO stat request, so we know how many slots are left. + * We do this only in HOSTAP or multiple vap mode since FIFO stats + * are used only in those modes. + * We just drain stats. AMRR gets updated every 1 sec by + * run_ratectl_cb() via callout. + * Call it early. Otherwise overflow. + */ + if (sc->fifo_cnt++ == 10) { + /* + * With multiple vaps or if_bridge, if_start() is called + * with a non-sleepable lock, tcpinp. So, need to defer. + */ + uint32_t i = RUN_CMDQ_GET(&sc->cmdq_store); + DPRINTFN(6, "cmdq_store=%d\n", i); + sc->cmdq[i].func = run_drain_fifo; + sc->cmdq[i].arg0 = sc; + ieee80211_runtask(ic, &sc->cmdq_task); + } + } + + STAILQ_INSERT_TAIL(&sc->sc_epq[qid].tx_qh, data, next); + + usbd_transfer_start(sc->sc_xfer[qid]); + + DPRINTFN(8, "sending data frame len=%d rate=%d qid=%d\n", m->m_pkthdr.len + + (int)(sizeof (struct rt2870_txd) + sizeof (struct rt2860_rxwi)), + rt2860_rates[ridx].rate, qid); + + return (0); +} + +static int +run_tx_mgt(struct run_softc *sc, struct mbuf *m, struct ieee80211_node *ni) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct run_node *rn = (void *)ni; + struct run_tx_data *data; + struct ieee80211_frame *wh; + struct rt2870_txd *txd; + struct rt2860_txwi *txwi; + uint16_t dur; + uint8_t ridx = rn->mgt_ridx; + uint8_t type; + uint8_t xflags = 0; + uint8_t wflags = 0; + + RUN_LOCK_ASSERT(sc, MA_OWNED); + + wh = mtod(m, struct ieee80211_frame *); + + type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; + + /* tell hardware to add timestamp for probe responses */ + if ((wh->i_fc[0] & + (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) == + (IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_RESP)) + wflags |= RT2860_TX_TS; + else if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + xflags |= RT2860_TX_ACK; + + dur = ieee80211_ack_duration(ic->ic_rt, rt2860_rates[ridx].rate, + ic->ic_flags & IEEE80211_F_SHPREAMBLE); + *(uint16_t *)wh->i_dur = htole16(dur); + } + + if (sc->sc_epq[0].tx_nfree == 0) { + /* let caller free mbuf */ + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + return (EIO); + } + data = STAILQ_FIRST(&sc->sc_epq[0].tx_fh); + STAILQ_REMOVE_HEAD(&sc->sc_epq[0].tx_fh, next); + sc->sc_epq[0].tx_nfree--; + + txd = (struct rt2870_txd *)&data->desc; + txd->flags = RT2860_TX_QSEL_EDCA; + txwi = (struct rt2860_txwi *)(txd + 1); + txwi->wcid = 0xff; + txwi->flags = wflags; + txwi->xflags = xflags; + txwi->txop = 0; /* clear leftover garbage bits */ + + data->m = m; + data->ni = ni; + data->ridx = ridx; + + run_set_tx_desc(sc, data); + + DPRINTFN(10, "sending mgt frame len=%d rate=%d\n", m->m_pkthdr.len + + (int)(sizeof (struct rt2870_txd) + sizeof (struct rt2860_rxwi)), + rt2860_rates[ridx].rate); + + STAILQ_INSERT_TAIL(&sc->sc_epq[0].tx_qh, data, next); + + usbd_transfer_start(sc->sc_xfer[0]); + + return (0); +} + +static int +run_sendprot(struct run_softc *sc, + const struct mbuf *m, struct ieee80211_node *ni, int prot, int rate) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ieee80211_frame *wh; + struct run_tx_data *data; + struct rt2870_txd *txd; + struct rt2860_txwi *txwi; + struct mbuf *mprot; + int ridx; + int protrate; + int ackrate; + int pktlen; + int isshort; + uint16_t dur; + uint8_t type; + uint8_t wflags = 0; + uint8_t xflags = 0; + + RUN_LOCK_ASSERT(sc, MA_OWNED); + + KASSERT(prot == IEEE80211_PROT_RTSCTS || prot == IEEE80211_PROT_CTSONLY, + ("protection %d", prot)); + + wh = mtod(m, struct ieee80211_frame *); + pktlen = m->m_pkthdr.len + IEEE80211_CRC_LEN; + type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; + + protrate = ieee80211_ctl_rate(ic->ic_rt, rate); + ackrate = ieee80211_ack_rate(ic->ic_rt, rate); + + isshort = (ic->ic_flags & IEEE80211_F_SHPREAMBLE) != 0; + dur = ieee80211_compute_duration(ic->ic_rt, pktlen, rate, isshort) + + ieee80211_ack_duration(ic->ic_rt, rate, isshort); + wflags = RT2860_TX_FRAG; + + /* check that there are free slots before allocating the mbuf */ + if (sc->sc_epq[0].tx_nfree == 0) { + /* let caller free mbuf */ + sc->sc_ifp->if_drv_flags |= IFF_DRV_OACTIVE; + return (ENOBUFS); + } + + if (prot == IEEE80211_PROT_RTSCTS) { + /* NB: CTS is the same size as an ACK */ + dur += ieee80211_ack_duration(ic->ic_rt, rate, isshort); + xflags |= RT2860_TX_ACK; + mprot = ieee80211_alloc_rts(ic, wh->i_addr1, wh->i_addr2, dur); + } else { + mprot = ieee80211_alloc_cts(ic, ni->ni_vap->iv_myaddr, dur); + } + if (mprot == NULL) { + sc->sc_ifp->if_oerrors++; + DPRINTF("could not allocate mbuf\n"); + return (ENOBUFS); + } + + data = STAILQ_FIRST(&sc->sc_epq[0].tx_fh); + STAILQ_REMOVE_HEAD(&sc->sc_epq[0].tx_fh, next); + sc->sc_epq[0].tx_nfree--; + + txd = (struct rt2870_txd *)&data->desc; + txd->flags = RT2860_TX_QSEL_EDCA; + txwi = (struct rt2860_txwi *)(txd + 1); + txwi->wcid = 0xff; + txwi->flags = wflags; + txwi->xflags = xflags; + txwi->txop = 0; /* clear leftover garbage bits */ + + data->m = mprot; + data->ni = ieee80211_ref_node(ni); + + for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++) + if (rt2860_rates[ridx].rate == protrate) + break; + data->ridx = ridx; + + run_set_tx_desc(sc, data); + + DPRINTFN(1, "sending prot len=%u rate=%u\n", + m->m_pkthdr.len, rate); + + STAILQ_INSERT_TAIL(&sc->sc_epq[0].tx_qh, data, next); + + usbd_transfer_start(sc->sc_xfer[0]); + + return (0); +} + +static int +run_tx_param(struct run_softc *sc, struct mbuf *m, struct ieee80211_node *ni, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ieee80211_frame *wh; + struct run_tx_data *data; + struct rt2870_txd *txd; + struct rt2860_txwi *txwi; + uint8_t type; + uint8_t ridx; + uint8_t rate; + uint8_t opflags = 0; + uint8_t xflags = 0; + int error; + + RUN_LOCK_ASSERT(sc, MA_OWNED); + + KASSERT(params != NULL, ("no raw xmit params")); + + wh = mtod(m, struct ieee80211_frame *); + type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; + + rate = params->ibp_rate0; + if (!ieee80211_isratevalid(ic->ic_rt, rate)) { + /* let caller free mbuf */ + return (EINVAL); + } + + if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0) + xflags |= RT2860_TX_ACK; + if (params->ibp_flags & (IEEE80211_BPF_RTS|IEEE80211_BPF_CTS)) { + error = run_sendprot(sc, m, ni, + params->ibp_flags & IEEE80211_BPF_RTS ? + IEEE80211_PROT_RTSCTS : IEEE80211_PROT_CTSONLY, + rate); + if (error) { + /* let caller free mbuf */ + return error; + } + opflags |= /*XXX RT2573_TX_LONG_RETRY |*/ RT2860_TX_TXOP_SIFS; + } + + if (sc->sc_epq[0].tx_nfree == 0) { + /* let caller free mbuf */ + sc->sc_ifp->if_drv_flags |= IFF_DRV_OACTIVE; + DPRINTF("sending raw frame, but tx ring is full\n"); + return (EIO); + } + data = STAILQ_FIRST(&sc->sc_epq[0].tx_fh); + STAILQ_REMOVE_HEAD(&sc->sc_epq[0].tx_fh, next); + sc->sc_epq[0].tx_nfree--; + + txd = (struct rt2870_txd *)&data->desc; + txd->flags = RT2860_TX_QSEL_EDCA; + txwi = (struct rt2860_txwi *)(txd + 1); + txwi->wcid = 0xff; + txwi->xflags = xflags; + txwi->txop = opflags; + txwi->flags = 0; /* clear leftover garbage bits */ + + data->m = m; + data->ni = ni; + for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++) + if (rt2860_rates[ridx].rate == rate) + break; + data->ridx = ridx; + + run_set_tx_desc(sc, data); + + DPRINTFN(10, "sending raw frame len=%u rate=%u\n", + m->m_pkthdr.len, rate); + + STAILQ_INSERT_TAIL(&sc->sc_epq[0].tx_qh, data, next); + + usbd_transfer_start(sc->sc_xfer[0]); + + return (0); +} + +static int +run_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ifnet *ifp = ni->ni_ic->ic_ifp; + struct run_softc *sc = ifp->if_softc; + int error = 0; + + RUN_LOCK(sc); + + /* prevent management frames from being sent if we're not ready */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + error = ENETDOWN; + goto done; + } + + if (params == NULL) { + /* tx mgt packet */ + if ((error = run_tx_mgt(sc, m, ni)) != 0) { + ifp->if_oerrors++; + DPRINTF("mgt tx failed\n"); + goto done; + } + } else { + /* tx raw packet with param */ + if ((error = run_tx_param(sc, m, ni, params)) != 0) { + ifp->if_oerrors++; + DPRINTF("tx with param failed\n"); + goto done; + } + } + + ifp->if_opackets++; + +done: + RUN_UNLOCK(sc); + + if (error != 0) { + if(m != NULL) + m_freem(m); + ieee80211_free_node(ni); + } + + return (error); +} + +static void +run_start(struct ifnet *ifp) +{ + struct run_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + struct mbuf *m; + + RUN_LOCK(sc); + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + RUN_UNLOCK(sc); + return; + } + + for (;;) { + /* send data frames */ + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + + ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; + if (run_tx(sc, m, ni) != 0) { + IFQ_DRV_PREPEND(&ifp->if_snd, m); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + break; + } + } + + RUN_UNLOCK(sc); +} + +static int +run_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct run_softc *sc = ifp->if_softc; + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *) data; + int startall = 0; + int error = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + RUN_LOCK(sc); + if (ifp->if_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)){ + startall = 1; + run_init_locked(sc); + } else + run_update_promisc_locked(ifp); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING && + (ic->ic_nrunning == 0 || sc->rvp_cnt <= 1)) { + run_stop(sc); + } + } + RUN_UNLOCK(sc); + if (startall) + ieee80211_start_all(ic); + break; + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + case SIOCGIFADDR: + error = ether_ioctl(ifp, cmd, data); + break; + default: + error = EINVAL; + break; + } + + return (error); +} + +static void +run_set_agc(struct run_softc *sc, uint8_t agc) +{ + uint8_t bbp; + + if (sc->mac_ver == 0x3572) { + run_bbp_read(sc, 27, &bbp); + bbp &= ~(0x3 << 5); + run_bbp_write(sc, 27, bbp | 0 << 5); /* select Rx0 */ + run_bbp_write(sc, 66, agc); + run_bbp_write(sc, 27, bbp | 1 << 5); /* select Rx1 */ + run_bbp_write(sc, 66, agc); + } else + run_bbp_write(sc, 66, agc); +} + +static void +run_select_chan_group(struct run_softc *sc, int group) +{ + uint32_t tmp; + uint8_t agc; + + run_bbp_write(sc, 62, 0x37 - sc->lna[group]); + run_bbp_write(sc, 63, 0x37 - sc->lna[group]); + run_bbp_write(sc, 64, 0x37 - sc->lna[group]); + run_bbp_write(sc, 86, 0x00); + + if (group == 0) { + if (sc->ext_2ghz_lna) { + run_bbp_write(sc, 82, 0x62); + run_bbp_write(sc, 75, 0x46); + } else { + run_bbp_write(sc, 82, 0x84); + run_bbp_write(sc, 75, 0x50); + } + } else { + if (sc->mac_ver == 0x3572) + run_bbp_write(sc, 82, 0x94); + else + run_bbp_write(sc, 82, 0xf2); + if (sc->ext_5ghz_lna) + run_bbp_write(sc, 75, 0x46); + else + run_bbp_write(sc, 75, 0x50); + } + + run_read(sc, RT2860_TX_BAND_CFG, &tmp); + tmp &= ~(RT2860_5G_BAND_SEL_N | RT2860_5G_BAND_SEL_P); + tmp |= (group == 0) ? RT2860_5G_BAND_SEL_N : RT2860_5G_BAND_SEL_P; + run_write(sc, RT2860_TX_BAND_CFG, tmp); + + /* enable appropriate Power Amplifiers and Low Noise Amplifiers */ + tmp = RT2860_RFTR_EN | RT2860_TRSW_EN | RT2860_LNA_PE0_EN; + if (sc->nrxchains > 1) + tmp |= RT2860_LNA_PE1_EN; + if (group == 0) { /* 2GHz */ + tmp |= RT2860_PA_PE_G0_EN; + if (sc->ntxchains > 1) + tmp |= RT2860_PA_PE_G1_EN; + } else { /* 5GHz */ + tmp |= RT2860_PA_PE_A0_EN; + if (sc->ntxchains > 1) + tmp |= RT2860_PA_PE_A1_EN; + } + if (sc->mac_ver == 0x3572) { + run_rt3070_rf_write(sc, 8, 0x00); + run_write(sc, RT2860_TX_PIN_CFG, tmp); + run_rt3070_rf_write(sc, 8, 0x80); + } else + run_write(sc, RT2860_TX_PIN_CFG, tmp); + + /* set initial AGC value */ + if (group == 0) { /* 2GHz band */ + if (sc->mac_ver >= 0x3070) + agc = 0x1c + sc->lna[0] * 2; + else + agc = 0x2e + sc->lna[0]; + } else { /* 5GHz band */ + if (sc->mac_ver == 0x3572) + agc = 0x22 + (sc->lna[group] * 5) / 3; + else + agc = 0x32 + (sc->lna[group] * 5) / 3; + } + run_set_agc(sc, agc); +} + +static void +run_rt2870_set_chan(struct run_softc *sc, uint32_t chan) +{ + const struct rfprog *rfprog = rt2860_rf2850; + uint32_t r2, r3, r4; + int8_t txpow1, txpow2; + int i; + + /* find the settings for this channel (we know it exists) */ + for (i = 0; rfprog[i].chan != chan; i++); + + r2 = rfprog[i].r2; + if (sc->ntxchains == 1) + r2 |= 1 << 12; /* 1T: disable Tx chain 2 */ + if (sc->nrxchains == 1) + r2 |= 1 << 15 | 1 << 4; /* 1R: disable Rx chains 2 & 3 */ + else if (sc->nrxchains == 2) + r2 |= 1 << 4; /* 2R: disable Rx chain 3 */ + + /* use Tx power values from EEPROM */ + txpow1 = sc->txpow1[i]; + txpow2 = sc->txpow2[i]; + if (chan > 14) { + if (txpow1 >= 0) + txpow1 = txpow1 << 1 | 1; + else + txpow1 = (7 + txpow1) << 1; + if (txpow2 >= 0) + txpow2 = txpow2 << 1 | 1; + else + txpow2 = (7 + txpow2) << 1; + } + r3 = rfprog[i].r3 | txpow1 << 7; + r4 = rfprog[i].r4 | sc->freq << 13 | txpow2 << 4; + + run_rt2870_rf_write(sc, RT2860_RF1, rfprog[i].r1); + run_rt2870_rf_write(sc, RT2860_RF2, r2); + run_rt2870_rf_write(sc, RT2860_RF3, r3); + run_rt2870_rf_write(sc, RT2860_RF4, r4); + + run_delay(sc, 10); + + run_rt2870_rf_write(sc, RT2860_RF1, rfprog[i].r1); + run_rt2870_rf_write(sc, RT2860_RF2, r2); + run_rt2870_rf_write(sc, RT2860_RF3, r3 | 1); + run_rt2870_rf_write(sc, RT2860_RF4, r4); + + run_delay(sc, 10); + + run_rt2870_rf_write(sc, RT2860_RF1, rfprog[i].r1); + run_rt2870_rf_write(sc, RT2860_RF2, r2); + run_rt2870_rf_write(sc, RT2860_RF3, r3); + run_rt2870_rf_write(sc, RT2860_RF4, r4); +} + +static void +run_rt3070_set_chan(struct run_softc *sc, uint32_t chan) +{ + int8_t txpow1, txpow2; + uint8_t rf; + int i; + + /* RT3070 is 2GHz only */ + KASSERT(chan >= 1 && chan <= 14, ("wrong channel selected\n")); + + /* find the settings for this channel (we know it exists) */ + for (i = 0; rt2860_rf2850[i].chan != chan; i++); + + /* use Tx power values from EEPROM */ + txpow1 = sc->txpow1[i]; + txpow2 = sc->txpow2[i]; + + run_rt3070_rf_write(sc, 2, rt3070_freqs[i].n); + run_rt3070_rf_write(sc, 3, rt3070_freqs[i].k); + run_rt3070_rf_read(sc, 6, &rf); + rf = (rf & ~0x03) | rt3070_freqs[i].r; + run_rt3070_rf_write(sc, 6, rf); + + /* set Tx0 power */ + run_rt3070_rf_read(sc, 12, &rf); + rf = (rf & ~0x1f) | txpow1; + run_rt3070_rf_write(sc, 12, rf); + + /* set Tx1 power */ + run_rt3070_rf_read(sc, 13, &rf); + rf = (rf & ~0x1f) | txpow2; + run_rt3070_rf_write(sc, 13, rf); + + run_rt3070_rf_read(sc, 1, &rf); + rf &= ~0xfc; + if (sc->ntxchains == 1) + rf |= 1 << 7 | 1 << 5; /* 1T: disable Tx chains 2 & 3 */ + else if (sc->ntxchains == 2) + rf |= 1 << 7; /* 2T: disable Tx chain 3 */ + if (sc->nrxchains == 1) + rf |= 1 << 6 | 1 << 4; /* 1R: disable Rx chains 2 & 3 */ + else if (sc->nrxchains == 2) + rf |= 1 << 6; /* 2R: disable Rx chain 3 */ + run_rt3070_rf_write(sc, 1, rf); + + /* set RF offset */ + run_rt3070_rf_read(sc, 23, &rf); + rf = (rf & ~0x7f) | sc->freq; + run_rt3070_rf_write(sc, 23, rf); + + /* program RF filter */ + run_rt3070_rf_read(sc, 24, &rf); /* Tx */ + rf = (rf & ~0x3f) | sc->rf24_20mhz; + run_rt3070_rf_write(sc, 24, rf); + run_rt3070_rf_read(sc, 31, &rf); /* Rx */ + rf = (rf & ~0x3f) | sc->rf24_20mhz; + run_rt3070_rf_write(sc, 31, rf); + + /* enable RF tuning */ + run_rt3070_rf_read(sc, 7, &rf); + run_rt3070_rf_write(sc, 7, rf | 0x01); +} + +static void +run_rt3572_set_chan(struct run_softc *sc, u_int chan) +{ + int8_t txpow1, txpow2; + uint32_t tmp; + uint8_t rf; + int i; + + /* find the settings for this channel (we know it exists) */ + for (i = 0; rt2860_rf2850[i].chan != chan; i++); + + /* use Tx power values from EEPROM */ + txpow1 = sc->txpow1[i]; + txpow2 = sc->txpow2[i]; + + if (chan <= 14) { + run_bbp_write(sc, 25, sc->bbp25); + run_bbp_write(sc, 26, sc->bbp26); + } else { + /* enable IQ phase correction */ + run_bbp_write(sc, 25, 0x09); + run_bbp_write(sc, 26, 0xff); + } + + run_rt3070_rf_write(sc, 2, rt3070_freqs[i].n); + run_rt3070_rf_write(sc, 3, rt3070_freqs[i].k); + run_rt3070_rf_read(sc, 6, &rf); + rf = (rf & ~0x0f) | rt3070_freqs[i].r; + rf |= (chan <= 14) ? 0x08 : 0x04; + run_rt3070_rf_write(sc, 6, rf); + + /* set PLL mode */ + run_rt3070_rf_read(sc, 5, &rf); + rf &= ~(0x08 | 0x04); + rf |= (chan <= 14) ? 0x04 : 0x08; + run_rt3070_rf_write(sc, 5, rf); + + /* set Tx power for chain 0 */ + if (chan <= 14) + rf = 0x60 | txpow1; + else + rf = 0xe0 | (txpow1 & 0xc) << 1 | (txpow1 & 0x3); + run_rt3070_rf_write(sc, 12, rf); + + /* set Tx power for chain 1 */ + if (chan <= 14) + rf = 0x60 | txpow2; + else + rf = 0xe0 | (txpow2 & 0xc) << 1 | (txpow2 & 0x3); + run_rt3070_rf_write(sc, 13, rf); + + /* set Tx/Rx streams */ + run_rt3070_rf_read(sc, 1, &rf); + rf &= ~0xfc; + if (sc->ntxchains == 1) + rf |= 1 << 7 | 1 << 5; /* 1T: disable Tx chains 2 & 3 */ + else if (sc->ntxchains == 2) + rf |= 1 << 7; /* 2T: disable Tx chain 3 */ + if (sc->nrxchains == 1) + rf |= 1 << 6 | 1 << 4; /* 1R: disable Rx chains 2 & 3 */ + else if (sc->nrxchains == 2) + rf |= 1 << 6; /* 2R: disable Rx chain 3 */ + run_rt3070_rf_write(sc, 1, rf); + + /* set RF offset */ + run_rt3070_rf_read(sc, 23, &rf); + rf = (rf & ~0x7f) | sc->freq; + run_rt3070_rf_write(sc, 23, rf); + + /* program RF filter */ + rf = sc->rf24_20mhz; + run_rt3070_rf_write(sc, 24, rf); /* Tx */ + run_rt3070_rf_write(sc, 31, rf); /* Rx */ + + /* enable RF tuning */ + run_rt3070_rf_read(sc, 7, &rf); + rf = (chan <= 14) ? 0xd8 : ((rf & ~0xc8) | 0x14); + run_rt3070_rf_write(sc, 7, rf); + + /* TSSI */ + rf = (chan <= 14) ? 0xc3 : 0xc0; + run_rt3070_rf_write(sc, 9, rf); + + /* set loop filter 1 */ + run_rt3070_rf_write(sc, 10, 0xf1); + /* set loop filter 2 */ + run_rt3070_rf_write(sc, 11, (chan <= 14) ? 0xb9 : 0x00); + + /* set tx_mx2_ic */ + run_rt3070_rf_write(sc, 15, (chan <= 14) ? 0x53 : 0x43); + /* set tx_mx1_ic */ + if (chan <= 14) + rf = 0x48 | sc->txmixgain_2ghz; + else + rf = 0x78 | sc->txmixgain_5ghz; + run_rt3070_rf_write(sc, 16, rf); + + /* set tx_lo1 */ + run_rt3070_rf_write(sc, 17, 0x23); + /* set tx_lo2 */ + if (chan <= 14) + rf = 0x93; + else if (chan <= 64) + rf = 0xb7; + else if (chan <= 128) + rf = 0x74; + else + rf = 0x72; + run_rt3070_rf_write(sc, 19, rf); + + /* set rx_lo1 */ + if (chan <= 14) + rf = 0xb3; + else if (chan <= 64) + rf = 0xf6; + else if (chan <= 128) + rf = 0xf4; + else + rf = 0xf3; + run_rt3070_rf_write(sc, 20, rf); + + /* set pfd_delay */ + if (chan <= 14) + rf = 0x15; + else if (chan <= 64) + rf = 0x3d; + else + rf = 0x01; + run_rt3070_rf_write(sc, 25, rf); + + /* set rx_lo2 */ + run_rt3070_rf_write(sc, 26, (chan <= 14) ? 0x85 : 0x87); + /* set ldo_rf_vc */ + run_rt3070_rf_write(sc, 27, (chan <= 14) ? 0x00 : 0x01); + /* set drv_cc */ + run_rt3070_rf_write(sc, 29, (chan <= 14) ? 0x9b : 0x9f); + + run_read(sc, RT2860_GPIO_CTRL, &tmp); + tmp &= ~0x8080; + if (chan <= 14) + tmp |= 0x80; + run_write(sc, RT2860_GPIO_CTRL, tmp); + + /* enable RF tuning */ + run_rt3070_rf_read(sc, 7, &rf); + run_rt3070_rf_write(sc, 7, rf | 0x01); + + run_delay(sc, 2); +} + +static void +run_set_rx_antenna(struct run_softc *sc, int aux) +{ + uint32_t tmp; + + if (aux) { + run_mcu_cmd(sc, RT2860_MCU_CMD_ANTSEL, 0); + run_read(sc, RT2860_GPIO_CTRL, &tmp); + run_write(sc, RT2860_GPIO_CTRL, (tmp & ~0x0808) | 0x08); + } else { + run_mcu_cmd(sc, RT2860_MCU_CMD_ANTSEL, 1); + run_read(sc, RT2860_GPIO_CTRL, &tmp); + run_write(sc, RT2860_GPIO_CTRL, tmp & ~0x0808); + } +} + +static int +run_set_chan(struct run_softc *sc, struct ieee80211_channel *c) +{ + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + uint32_t chan, group; + + chan = ieee80211_chan2ieee(ic, c); + if (chan == 0 || chan == IEEE80211_CHAN_ANY) + return (EINVAL); + + if (sc->mac_ver == 0x3572) + run_rt3572_set_chan(sc, chan); + else if (sc->mac_ver >= 0x3070) + run_rt3070_set_chan(sc, chan); + else + run_rt2870_set_chan(sc, chan); + + /* determine channel group */ + if (chan <= 14) + group = 0; + else if (chan <= 64) + group = 1; + else if (chan <= 128) + group = 2; + else + group = 3; + + /* XXX necessary only when group has changed! */ + run_select_chan_group(sc, group); + + run_delay(sc, 10); + + return (0); +} + +static void +run_set_channel(struct ieee80211com *ic) +{ + struct run_softc *sc = ic->ic_ifp->if_softc; + + RUN_LOCK(sc); + run_set_chan(sc, ic->ic_curchan); + RUN_UNLOCK(sc); + + return; +} + +static void +run_scan_start(struct ieee80211com *ic) +{ + struct run_softc *sc = ic->ic_ifp->if_softc; + uint32_t tmp; + + RUN_LOCK(sc); + + /* abort TSF synchronization */ + run_read(sc, RT2860_BCN_TIME_CFG, &tmp); + run_write(sc, RT2860_BCN_TIME_CFG, + tmp & ~(RT2860_BCN_TX_EN | RT2860_TSF_TIMER_EN | + RT2860_TBTT_TIMER_EN)); + run_set_bssid(sc, sc->sc_ifp->if_broadcastaddr); + + RUN_UNLOCK(sc); + + return; +} + +static void +run_scan_end(struct ieee80211com *ic) +{ + struct run_softc *sc = ic->ic_ifp->if_softc; + + RUN_LOCK(sc); + + run_enable_tsf_sync(sc); + /* XXX keep local copy */ + run_set_bssid(sc, sc->sc_bssid); + + RUN_UNLOCK(sc); + + return; +} + +/* + * Could be called from ieee80211_node_timeout() + * (non-sleepable thread) + */ +static void +run_update_beacon(struct ieee80211vap *vap, int item) +{ + struct ieee80211com *ic = vap->iv_ic; + struct run_softc *sc = ic->ic_ifp->if_softc; + struct run_vap *rvp = RUN_VAP(vap); + int mcast = 0; + uint32_t i; + + KASSERT(vap != NULL, ("no beacon")); + + switch (item) { + case IEEE80211_BEACON_ERP: + run_updateslot(ic->ic_ifp); + break; + case IEEE80211_BEACON_HTINFO: + run_updateprot(ic); + break; + case IEEE80211_BEACON_TIM: + mcast = 1; /*TODO*/ + break; + default: + break; + } + + setbit(rvp->bo.bo_flags, item); + ieee80211_beacon_update(vap->iv_bss, &rvp->bo, rvp->beacon_mbuf, mcast); + + i = RUN_CMDQ_GET(&sc->cmdq_store); + DPRINTF("cmdq_store=%d\n", i); + sc->cmdq[i].func = run_update_beacon_cb; + sc->cmdq[i].arg0 = vap; + ieee80211_runtask(ic, &sc->cmdq_task); + + return; +} + +static void +run_update_beacon_cb(void *arg) +{ + struct ieee80211vap *vap = arg; + struct run_vap *rvp = RUN_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + struct run_softc *sc = ic->ic_ifp->if_softc; + struct rt2860_txwi txwi; + struct mbuf *m; + uint8_t ridx; + + if (vap->iv_bss->ni_chan == IEEE80211_CHAN_ANYC) + return; + + /* + * No need to call ieee80211_beacon_update(), run_update_beacon() + * is taking care of apropriate calls. + */ + if (rvp->beacon_mbuf == NULL) { + rvp->beacon_mbuf = ieee80211_beacon_alloc(vap->iv_bss, + &rvp->bo); + if (rvp->beacon_mbuf == NULL) + return; + } + m = rvp->beacon_mbuf; + + memset(&txwi, 0, sizeof txwi); + txwi.wcid = 0xff; + txwi.len = htole16(m->m_pkthdr.len); + /* send beacons at the lowest available rate */ + ridx = (ic->ic_curmode == IEEE80211_MODE_11A) ? + RT2860_RIDX_OFDM6 : RT2860_RIDX_CCK1; + txwi.phy = htole16(rt2860_rates[ridx].mcs); + if (rt2860_rates[ridx].phy == IEEE80211_T_OFDM) + txwi.phy |= htole16(RT2860_PHY_OFDM); + txwi.txop = RT2860_TX_TXOP_HT; + txwi.flags = RT2860_TX_TS; + txwi.xflags = RT2860_TX_NSEQ; + + run_write_region_1(sc, RT2860_BCN_BASE(rvp->rvp_id), + (uint8_t *)&txwi, sizeof txwi); + run_write_region_1(sc, RT2860_BCN_BASE(rvp->rvp_id) + sizeof txwi, + mtod(m, uint8_t *), (m->m_pkthdr.len + 1) & ~1); /* roundup len */ + + return; +} + +static void +run_updateprot(struct ieee80211com *ic) +{ + struct run_softc *sc = ic->ic_ifp->if_softc; + uint32_t i; + + i = RUN_CMDQ_GET(&sc->cmdq_store); + DPRINTF("cmdq_store=%d\n", i); + sc->cmdq[i].func = run_updateprot_cb; + sc->cmdq[i].arg0 = ic; + ieee80211_runtask(ic, &sc->cmdq_task); +} + +static void +run_updateprot_cb(void *arg) +{ + struct ieee80211com *ic = arg; + struct run_softc *sc = ic->ic_ifp->if_softc; + uint32_t tmp; + + tmp = RT2860_RTSTH_EN | RT2860_PROT_NAV_SHORT | RT2860_TXOP_ALLOW_ALL; + /* setup protection frame rate (MCS code) */ + tmp |= (ic->ic_curmode == IEEE80211_MODE_11A) ? + rt2860_rates[RT2860_RIDX_OFDM6].mcs : + rt2860_rates[RT2860_RIDX_CCK11].mcs; + + /* CCK frames don't require protection */ + run_write(sc, RT2860_CCK_PROT_CFG, tmp); + if (ic->ic_flags & IEEE80211_F_USEPROT) { + if (ic->ic_protmode == IEEE80211_PROT_RTSCTS) + tmp |= RT2860_PROT_CTRL_RTS_CTS; + else if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) + tmp |= RT2860_PROT_CTRL_CTS; + } + run_write(sc, RT2860_OFDM_PROT_CFG, tmp); +} + +static void +run_usb_timeout_cb(void *arg) +{ + struct ieee80211vap *vap = arg; + struct run_softc *sc = vap->iv_ic->ic_ifp->if_softc; + + RUN_LOCK_ASSERT(sc, MA_OWNED); + + if(vap->iv_state == IEEE80211_S_RUN && + vap->iv_opmode != IEEE80211_M_STA) + run_reset_livelock(sc); + else if (vap->iv_state == IEEE80211_S_SCAN) { + DPRINTF("timeout caused by scan\n"); + /* cancel bgscan */ + ieee80211_cancel_scan(vap); + } else + DPRINTF("timeout by unknown cause\n"); +} + +static void +run_reset_livelock(struct run_softc *sc) +{ + uint32_t tmp; + + RUN_LOCK_ASSERT(sc, MA_OWNED); + + /* + * In IBSS or HostAP modes (when the hardware sends beacons), the MAC + * can run into a livelock and start sending CTS-to-self frames like + * crazy if protection is enabled. Reset MAC/BBP for a while + */ + run_read(sc, RT2860_DEBUG, &tmp); + DPRINTFN(3, "debug reg %08x\n", tmp); + if ((tmp & (1 << 29)) && (tmp & (1 << 7 | 1 << 5))) { + DPRINTF("CTS-to-self livelock detected\n"); + run_write(sc, RT2860_MAC_SYS_CTRL, RT2860_MAC_SRST); + run_delay(sc, 1); + run_write(sc, RT2860_MAC_SYS_CTRL, + RT2860_MAC_RX_EN | RT2860_MAC_TX_EN); + } +} + +static void +run_update_promisc_locked(struct ifnet *ifp) +{ + struct run_softc *sc = ifp->if_softc; + uint32_t tmp; + + run_read(sc, RT2860_RX_FILTR_CFG, &tmp); + + tmp |= RT2860_DROP_UC_NOME; + if (ifp->if_flags & IFF_PROMISC) + tmp &= ~RT2860_DROP_UC_NOME; + + run_write(sc, RT2860_RX_FILTR_CFG, tmp); + + DPRINTF("%s promiscuous mode\n", (ifp->if_flags & IFF_PROMISC) ? + "entering" : "leaving"); +} + +static void +run_update_promisc(struct ifnet *ifp) +{ + struct run_softc *sc = ifp->if_softc; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + RUN_LOCK(sc); + run_update_promisc_locked(ifp); + RUN_UNLOCK(sc); +} + +static void +run_enable_tsf_sync(struct run_softc *sc) +{ + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + uint32_t tmp; + + DPRINTF("rvp_id=%d ic_opmode=%d\n", RUN_VAP(vap)->rvp_id, ic->ic_opmode); + + run_read(sc, RT2860_BCN_TIME_CFG, &tmp); + tmp &= ~0x1fffff; + tmp |= vap->iv_bss->ni_intval * 16; + tmp |= RT2860_TSF_TIMER_EN | RT2860_TBTT_TIMER_EN; + + if (ic->ic_opmode == IEEE80211_M_STA) { + /* + * Local TSF is always updated with remote TSF on beacon + * reception. + */ + tmp |= 1 << RT2860_TSF_SYNC_MODE_SHIFT; + } else if (ic->ic_opmode == IEEE80211_M_IBSS) { + tmp |= RT2860_BCN_TX_EN; + /* + * Local TSF is updated with remote TSF on beacon reception + * only if the remote TSF is greater than local TSF. + */ + tmp |= 2 << RT2860_TSF_SYNC_MODE_SHIFT; + } else if (ic->ic_opmode == IEEE80211_M_HOSTAP || + ic->ic_opmode == IEEE80211_M_MBSS) { + tmp |= RT2860_BCN_TX_EN; + /* SYNC with nobody */ + tmp |= 3 << RT2860_TSF_SYNC_MODE_SHIFT; + } else { + DPRINTF("Enabling TSF failed. undefined opmode\n"); + return; + } + + run_write(sc, RT2860_BCN_TIME_CFG, tmp); +} + +static void +run_enable_mrr(struct run_softc *sc) +{ +#define CCK(mcs) (mcs) +#define OFDM(mcs) (1 << 3 | (mcs)) + run_write(sc, RT2860_LG_FBK_CFG0, + OFDM(6) << 28 | /* 54->48 */ + OFDM(5) << 24 | /* 48->36 */ + OFDM(4) << 20 | /* 36->24 */ + OFDM(3) << 16 | /* 24->18 */ + OFDM(2) << 12 | /* 18->12 */ + OFDM(1) << 8 | /* 12-> 9 */ + OFDM(0) << 4 | /* 9-> 6 */ + OFDM(0)); /* 6-> 6 */ + + run_write(sc, RT2860_LG_FBK_CFG1, + CCK(2) << 12 | /* 11->5.5 */ + CCK(1) << 8 | /* 5.5-> 2 */ + CCK(0) << 4 | /* 2-> 1 */ + CCK(0)); /* 1-> 1 */ +#undef OFDM +#undef CCK +} + +static void +run_set_txpreamble(struct run_softc *sc) +{ + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + uint32_t tmp; + + run_read(sc, RT2860_AUTO_RSP_CFG, &tmp); + if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) + tmp |= RT2860_CCK_SHORT_EN; + else + tmp &= ~RT2860_CCK_SHORT_EN; + run_write(sc, RT2860_AUTO_RSP_CFG, tmp); +} + +static void +run_set_basicrates(struct run_softc *sc) +{ + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + + /* set basic rates mask */ + if (ic->ic_curmode == IEEE80211_MODE_11B) + run_write(sc, RT2860_LEGACY_BASIC_RATE, 0x003); + else if (ic->ic_curmode == IEEE80211_MODE_11A) + run_write(sc, RT2860_LEGACY_BASIC_RATE, 0x150); + else /* 11g */ + run_write(sc, RT2860_LEGACY_BASIC_RATE, 0x15f); +} + +static void +run_set_leds(struct run_softc *sc, uint16_t which) +{ + (void)run_mcu_cmd(sc, RT2860_MCU_CMD_LEDS, + which | (sc->leds & 0x7f)); +} + +static void +run_set_bssid(struct run_softc *sc, const uint8_t *bssid) +{ + run_write(sc, RT2860_MAC_BSSID_DW0, + bssid[0] | bssid[1] << 8 | bssid[2] << 16 | bssid[3] << 24); + run_write(sc, RT2860_MAC_BSSID_DW1, + bssid[4] | bssid[5] << 8); +} + +static void +run_set_macaddr(struct run_softc *sc, const uint8_t *addr) +{ + run_write(sc, RT2860_MAC_ADDR_DW0, + addr[0] | addr[1] << 8 | addr[2] << 16 | addr[3] << 24); + run_write(sc, RT2860_MAC_ADDR_DW1, + addr[4] | addr[5] << 8 | 0xff << 16); +} + +static void +run_updateslot(struct ifnet *ifp) +{ + struct run_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t i; + + i = RUN_CMDQ_GET(&sc->cmdq_store); + DPRINTF("cmdq_store=%d\n", i); + sc->cmdq[i].func = run_updateslot_cb; + sc->cmdq[i].arg0 = ifp; + ieee80211_runtask(ic, &sc->cmdq_task); + + return; +} + +/* ARGSUSED */ +static void +run_updateslot_cb(void *arg) +{ + struct ifnet *ifp = arg; + struct run_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t tmp; + + run_read(sc, RT2860_BKOFF_SLOT_CFG, &tmp); + tmp &= ~0xff; + tmp |= (ic->ic_flags & IEEE80211_F_SHSLOT) ? 9 : 20; + run_write(sc, RT2860_BKOFF_SLOT_CFG, tmp); +} + +static void +run_update_mcast(struct ifnet *ifp) +{ + /* h/w filter supports getting everything or nothing */ + ifp->if_flags |= IFF_ALLMULTI; +} + +static int8_t +run_rssi2dbm(struct run_softc *sc, uint8_t rssi, uint8_t rxchain) +{ + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + struct ieee80211_channel *c = ic->ic_curchan; + int delta; + + if (IEEE80211_IS_CHAN_5GHZ(c)) { + uint32_t chan = ieee80211_chan2ieee(ic, c); + delta = sc->rssi_5ghz[rxchain]; + + /* determine channel group */ + if (chan <= 64) + delta -= sc->lna[1]; + else if (chan <= 128) + delta -= sc->lna[2]; + else + delta -= sc->lna[3]; + } else + delta = sc->rssi_2ghz[rxchain] - sc->lna[0]; + + return (-12 - delta - rssi); +} + +static int +run_bbp_init(struct run_softc *sc) +{ + int i, error, ntries; + uint8_t bbp0; + + /* wait for BBP to wake up */ + for (ntries = 0; ntries < 20; ntries++) { + if ((error = run_bbp_read(sc, 0, &bbp0)) != 0) + return error; + if (bbp0 != 0 && bbp0 != 0xff) + break; + } + if (ntries == 20) + return (ETIMEDOUT); + + /* initialize BBP registers to default values */ + for (i = 0; i < nitems(rt2860_def_bbp); i++) { + run_bbp_write(sc, rt2860_def_bbp[i].reg, + rt2860_def_bbp[i].val); + } + + /* fix BBP84 for RT2860E */ + if (sc->mac_ver == 0x2860 && sc->mac_rev != 0x0101) + run_bbp_write(sc, 84, 0x19); + + if (sc->mac_ver >= 0x3070) { + run_bbp_write(sc, 79, 0x13); + run_bbp_write(sc, 80, 0x05); + run_bbp_write(sc, 81, 0x33); + } else if (sc->mac_ver == 0x2860 && sc->mac_rev == 0x0100) { + run_bbp_write(sc, 69, 0x16); + run_bbp_write(sc, 73, 0x12); + } + return (0); +} + +static int +run_rt3070_rf_init(struct run_softc *sc) +{ + uint32_t tmp; + uint8_t rf, target, bbp4; + int i; + + run_rt3070_rf_read(sc, 30, &rf); + /* toggle RF R30 bit 7 */ + run_rt3070_rf_write(sc, 30, rf | 0x80); + run_delay(sc, 10); + run_rt3070_rf_write(sc, 30, rf & ~0x80); + + /* initialize RF registers to default value */ + if (sc->mac_ver == 0x3572) { + for (i = 0; i < nitems(rt3572_def_rf); i++) { + run_rt3070_rf_write(sc, rt3572_def_rf[i].reg, + rt3572_def_rf[i].val); + } + } else { + for (i = 0; i < nitems(rt3070_def_rf); i++) { + run_rt3070_rf_write(sc, rt3070_def_rf[i].reg, + rt3070_def_rf[i].val); + } + } + + if (sc->mac_ver == 0x3070) { + /* change voltage from 1.2V to 1.35V for RT3070 */ + run_read(sc, RT3070_LDO_CFG0, &tmp); + tmp = (tmp & ~0x0f000000) | 0x0d000000; + run_write(sc, RT3070_LDO_CFG0, tmp); + + } else if (sc->mac_ver == 0x3071) { + run_rt3070_rf_read(sc, 6, &rf); + run_rt3070_rf_write(sc, 6, rf | 0x40); + run_rt3070_rf_write(sc, 31, 0x14); + + run_read(sc, RT3070_LDO_CFG0, &tmp); + tmp &= ~0x1f000000; + if (sc->mac_rev < 0x0211) + tmp |= 0x0d000000; /* 1.3V */ + else + tmp |= 0x01000000; /* 1.2V */ + run_write(sc, RT3070_LDO_CFG0, tmp); + + /* patch LNA_PE_G1 */ + run_read(sc, RT3070_GPIO_SWITCH, &tmp); + run_write(sc, RT3070_GPIO_SWITCH, tmp & ~0x20); + + } else if (sc->mac_ver == 0x3572) { + run_rt3070_rf_read(sc, 6, &rf); + run_rt3070_rf_write(sc, 6, rf | 0x40); + + /* increase voltage from 1.2V to 1.35V */ + run_read(sc, RT3070_LDO_CFG0, &tmp); + tmp = (tmp & ~0x1f000000) | 0x0d000000; + run_write(sc, RT3070_LDO_CFG0, tmp); + + if (sc->mac_rev < 0x0211 || !sc->patch_dac) { + run_delay(sc, 1); /* wait for 1msec */ + /* decrease voltage back to 1.2V */ + tmp = (tmp & ~0x1f000000) | 0x01000000; + run_write(sc, RT3070_LDO_CFG0, tmp); + } + } + + /* select 20MHz bandwidth */ + run_rt3070_rf_read(sc, 31, &rf); + run_rt3070_rf_write(sc, 31, rf & ~0x20); + + /* calibrate filter for 20MHz bandwidth */ + sc->rf24_20mhz = 0x1f; /* default value */ + target = (sc->mac_ver < 0x3071) ? 0x16 : 0x13; + run_rt3070_filter_calib(sc, 0x07, target, &sc->rf24_20mhz); + + /* select 40MHz bandwidth */ + run_bbp_read(sc, 4, &bbp4); + run_bbp_write(sc, 4, (bbp4 & ~0x08) | 0x10); + run_rt3070_rf_read(sc, 31, &rf); + run_rt3070_rf_write(sc, 31, rf | 0x20); + + /* calibrate filter for 40MHz bandwidth */ + sc->rf24_40mhz = 0x2f; /* default value */ + target = (sc->mac_ver < 0x3071) ? 0x19 : 0x15; + run_rt3070_filter_calib(sc, 0x27, target, &sc->rf24_40mhz); + + /* go back to 20MHz bandwidth */ + run_bbp_read(sc, 4, &bbp4); + run_bbp_write(sc, 4, bbp4 & ~0x18); + + if (sc->mac_ver == 0x3572) { + /* save default BBP registers 25 and 26 values */ + run_bbp_read(sc, 25, &sc->bbp25); + run_bbp_read(sc, 26, &sc->bbp26); + } else if (sc->mac_rev < 0x0211) + run_rt3070_rf_write(sc, 27, 0x03); + + run_read(sc, RT3070_OPT_14, &tmp); + run_write(sc, RT3070_OPT_14, tmp | 1); + + if (sc->mac_ver == 0x3070 || sc->mac_ver == 0x3071) { + run_rt3070_rf_read(sc, 17, &rf); + rf &= ~RT3070_TX_LO1; + if ((sc->mac_ver == 0x3070 || + (sc->mac_ver == 0x3071 && sc->mac_rev >= 0x0211)) && + !sc->ext_2ghz_lna) + rf |= 0x20; /* fix for long range Rx issue */ + if (sc->txmixgain_2ghz >= 1) + rf = (rf & ~0x7) | sc->txmixgain_2ghz; + run_rt3070_rf_write(sc, 17, rf); + } + + if (sc->mac_rev == 0x3071) { + run_rt3070_rf_read(sc, 1, &rf); + rf &= ~(RT3070_RX0_PD | RT3070_TX0_PD); + rf |= RT3070_RF_BLOCK | RT3070_RX1_PD | RT3070_TX1_PD; + run_rt3070_rf_write(sc, 1, rf); + + run_rt3070_rf_read(sc, 15, &rf); + run_rt3070_rf_write(sc, 15, rf & ~RT3070_TX_LO2); + + run_rt3070_rf_read(sc, 20, &rf); + run_rt3070_rf_write(sc, 20, rf & ~RT3070_RX_LO1); + + run_rt3070_rf_read(sc, 21, &rf); + run_rt3070_rf_write(sc, 21, rf & ~RT3070_RX_LO2); + } + + if (sc->mac_ver == 0x3070 || sc->mac_ver == 0x3071) { + /* fix Tx to Rx IQ glitch by raising RF voltage */ + run_rt3070_rf_read(sc, 27, &rf); + rf &= ~0x77; + if (sc->mac_rev < 0x0211) + rf |= 0x03; + run_rt3070_rf_write(sc, 27, rf); + } + return (0); +} + +static int +run_rt3070_filter_calib(struct run_softc *sc, uint8_t init, uint8_t target, + uint8_t *val) +{ + uint8_t rf22, rf24; + uint8_t bbp55_pb, bbp55_sb, delta; + int ntries; + + /* program filter */ + run_rt3070_rf_read(sc, 24, &rf24); + rf24 = (rf24 & 0xc0) | init; /* initial filter value */ + run_rt3070_rf_write(sc, 24, rf24); + + /* enable baseband loopback mode */ + run_rt3070_rf_read(sc, 22, &rf22); + run_rt3070_rf_write(sc, 22, rf22 | 0x01); + + /* set power and frequency of passband test tone */ + run_bbp_write(sc, 24, 0x00); + for (ntries = 0; ntries < 100; ntries++) { + /* transmit test tone */ + run_bbp_write(sc, 25, 0x90); + run_delay(sc, 10); + /* read received power */ + run_bbp_read(sc, 55, &bbp55_pb); + if (bbp55_pb != 0) + break; + } + if (ntries == 100) + return ETIMEDOUT; + + /* set power and frequency of stopband test tone */ + run_bbp_write(sc, 24, 0x06); + for (ntries = 0; ntries < 100; ntries++) { + /* transmit test tone */ + run_bbp_write(sc, 25, 0x90); + run_delay(sc, 10); + /* read received power */ + run_bbp_read(sc, 55, &bbp55_sb); + + delta = bbp55_pb - bbp55_sb; + if (delta > target) + break; + + /* reprogram filter */ + rf24++; + run_rt3070_rf_write(sc, 24, rf24); + } + if (ntries < 100) { + if (rf24 != init) + rf24--; /* backtrack */ + *val = rf24; + run_rt3070_rf_write(sc, 24, rf24); + } + + /* restore initial state */ + run_bbp_write(sc, 24, 0x00); + + /* disable baseband loopback mode */ + run_rt3070_rf_read(sc, 22, &rf22); + run_rt3070_rf_write(sc, 22, rf22 & ~0x01); + + return (0); +} + +static void +run_rt3070_rf_setup(struct run_softc *sc) +{ + uint8_t bbp, rf; + int i; + + if (sc->mac_ver == 0x3572) { + /* enable DC filter */ + if (sc->mac_rev >= 0x0201) + run_bbp_write(sc, 103, 0xc0); + + run_bbp_read(sc, 138, &bbp); + if (sc->ntxchains == 1) + bbp |= 0x20; /* turn off DAC1 */ + if (sc->nrxchains == 1) + bbp &= ~0x02; /* turn off ADC1 */ + run_bbp_write(sc, 138, bbp); + + if (sc->mac_rev >= 0x0211) { + /* improve power consumption */ + run_bbp_read(sc, 31, &bbp); + run_bbp_write(sc, 31, bbp & ~0x03); + } + + run_rt3070_rf_read(sc, 16, &rf); + rf = (rf & ~0x07) | sc->txmixgain_2ghz; + run_rt3070_rf_write(sc, 16, rf); + + } else if (sc->mac_ver == 0x3071) { + /* enable DC filter */ + if (sc->mac_rev >= 0x0201) + run_bbp_write(sc, 103, 0xc0); + + run_bbp_read(sc, 138, &bbp); + if (sc->ntxchains == 1) + bbp |= 0x20; /* turn off DAC1 */ + if (sc->nrxchains == 1) + bbp &= ~0x02; /* turn off ADC1 */ + run_bbp_write(sc, 138, bbp); + + if (sc->mac_rev >= 0x0211) { + /* improve power consumption */ + run_bbp_read(sc, 31, &bbp); + run_bbp_write(sc, 31, bbp & ~0x03); + } + + run_write(sc, RT2860_TX_SW_CFG1, 0); + if (sc->mac_rev < 0x0211) { + run_write(sc, RT2860_TX_SW_CFG2, + sc->patch_dac ? 0x2c : 0x0f); + } else + run_write(sc, RT2860_TX_SW_CFG2, 0); + + } else if (sc->mac_ver == 0x3070) { + if (sc->mac_rev >= 0x0201) { + /* enable DC filter */ + run_bbp_write(sc, 103, 0xc0); + + /* improve power consumption */ + run_bbp_read(sc, 31, &bbp); + run_bbp_write(sc, 31, bbp & ~0x03); + } + + if (sc->mac_rev < 0x0211) { + run_write(sc, RT2860_TX_SW_CFG1, 0); + run_write(sc, RT2860_TX_SW_CFG2, 0x2c); + } else + run_write(sc, RT2860_TX_SW_CFG2, 0); + } + + /* initialize RF registers from ROM for >=RT3071*/ + if (sc->mac_ver >= 0x3071) { + for (i = 0; i < 10; i++) { + if (sc->rf[i].reg == 0 || sc->rf[i].reg == 0xff) + continue; + run_rt3070_rf_write(sc, sc->rf[i].reg, sc->rf[i].val); + } + } +} + +static int +run_txrx_enable(struct run_softc *sc) +{ + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + uint32_t tmp; + int error, ntries; + + run_write(sc, RT2860_MAC_SYS_CTRL, RT2860_MAC_TX_EN); + for (ntries = 0; ntries < 200; ntries++) { + if ((error = run_read(sc, RT2860_WPDMA_GLO_CFG, &tmp)) != 0) + return error; + if ((tmp & (RT2860_TX_DMA_BUSY | RT2860_RX_DMA_BUSY)) == 0) + break; + run_delay(sc, 50); + } + if (ntries == 200) + return ETIMEDOUT; + + run_delay(sc, 50); + + tmp |= RT2860_RX_DMA_EN | RT2860_TX_DMA_EN | RT2860_TX_WB_DDONE; + run_write(sc, RT2860_WPDMA_GLO_CFG, tmp); + + /* enable Rx bulk aggregation (set timeout and limit) */ + tmp = RT2860_USB_TX_EN | RT2860_USB_RX_EN | RT2860_USB_RX_AGG_EN | + RT2860_USB_RX_AGG_TO(128) | RT2860_USB_RX_AGG_LMT(2); + run_write(sc, RT2860_USB_DMA_CFG, tmp); + + /* set Rx filter */ + tmp = RT2860_DROP_CRC_ERR | RT2860_DROP_PHY_ERR; + if (ic->ic_opmode != IEEE80211_M_MONITOR) { + tmp |= RT2860_DROP_UC_NOME | RT2860_DROP_DUPL | + RT2860_DROP_CTS | RT2860_DROP_BA | RT2860_DROP_ACK | + RT2860_DROP_VER_ERR | RT2860_DROP_CTRL_RSV | + RT2860_DROP_CFACK | RT2860_DROP_CFEND; + if (ic->ic_opmode == IEEE80211_M_STA) + tmp |= RT2860_DROP_RTS | RT2860_DROP_PSPOLL; + } + run_write(sc, RT2860_RX_FILTR_CFG, tmp); + + run_write(sc, RT2860_MAC_SYS_CTRL, + RT2860_MAC_RX_EN | RT2860_MAC_TX_EN); + + return (0); +} + +static void +run_init_locked(struct run_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t tmp; + uint8_t bbp1, bbp3; + int i; + int ridx; + int ntries; + + if (ic->ic_nrunning > 1) + return; + + run_stop(sc); + + for (ntries = 0; ntries < 100; ntries++) { + if (run_read(sc, RT2860_ASIC_VER_ID, &tmp) != 0) + goto fail; + if (tmp != 0 && tmp != 0xffffffff) + break; + run_delay(sc, 10); + } + if (ntries == 100) + goto fail; + + for (i = 0; i != RUN_EP_QUEUES; i++) + run_setup_tx_list(sc, &sc->sc_epq[i]); + + run_set_macaddr(sc, IF_LLADDR(ifp)); + + for (ntries = 0; ntries < 100; ntries++) { + if (run_read(sc, RT2860_WPDMA_GLO_CFG, &tmp) != 0) + goto fail; + if ((tmp & (RT2860_TX_DMA_BUSY | RT2860_RX_DMA_BUSY)) == 0) + break; + run_delay(sc, 10); + } + if (ntries == 100) { + device_printf(sc->sc_dev, "timeout waiting for DMA engine\n"); + goto fail; + } + tmp &= 0xff0; + tmp |= RT2860_TX_WB_DDONE; + run_write(sc, RT2860_WPDMA_GLO_CFG, tmp); + + /* turn off PME_OEN to solve high-current issue */ + run_read(sc, RT2860_SYS_CTRL, &tmp); + run_write(sc, RT2860_SYS_CTRL, tmp & ~RT2860_PME_OEN); + + run_write(sc, RT2860_MAC_SYS_CTRL, + RT2860_BBP_HRST | RT2860_MAC_SRST); + run_write(sc, RT2860_USB_DMA_CFG, 0); + + if (run_reset(sc) != 0) { + device_printf(sc->sc_dev, "could not reset chipset\n"); + goto fail; + } + + run_write(sc, RT2860_MAC_SYS_CTRL, 0); + + /* init Tx power for all Tx rates (from EEPROM) */ + for (ridx = 0; ridx < 5; ridx++) { + if (sc->txpow20mhz[ridx] == 0xffffffff) + continue; + run_write(sc, RT2860_TX_PWR_CFG(ridx), sc->txpow20mhz[ridx]); + } + + for (i = 0; i < nitems(rt2870_def_mac); i++) + run_write(sc, rt2870_def_mac[i].reg, rt2870_def_mac[i].val); + run_write(sc, RT2860_WMM_AIFSN_CFG, 0x00002273); + run_write(sc, RT2860_WMM_CWMIN_CFG, 0x00002344); + run_write(sc, RT2860_WMM_CWMAX_CFG, 0x000034aa); + + if (sc->mac_ver >= 0x3070) { + /* set delay of PA_PE assertion to 1us (unit of 0.25us) */ + run_write(sc, RT2860_TX_SW_CFG0, + 4 << RT2860_DLY_PAPE_EN_SHIFT); + } + + /* wait while MAC is busy */ + for (ntries = 0; ntries < 100; ntries++) { + if (run_read(sc, RT2860_MAC_STATUS_REG, &tmp) != 0) + goto fail; + if (!(tmp & (RT2860_RX_STATUS_BUSY | RT2860_TX_STATUS_BUSY))) + break; + run_delay(sc, 10); + } + if (ntries == 100) + goto fail; + + /* clear Host to MCU mailbox */ + run_write(sc, RT2860_H2M_BBPAGENT, 0); + run_write(sc, RT2860_H2M_MAILBOX, 0); + run_delay(sc, 10); + + if (run_bbp_init(sc) != 0) { + device_printf(sc->sc_dev, "could not initialize BBP\n"); + goto fail; + } + + /* abort TSF synchronization */ + run_read(sc, RT2860_BCN_TIME_CFG, &tmp); + tmp &= ~(RT2860_BCN_TX_EN | RT2860_TSF_TIMER_EN | + RT2860_TBTT_TIMER_EN); + run_write(sc, RT2860_BCN_TIME_CFG, tmp); + + /* clear RX WCID search table */ + run_set_region_4(sc, RT2860_WCID_ENTRY(0), 0, 512); + /* clear WCID attribute table */ + run_set_region_4(sc, RT2860_WCID_ATTR(0), 0, 8 * 32); + + /* hostapd sets a key before init. So, don't clear it. */ + if (sc->cmdq_key_set != RUN_CMDQ_GO) { + /* clear shared key table */ + run_set_region_4(sc, RT2860_SKEY(0, 0), 0, 8 * 32); + /* clear shared key mode */ + run_set_region_4(sc, RT2860_SKEY_MODE_0_7, 0, 4); + } + + run_read(sc, RT2860_US_CYC_CNT, &tmp); + tmp = (tmp & ~0xff) | 0x1e; + run_write(sc, RT2860_US_CYC_CNT, tmp); + + if (sc->mac_rev != 0x0101) + run_write(sc, RT2860_TXOP_CTRL_CFG, 0x0000583f); + + run_write(sc, RT2860_WMM_TXOP0_CFG, 0); + run_write(sc, RT2860_WMM_TXOP1_CFG, 48 << 16 | 96); + + /* write vendor-specific BBP values (from EEPROM) */ + for (i = 0; i < 10; i++) { + if (sc->bbp[i].reg == 0 || sc->bbp[i].reg == 0xff) + continue; + run_bbp_write(sc, sc->bbp[i].reg, sc->bbp[i].val); + } + + /* select Main antenna for 1T1R devices */ + if (sc->rf_rev == RT3070_RF_3020) + run_set_rx_antenna(sc, 0); + + /* send LEDs operating mode to microcontroller */ + (void)run_mcu_cmd(sc, RT2860_MCU_CMD_LED1, sc->led[0]); + (void)run_mcu_cmd(sc, RT2860_MCU_CMD_LED2, sc->led[1]); + (void)run_mcu_cmd(sc, RT2860_MCU_CMD_LED3, sc->led[2]); + + if (sc->mac_ver >= 0x3070) + run_rt3070_rf_init(sc); + + /* disable non-existing Rx chains */ + run_bbp_read(sc, 3, &bbp3); + bbp3 &= ~(1 << 3 | 1 << 4); + if (sc->nrxchains == 2) + bbp3 |= 1 << 3; + else if (sc->nrxchains == 3) + bbp3 |= 1 << 4; + run_bbp_write(sc, 3, bbp3); + + /* disable non-existing Tx chains */ + run_bbp_read(sc, 1, &bbp1); + if (sc->ntxchains == 1) + bbp1 &= ~(1 << 3 | 1 << 4); + run_bbp_write(sc, 1, bbp1); + + if (sc->mac_ver >= 0x3070) + run_rt3070_rf_setup(sc); + + /* select default channel */ + run_set_chan(sc, ic->ic_curchan); + + /* setup initial protection mode */ + run_updateprot_cb(ic); + + /* turn radio LED on */ + run_set_leds(sc, RT2860_LED_RADIO); + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + sc->cmdq_run = RUN_CMDQ_GO; + + for (i = 0; i != RUN_N_XFER; i++) + usbd_xfer_set_stall(sc->sc_xfer[i]); + + usbd_transfer_start(sc->sc_xfer[RUN_BULK_RX]); + + if (run_txrx_enable(sc) != 0) + goto fail; + + return; + +fail: + run_stop(sc); +} + +static void +run_init(void *arg) +{ + struct run_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + RUN_LOCK(sc); + run_init_locked(sc); + RUN_UNLOCK(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ieee80211_start_all(ic); +} + +static void +run_stop(void *arg) +{ + struct run_softc *sc = (struct run_softc *)arg; + struct ifnet *ifp = sc->sc_ifp; + uint32_t tmp; + int i; + int ntries; + + RUN_LOCK_ASSERT(sc, MA_OWNED); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + run_set_leds(sc, 0); /* turn all LEDs off */ + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + sc->ratectl_run = RUN_RATECTL_OFF; + sc->cmdq_run = sc->cmdq_key_set; + + RUN_UNLOCK(sc); + + for(i = 0; i < RUN_N_XFER; i++) + usbd_transfer_drain(sc->sc_xfer[i]); + + RUN_LOCK(sc); + + if (sc->rx_m != NULL) { + m_free(sc->rx_m); + sc->rx_m = NULL; + } + + /* disable Tx/Rx */ + run_read(sc, RT2860_MAC_SYS_CTRL, &tmp); + tmp &= ~(RT2860_MAC_RX_EN | RT2860_MAC_TX_EN); + run_write(sc, RT2860_MAC_SYS_CTRL, tmp); + + /* wait for pending Tx to complete */ + for (ntries = 0; ntries < 100; ntries++) { + if (run_read(sc, RT2860_TXRXQ_PCNT, &tmp) != 0) { + DPRINTF("Cannot read Tx queue count\n"); + break; + } + if ((tmp & RT2860_TX2Q_PCNT_MASK) == 0) { + DPRINTF("All Tx cleared\n"); + break; + } + run_delay(sc, 10); + } + if (ntries >= 100) + DPRINTF("There are still pending Tx\n"); + run_delay(sc, 10); + run_write(sc, RT2860_USB_DMA_CFG, 0); + + run_write(sc, RT2860_MAC_SYS_CTRL, RT2860_BBP_HRST | RT2860_MAC_SRST); + run_write(sc, RT2860_MAC_SYS_CTRL, 0); + + for (i = 0; i != RUN_EP_QUEUES; i++) + run_unsetup_tx_list(sc, &sc->sc_epq[i]); + + return; +} + +static void +run_delay(struct run_softc *sc, unsigned int ms) +{ + usb_pause_mtx(mtx_owned(&sc->sc_mtx) ? + &sc->sc_mtx : NULL, USB_MS_TO_TICKS(ms)); +} + +static device_method_t run_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, run_match), + DEVMETHOD(device_attach, run_attach), + DEVMETHOD(device_detach, run_detach), + + { 0, 0 } +}; + +static driver_t run_driver = { + "run", + run_methods, + sizeof(struct run_softc) +}; + +static devclass_t run_devclass; + +DRIVER_MODULE(run, uhub, run_driver, run_devclass, NULL, 0); +MODULE_DEPEND(run, wlan, 1, 1, 1); +MODULE_DEPEND(run, usb, 1, 1, 1); +MODULE_DEPEND(run, firmware, 1, 1, 1); +MODULE_VERSION(run, 1); diff --git a/sys/bus/u4b/wlan/if_runreg.h b/sys/bus/u4b/wlan/if_runreg.h new file mode 100644 index 0000000000..3a5125f82b --- /dev/null +++ b/sys/bus/u4b/wlan/if_runreg.h @@ -0,0 +1,1224 @@ +/* $OpenBSD: rt2860reg.h,v 1.19 2009/05/18 19:25:07 damien Exp $ */ + +/*- + * Copyright (c) 2007 + * Damien Bergamini + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $FreeBSD$ + */ + +#ifndef _IF_RUNREG_H_ +#define _IF_RUNREG_H_ + +/* PCI registers */ +#define RT2860_PCI_CFG 0x0000 +#define RT2860_PCI_EECTRL 0x0004 +#define RT2860_PCI_MCUCTRL 0x0008 +#define RT2860_PCI_SYSCTRL 0x000c +#define RT2860_PCIE_JTAG 0x0010 + +#define RT2860_CONFIG_NO 1 +#define RT2860_IFACE_INDEX 0 + +#define RT3070_OPT_14 0x0114 + +/* SCH/DMA registers */ +#define RT2860_INT_STATUS 0x0200 +#define RT2860_INT_MASK 0x0204 +#define RT2860_WPDMA_GLO_CFG 0x0208 +#define RT2860_WPDMA_RST_IDX 0x020c +#define RT2860_DELAY_INT_CFG 0x0210 +#define RT2860_WMM_AIFSN_CFG 0x0214 +#define RT2860_WMM_CWMIN_CFG 0x0218 +#define RT2860_WMM_CWMAX_CFG 0x021c +#define RT2860_WMM_TXOP0_CFG 0x0220 +#define RT2860_WMM_TXOP1_CFG 0x0224 +#define RT2860_GPIO_CTRL 0x0228 +#define RT2860_MCU_CMD_REG 0x022c +#define RT2860_TX_BASE_PTR(qid) (0x0230 + (qid) * 16) +#define RT2860_TX_MAX_CNT(qid) (0x0234 + (qid) * 16) +#define RT2860_TX_CTX_IDX(qid) (0x0238 + (qid) * 16) +#define RT2860_TX_DTX_IDX(qid) (0x023c + (qid) * 16) +#define RT2860_RX_BASE_PTR 0x0290 +#define RT2860_RX_MAX_CNT 0x0294 +#define RT2860_RX_CALC_IDX 0x0298 +#define RT2860_FS_DRX_IDX 0x029c +#define RT2860_USB_DMA_CFG 0x02a0 /* RT2870 only */ +#define RT2860_US_CYC_CNT 0x02a4 + +/* PBF registers */ +#define RT2860_SYS_CTRL 0x0400 +#define RT2860_HOST_CMD 0x0404 +#define RT2860_PBF_CFG 0x0408 +#define RT2860_MAX_PCNT 0x040c +#define RT2860_BUF_CTRL 0x0410 +#define RT2860_MCU_INT_STA 0x0414 +#define RT2860_MCU_INT_ENA 0x0418 +#define RT2860_TXQ_IO(qid) (0x041c + (qid) * 4) +#define RT2860_RX0Q_IO 0x0424 +#define RT2860_BCN_OFFSET0 0x042c +#define RT2860_BCN_OFFSET1 0x0430 +#define RT2860_TXRXQ_STA 0x0434 +#define RT2860_TXRXQ_PCNT 0x0438 +#define RT2860_PBF_DBG 0x043c +#define RT2860_CAP_CTRL 0x0440 + +/* RT3070 registers */ +#define RT3070_RF_CSR_CFG 0x0500 +#define RT3070_EFUSE_CTRL 0x0580 +#define RT3070_EFUSE_DATA0 0x0590 +#define RT3070_EFUSE_DATA1 0x0594 +#define RT3070_EFUSE_DATA2 0x0598 +#define RT3070_EFUSE_DATA3 0x059c +#define RT3070_LDO_CFG0 0x05d4 +#define RT3070_GPIO_SWITCH 0x05dc + +/* MAC registers */ +#define RT2860_ASIC_VER_ID 0x1000 +#define RT2860_MAC_SYS_CTRL 0x1004 +#define RT2860_MAC_ADDR_DW0 0x1008 +#define RT2860_MAC_ADDR_DW1 0x100c +#define RT2860_MAC_BSSID_DW0 0x1010 +#define RT2860_MAC_BSSID_DW1 0x1014 +#define RT2860_MAX_LEN_CFG 0x1018 +#define RT2860_BBP_CSR_CFG 0x101c +#define RT2860_RF_CSR_CFG0 0x1020 +#define RT2860_RF_CSR_CFG1 0x1024 +#define RT2860_RF_CSR_CFG2 0x1028 +#define RT2860_LED_CFG 0x102c + +/* undocumented registers */ +#define RT2860_DEBUG 0x10f4 + +/* MAC Timing control registers */ +#define RT2860_XIFS_TIME_CFG 0x1100 +#define RT2860_BKOFF_SLOT_CFG 0x1104 +#define RT2860_NAV_TIME_CFG 0x1108 +#define RT2860_CH_TIME_CFG 0x110c +#define RT2860_PBF_LIFE_TIMER 0x1110 +#define RT2860_BCN_TIME_CFG 0x1114 +#define RT2860_TBTT_SYNC_CFG 0x1118 +#define RT2860_TSF_TIMER_DW0 0x111c +#define RT2860_TSF_TIMER_DW1 0x1120 +#define RT2860_TBTT_TIMER 0x1124 +#define RT2860_INT_TIMER_CFG 0x1128 +#define RT2860_INT_TIMER_EN 0x112c +#define RT2860_CH_IDLE_TIME 0x1130 + +/* MAC Power Save configuration registers */ +#define RT2860_MAC_STATUS_REG 0x1200 +#define RT2860_PWR_PIN_CFG 0x1204 +#define RT2860_AUTO_WAKEUP_CFG 0x1208 + +/* MAC TX configuration registers */ +#define RT2860_EDCA_AC_CFG(aci) (0x1300 + (aci) * 4) +#define RT2860_EDCA_TID_AC_MAP 0x1310 +#define RT2860_TX_PWR_CFG(ridx) (0x1314 + (ridx) * 4) +#define RT2860_TX_PIN_CFG 0x1328 +#define RT2860_TX_BAND_CFG 0x132c +#define RT2860_TX_SW_CFG0 0x1330 +#define RT2860_TX_SW_CFG1 0x1334 +#define RT2860_TX_SW_CFG2 0x1338 +#define RT2860_TXOP_THRES_CFG 0x133c +#define RT2860_TXOP_CTRL_CFG 0x1340 +#define RT2860_TX_RTS_CFG 0x1344 +#define RT2860_TX_TIMEOUT_CFG 0x1348 +#define RT2860_TX_RTY_CFG 0x134c +#define RT2860_TX_LINK_CFG 0x1350 +#define RT2860_HT_FBK_CFG0 0x1354 +#define RT2860_HT_FBK_CFG1 0x1358 +#define RT2860_LG_FBK_CFG0 0x135c +#define RT2860_LG_FBK_CFG1 0x1360 +#define RT2860_CCK_PROT_CFG 0x1364 +#define RT2860_OFDM_PROT_CFG 0x1368 +#define RT2860_MM20_PROT_CFG 0x136c +#define RT2860_MM40_PROT_CFG 0x1370 +#define RT2860_GF20_PROT_CFG 0x1374 +#define RT2860_GF40_PROT_CFG 0x1378 +#define RT2860_EXP_CTS_TIME 0x137c +#define RT2860_EXP_ACK_TIME 0x1380 + +/* MAC RX configuration registers */ +#define RT2860_RX_FILTR_CFG 0x1400 +#define RT2860_AUTO_RSP_CFG 0x1404 +#define RT2860_LEGACY_BASIC_RATE 0x1408 +#define RT2860_HT_BASIC_RATE 0x140c +#define RT2860_HT_CTRL_CFG 0x1410 +#define RT2860_SIFS_COST_CFG 0x1414 +#define RT2860_RX_PARSER_CFG 0x1418 + +/* MAC Security configuration registers */ +#define RT2860_TX_SEC_CNT0 0x1500 +#define RT2860_RX_SEC_CNT0 0x1504 +#define RT2860_CCMP_FC_MUTE 0x1508 + +/* MAC HCCA/PSMP configuration registers */ +#define RT2860_TXOP_HLDR_ADDR0 0x1600 +#define RT2860_TXOP_HLDR_ADDR1 0x1604 +#define RT2860_TXOP_HLDR_ET 0x1608 +#define RT2860_QOS_CFPOLL_RA_DW0 0x160c +#define RT2860_QOS_CFPOLL_A1_DW1 0x1610 +#define RT2860_QOS_CFPOLL_QC 0x1614 + +/* MAC Statistics Counters */ +#define RT2860_RX_STA_CNT0 0x1700 +#define RT2860_RX_STA_CNT1 0x1704 +#define RT2860_RX_STA_CNT2 0x1708 +#define RT2860_TX_STA_CNT0 0x170c +#define RT2860_TX_STA_CNT1 0x1710 +#define RT2860_TX_STA_CNT2 0x1714 +#define RT2860_TX_STAT_FIFO 0x1718 + +/* RX WCID search table */ +#define RT2860_WCID_ENTRY(wcid) (0x1800 + (wcid) * 8) + +#define RT2860_FW_BASE 0x2000 +#define RT2870_FW_BASE 0x3000 + +/* Pair-wise key table */ +#define RT2860_PKEY(wcid) (0x4000 + (wcid) * 32) + +/* IV/EIV table */ +#define RT2860_IVEIV(wcid) (0x6000 + (wcid) * 8) + +/* WCID attribute table */ +#define RT2860_WCID_ATTR(wcid) (0x6800 + (wcid) * 4) + +/* Shared Key Table */ +#define RT2860_SKEY(vap, kidx) (0x6c00 + (vap) * 128 + (kidx) * 32) + +/* Shared Key Mode */ +#define RT2860_SKEY_MODE_0_7 0x7000 +#define RT2860_SKEY_MODE_8_15 0x7004 +#define RT2860_SKEY_MODE_16_23 0x7008 +#define RT2860_SKEY_MODE_24_31 0x700c + +/* Shared Memory between MCU and host */ +#define RT2860_H2M_MAILBOX 0x7010 +#define RT2860_H2M_MAILBOX_CID 0x7014 +#define RT2860_H2M_MAILBOX_STATUS 0x701c +#define RT2860_H2M_BBPAGENT 0x7028 +#define RT2860_BCN_BASE(vap) (0x7800 + (vap) * 512) + + +/* possible flags for register RT2860_PCI_EECTRL */ +#define RT2860_C (1 << 0) +#define RT2860_S (1 << 1) +#define RT2860_D (1 << 2) +#define RT2860_SHIFT_D 2 +#define RT2860_Q (1 << 3) +#define RT2860_SHIFT_Q 3 + +/* possible flags for registers INT_STATUS/INT_MASK */ +#define RT2860_TX_COHERENT (1 << 17) +#define RT2860_RX_COHERENT (1 << 16) +#define RT2860_MAC_INT_4 (1 << 15) +#define RT2860_MAC_INT_3 (1 << 14) +#define RT2860_MAC_INT_2 (1 << 13) +#define RT2860_MAC_INT_1 (1 << 12) +#define RT2860_MAC_INT_0 (1 << 11) +#define RT2860_TX_RX_COHERENT (1 << 10) +#define RT2860_MCU_CMD_INT (1 << 9) +#define RT2860_TX_DONE_INT5 (1 << 8) +#define RT2860_TX_DONE_INT4 (1 << 7) +#define RT2860_TX_DONE_INT3 (1 << 6) +#define RT2860_TX_DONE_INT2 (1 << 5) +#define RT2860_TX_DONE_INT1 (1 << 4) +#define RT2860_TX_DONE_INT0 (1 << 3) +#define RT2860_RX_DONE_INT (1 << 2) +#define RT2860_TX_DLY_INT (1 << 1) +#define RT2860_RX_DLY_INT (1 << 0) + +/* possible flags for register WPDMA_GLO_CFG */ +#define RT2860_HDR_SEG_LEN_SHIFT 8 +#define RT2860_BIG_ENDIAN (1 << 7) +#define RT2860_TX_WB_DDONE (1 << 6) +#define RT2860_WPDMA_BT_SIZE_SHIFT 4 +#define RT2860_WPDMA_BT_SIZE16 0 +#define RT2860_WPDMA_BT_SIZE32 1 +#define RT2860_WPDMA_BT_SIZE64 2 +#define RT2860_WPDMA_BT_SIZE128 3 +#define RT2860_RX_DMA_BUSY (1 << 3) +#define RT2860_RX_DMA_EN (1 << 2) +#define RT2860_TX_DMA_BUSY (1 << 1) +#define RT2860_TX_DMA_EN (1 << 0) + +/* possible flags for register DELAY_INT_CFG */ +#define RT2860_TXDLY_INT_EN (1 << 31) +#define RT2860_TXMAX_PINT_SHIFT 24 +#define RT2860_TXMAX_PTIME_SHIFT 16 +#define RT2860_RXDLY_INT_EN (1 << 15) +#define RT2860_RXMAX_PINT_SHIFT 8 +#define RT2860_RXMAX_PTIME_SHIFT 0 + +/* possible flags for register GPIO_CTRL */ +#define RT2860_GPIO_D_SHIFT 8 +#define RT2860_GPIO_O_SHIFT 0 + +/* possible flags for register USB_DMA_CFG */ +#define RT2860_USB_TX_BUSY (1 << 31) +#define RT2860_USB_RX_BUSY (1 << 30) +#define RT2860_USB_EPOUT_VLD_SHIFT 24 +#define RT2860_USB_TX_EN (1 << 23) +#define RT2860_USB_RX_EN (1 << 22) +#define RT2860_USB_RX_AGG_EN (1 << 21) +#define RT2860_USB_TXOP_HALT (1 << 20) +#define RT2860_USB_TX_CLEAR (1 << 19) +#define RT2860_USB_PHY_WD_EN (1 << 16) +#define RT2860_USB_PHY_MAN_RST (1 << 15) +#define RT2860_USB_RX_AGG_LMT(x) ((x) << 8) /* in unit of 1KB */ +#define RT2860_USB_RX_AGG_TO(x) ((x) & 0xff) /* in unit of 33ns */ + +/* possible flags for register US_CYC_CNT */ +#define RT2860_TEST_EN (1 << 24) +#define RT2860_TEST_SEL_SHIFT 16 +#define RT2860_BT_MODE_EN (1 << 8) +#define RT2860_US_CYC_CNT_SHIFT 0 + +/* possible flags for register SYS_CTRL */ +#define RT2860_HST_PM_SEL (1 << 16) +#define RT2860_CAP_MODE (1 << 14) +#define RT2860_PME_OEN (1 << 13) +#define RT2860_CLKSELECT (1 << 12) +#define RT2860_PBF_CLK_EN (1 << 11) +#define RT2860_MAC_CLK_EN (1 << 10) +#define RT2860_DMA_CLK_EN (1 << 9) +#define RT2860_MCU_READY (1 << 7) +#define RT2860_ASY_RESET (1 << 4) +#define RT2860_PBF_RESET (1 << 3) +#define RT2860_MAC_RESET (1 << 2) +#define RT2860_DMA_RESET (1 << 1) +#define RT2860_MCU_RESET (1 << 0) + +/* possible values for register HOST_CMD */ +#define RT2860_MCU_CMD_SLEEP 0x30 +#define RT2860_MCU_CMD_WAKEUP 0x31 +#define RT2860_MCU_CMD_LEDS 0x50 +#define RT2860_MCU_CMD_LED_RSSI 0x51 +#define RT2860_MCU_CMD_LED1 0x52 +#define RT2860_MCU_CMD_LED2 0x53 +#define RT2860_MCU_CMD_LED3 0x54 +#define RT2860_MCU_CMD_RFRESET 0x72 +#define RT2860_MCU_CMD_ANTSEL 0x73 +#define RT2860_MCU_CMD_BBP 0x80 +#define RT2860_MCU_CMD_PSLEVEL 0x83 + +/* possible flags for register PBF_CFG */ +#define RT2860_TX1Q_NUM_SHIFT 21 +#define RT2860_TX2Q_NUM_SHIFT 16 +#define RT2860_NULL0_MODE (1 << 15) +#define RT2860_NULL1_MODE (1 << 14) +#define RT2860_RX_DROP_MODE (1 << 13) +#define RT2860_TX0Q_MANUAL (1 << 12) +#define RT2860_TX1Q_MANUAL (1 << 11) +#define RT2860_TX2Q_MANUAL (1 << 10) +#define RT2860_RX0Q_MANUAL (1 << 9) +#define RT2860_HCCA_EN (1 << 8) +#define RT2860_TX0Q_EN (1 << 4) +#define RT2860_TX1Q_EN (1 << 3) +#define RT2860_TX2Q_EN (1 << 2) +#define RT2860_RX0Q_EN (1 << 1) + +/* possible flags for register BUF_CTRL */ +#define RT2860_WRITE_TXQ(qid) (1 << (11 - (qid))) +#define RT2860_NULL0_KICK (1 << 7) +#define RT2860_NULL1_KICK (1 << 6) +#define RT2860_BUF_RESET (1 << 5) +#define RT2860_READ_TXQ(qid) (1 << (3 - (qid)) +#define RT2860_READ_RX0Q (1 << 0) + +/* possible flags for registers MCU_INT_STA/MCU_INT_ENA */ +#define RT2860_MCU_MAC_INT_8 (1 << 24) +#define RT2860_MCU_MAC_INT_7 (1 << 23) +#define RT2860_MCU_MAC_INT_6 (1 << 22) +#define RT2860_MCU_MAC_INT_4 (1 << 20) +#define RT2860_MCU_MAC_INT_3 (1 << 19) +#define RT2860_MCU_MAC_INT_2 (1 << 18) +#define RT2860_MCU_MAC_INT_1 (1 << 17) +#define RT2860_MCU_MAC_INT_0 (1 << 16) +#define RT2860_DTX0_INT (1 << 11) +#define RT2860_DTX1_INT (1 << 10) +#define RT2860_DTX2_INT (1 << 9) +#define RT2860_DRX0_INT (1 << 8) +#define RT2860_HCMD_INT (1 << 7) +#define RT2860_N0TX_INT (1 << 6) +#define RT2860_N1TX_INT (1 << 5) +#define RT2860_BCNTX_INT (1 << 4) +#define RT2860_MTX0_INT (1 << 3) +#define RT2860_MTX1_INT (1 << 2) +#define RT2860_MTX2_INT (1 << 1) +#define RT2860_MRX0_INT (1 << 0) + +/* possible flags for register TXRXQ_PCNT */ +#define RT2860_RX0Q_PCNT_MASK 0xff000000 +#define RT2860_TX2Q_PCNT_MASK 0x00ff0000 +#define RT2860_TX1Q_PCNT_MASK 0x0000ff00 +#define RT2860_TX0Q_PCNT_MASK 0x000000ff + +/* possible flags for register CAP_CTRL */ +#define RT2860_CAP_ADC_FEQ (1 << 31) +#define RT2860_CAP_START (1 << 30) +#define RT2860_MAN_TRIG (1 << 29) +#define RT2860_TRIG_OFFSET_SHIFT 16 +#define RT2860_START_ADDR_SHIFT 0 + +/* possible flags for register RF_CSR_CFG */ +#define RT3070_RF_KICK (1 << 17) +#define RT3070_RF_WRITE (1 << 16) + +/* possible flags for register EFUSE_CTRL */ +#define RT3070_SEL_EFUSE (1 << 31) +#define RT3070_EFSROM_KICK (1 << 30) +#define RT3070_EFSROM_AIN_MASK 0x03ff0000 +#define RT3070_EFSROM_AIN_SHIFT 16 +#define RT3070_EFSROM_MODE_MASK 0x000000c0 +#define RT3070_EFUSE_AOUT_MASK 0x0000003f + +/* possible flags for register MAC_SYS_CTRL */ +#define RT2860_RX_TS_EN (1 << 7) +#define RT2860_WLAN_HALT_EN (1 << 6) +#define RT2860_PBF_LOOP_EN (1 << 5) +#define RT2860_CONT_TX_TEST (1 << 4) +#define RT2860_MAC_RX_EN (1 << 3) +#define RT2860_MAC_TX_EN (1 << 2) +#define RT2860_BBP_HRST (1 << 1) +#define RT2860_MAC_SRST (1 << 0) + +/* possible flags for register MAC_BSSID_DW1 */ +#define RT2860_MULTI_BCN_NUM_SHIFT 18 +#define RT2860_MULTI_BSSID_MODE_SHIFT 16 + +/* possible flags for register MAX_LEN_CFG */ +#define RT2860_MIN_MPDU_LEN_SHIFT 16 +#define RT2860_MAX_PSDU_LEN_SHIFT 12 +#define RT2860_MAX_PSDU_LEN8K 0 +#define RT2860_MAX_PSDU_LEN16K 1 +#define RT2860_MAX_PSDU_LEN32K 2 +#define RT2860_MAX_PSDU_LEN64K 3 +#define RT2860_MAX_MPDU_LEN_SHIFT 0 + +/* possible flags for registers BBP_CSR_CFG/H2M_BBPAGENT */ +#define RT2860_BBP_RW_PARALLEL (1 << 19) +#define RT2860_BBP_PAR_DUR_112_5 (1 << 18) +#define RT2860_BBP_CSR_KICK (1 << 17) +#define RT2860_BBP_CSR_READ (1 << 16) +#define RT2860_BBP_ADDR_SHIFT 8 +#define RT2860_BBP_DATA_SHIFT 0 + +/* possible flags for register RF_CSR_CFG0 */ +#define RT2860_RF_REG_CTRL (1 << 31) +#define RT2860_RF_LE_SEL1 (1 << 30) +#define RT2860_RF_LE_STBY (1 << 29) +#define RT2860_RF_REG_WIDTH_SHIFT 24 +#define RT2860_RF_REG_0_SHIFT 0 + +/* possible flags for register RF_CSR_CFG1 */ +#define RT2860_RF_DUR_5 (1 << 24) +#define RT2860_RF_REG_1_SHIFT 0 + +/* possible flags for register LED_CFG */ +#define RT2860_LED_POL (1 << 30) +#define RT2860_Y_LED_MODE_SHIFT 28 +#define RT2860_G_LED_MODE_SHIFT 26 +#define RT2860_R_LED_MODE_SHIFT 24 +#define RT2860_LED_MODE_OFF 0 +#define RT2860_LED_MODE_BLINK_TX 1 +#define RT2860_LED_MODE_SLOW_BLINK 2 +#define RT2860_LED_MODE_ON 3 +#define RT2860_SLOW_BLK_TIME_SHIFT 16 +#define RT2860_LED_OFF_TIME_SHIFT 8 +#define RT2860_LED_ON_TIME_SHIFT 0 + +/* possible flags for register XIFS_TIME_CFG */ +#define RT2860_BB_RXEND_EN (1 << 29) +#define RT2860_EIFS_TIME_SHIFT 20 +#define RT2860_OFDM_XIFS_TIME_SHIFT 16 +#define RT2860_OFDM_SIFS_TIME_SHIFT 8 +#define RT2860_CCK_SIFS_TIME_SHIFT 0 + +/* possible flags for register BKOFF_SLOT_CFG */ +#define RT2860_CC_DELAY_TIME_SHIFT 8 +#define RT2860_SLOT_TIME 0 + +/* possible flags for register NAV_TIME_CFG */ +#define RT2860_NAV_UPD (1 << 31) +#define RT2860_NAV_UPD_VAL_SHIFT 16 +#define RT2860_NAV_CLR_EN (1 << 15) +#define RT2860_NAV_TIMER_SHIFT 0 + +/* possible flags for register CH_TIME_CFG */ +#define RT2860_EIFS_AS_CH_BUSY (1 << 4) +#define RT2860_NAV_AS_CH_BUSY (1 << 3) +#define RT2860_RX_AS_CH_BUSY (1 << 2) +#define RT2860_TX_AS_CH_BUSY (1 << 1) +#define RT2860_CH_STA_TIMER_EN (1 << 0) + +/* possible values for register BCN_TIME_CFG */ +#define RT2860_TSF_INS_COMP_SHIFT 24 +#define RT2860_BCN_TX_EN (1 << 20) +#define RT2860_TBTT_TIMER_EN (1 << 19) +#define RT2860_TSF_SYNC_MODE_SHIFT 17 +#define RT2860_TSF_SYNC_MODE_DIS 0 +#define RT2860_TSF_SYNC_MODE_STA 1 +#define RT2860_TSF_SYNC_MODE_IBSS 2 +#define RT2860_TSF_SYNC_MODE_HOSTAP 3 +#define RT2860_TSF_TIMER_EN (1 << 16) +#define RT2860_BCN_INTVAL_SHIFT 0 + +/* possible flags for register TBTT_SYNC_CFG */ +#define RT2860_BCN_CWMIN_SHIFT 20 +#define RT2860_BCN_AIFSN_SHIFT 16 +#define RT2860_BCN_EXP_WIN_SHIFT 8 +#define RT2860_TBTT_ADJUST_SHIFT 0 + +/* possible flags for register INT_TIMER_CFG */ +#define RT2860_GP_TIMER_SHIFT 16 +#define RT2860_PRE_TBTT_TIMER_SHIFT 0 + +/* possible flags for register INT_TIMER_EN */ +#define RT2860_GP_TIMER_EN (1 << 1) +#define RT2860_PRE_TBTT_INT_EN (1 << 0) + +/* possible flags for register MAC_STATUS_REG */ +#define RT2860_RX_STATUS_BUSY (1 << 1) +#define RT2860_TX_STATUS_BUSY (1 << 0) + +/* possible flags for register PWR_PIN_CFG */ +#define RT2860_IO_ADDA_PD (1 << 3) +#define RT2860_IO_PLL_PD (1 << 2) +#define RT2860_IO_RA_PE (1 << 1) +#define RT2860_IO_RF_PE (1 << 0) + +/* possible flags for register AUTO_WAKEUP_CFG */ +#define RT2860_AUTO_WAKEUP_EN (1 << 15) +#define RT2860_SLEEP_TBTT_NUM_SHIFT 8 +#define RT2860_WAKEUP_LEAD_TIME_SHIFT 0 + +/* possible flags for register TX_PIN_CFG */ +#define RT2860_TRSW_POL (1 << 19) +#define RT2860_TRSW_EN (1 << 18) +#define RT2860_RFTR_POL (1 << 17) +#define RT2860_RFTR_EN (1 << 16) +#define RT2860_LNA_PE_G1_POL (1 << 15) +#define RT2860_LNA_PE_A1_POL (1 << 14) +#define RT2860_LNA_PE_G0_POL (1 << 13) +#define RT2860_LNA_PE_A0_POL (1 << 12) +#define RT2860_LNA_PE_G1_EN (1 << 11) +#define RT2860_LNA_PE_A1_EN (1 << 10) +#define RT2860_LNA_PE1_EN (RT2860_LNA_PE_A1_EN | RT2860_LNA_PE_G1_EN) +#define RT2860_LNA_PE_G0_EN (1 << 9) +#define RT2860_LNA_PE_A0_EN (1 << 8) +#define RT2860_LNA_PE0_EN (RT2860_LNA_PE_A0_EN | RT2860_LNA_PE_G0_EN) +#define RT2860_PA_PE_G1_POL (1 << 7) +#define RT2860_PA_PE_A1_POL (1 << 6) +#define RT2860_PA_PE_G0_POL (1 << 5) +#define RT2860_PA_PE_A0_POL (1 << 4) +#define RT2860_PA_PE_G1_EN (1 << 3) +#define RT2860_PA_PE_A1_EN (1 << 2) +#define RT2860_PA_PE_G0_EN (1 << 1) +#define RT2860_PA_PE_A0_EN (1 << 0) + +/* possible flags for register TX_BAND_CFG */ +#define RT2860_5G_BAND_SEL_N (1 << 2) +#define RT2860_5G_BAND_SEL_P (1 << 1) +#define RT2860_TX_BAND_SEL (1 << 0) + +/* possible flags for register TX_SW_CFG0 */ +#define RT2860_DLY_RFTR_EN_SHIFT 24 +#define RT2860_DLY_TRSW_EN_SHIFT 16 +#define RT2860_DLY_PAPE_EN_SHIFT 8 +#define RT2860_DLY_TXPE_EN_SHIFT 0 + +/* possible flags for register TX_SW_CFG1 */ +#define RT2860_DLY_RFTR_DIS_SHIFT 16 +#define RT2860_DLY_TRSW_DIS_SHIFT 8 +#define RT2860_DLY_PAPE_DIS SHIFT 0 + +/* possible flags for register TX_SW_CFG2 */ +#define RT2860_DLY_LNA_EN_SHIFT 24 +#define RT2860_DLY_LNA_DIS_SHIFT 16 +#define RT2860_DLY_DAC_EN_SHIFT 8 +#define RT2860_DLY_DAC_DIS_SHIFT 0 + +/* possible flags for register TXOP_THRES_CFG */ +#define RT2860_TXOP_REM_THRES_SHIFT 24 +#define RT2860_CF_END_THRES_SHIFT 16 +#define RT2860_RDG_IN_THRES 8 +#define RT2860_RDG_OUT_THRES 0 + +/* possible flags for register TXOP_CTRL_CFG */ +#define RT2860_EXT_CW_MIN_SHIFT 16 +#define RT2860_EXT_CCA_DLY_SHIFT 8 +#define RT2860_EXT_CCA_EN (1 << 7) +#define RT2860_LSIG_TXOP_EN (1 << 6) +#define RT2860_TXOP_TRUN_EN_MIMOPS (1 << 4) +#define RT2860_TXOP_TRUN_EN_TXOP (1 << 3) +#define RT2860_TXOP_TRUN_EN_RATE (1 << 2) +#define RT2860_TXOP_TRUN_EN_AC (1 << 1) +#define RT2860_TXOP_TRUN_EN_TIMEOUT (1 << 0) + +/* possible flags for register TX_RTS_CFG */ +#define RT2860_RTS_FBK_EN (1 << 24) +#define RT2860_RTS_THRES_SHIFT 8 +#define RT2860_RTS_RTY_LIMIT_SHIFT 0 + +/* possible flags for register TX_TIMEOUT_CFG */ +#define RT2860_TXOP_TIMEOUT_SHIFT 16 +#define RT2860_RX_ACK_TIMEOUT_SHIFT 8 +#define RT2860_MPDU_LIFE_TIME_SHIFT 4 + +/* possible flags for register TX_RTY_CFG */ +#define RT2860_TX_AUTOFB_EN (1 << 30) +#define RT2860_AGG_RTY_MODE_TIMER (1 << 29) +#define RT2860_NAG_RTY_MODE_TIMER (1 << 28) +#define RT2860_LONG_RTY_THRES_SHIFT 16 +#define RT2860_LONG_RTY_LIMIT_SHIFT 8 +#define RT2860_SHORT_RTY_LIMIT_SHIFT 0 + +/* possible flags for register TX_LINK_CFG */ +#define RT2860_REMOTE_MFS_SHIFT 24 +#define RT2860_REMOTE_MFB_SHIFT 16 +#define RT2860_TX_CFACK_EN (1 << 12) +#define RT2860_TX_RDG_EN (1 << 11) +#define RT2860_TX_MRQ_EN (1 << 10) +#define RT2860_REMOTE_UMFS_EN (1 << 9) +#define RT2860_TX_MFB_EN (1 << 8) +#define RT2860_REMOTE_MFB_LT_SHIFT 0 + +/* possible flags for registers *_PROT_CFG */ +#define RT2860_RTSTH_EN (1 << 26) +#define RT2860_TXOP_ALLOW_GF40 (1 << 25) +#define RT2860_TXOP_ALLOW_GF20 (1 << 24) +#define RT2860_TXOP_ALLOW_MM40 (1 << 23) +#define RT2860_TXOP_ALLOW_MM20 (1 << 22) +#define RT2860_TXOP_ALLOW_OFDM (1 << 21) +#define RT2860_TXOP_ALLOW_CCK (1 << 20) +#define RT2860_TXOP_ALLOW_ALL (0x3f << 20) +#define RT2860_PROT_NAV_SHORT (1 << 18) +#define RT2860_PROT_NAV_LONG (2 << 18) +#define RT2860_PROT_CTRL_RTS_CTS (1 << 16) +#define RT2860_PROT_CTRL_CTS (2 << 16) + +/* possible flags for registers EXP_{CTS,ACK}_TIME */ +#define RT2860_EXP_OFDM_TIME_SHIFT 16 +#define RT2860_EXP_CCK_TIME_SHIFT 0 + +/* possible flags for register RX_FILTR_CFG */ +#define RT2860_DROP_CTRL_RSV (1 << 16) +#define RT2860_DROP_BAR (1 << 15) +#define RT2860_DROP_BA (1 << 14) +#define RT2860_DROP_PSPOLL (1 << 13) +#define RT2860_DROP_RTS (1 << 12) +#define RT2860_DROP_CTS (1 << 11) +#define RT2860_DROP_ACK (1 << 10) +#define RT2860_DROP_CFEND (1 << 9) +#define RT2860_DROP_CFACK (1 << 8) +#define RT2860_DROP_DUPL (1 << 7) +#define RT2860_DROP_BC (1 << 6) +#define RT2860_DROP_MC (1 << 5) +#define RT2860_DROP_VER_ERR (1 << 4) +#define RT2860_DROP_NOT_MYBSS (1 << 3) +#define RT2860_DROP_UC_NOME (1 << 2) +#define RT2860_DROP_PHY_ERR (1 << 1) +#define RT2860_DROP_CRC_ERR (1 << 0) + +/* possible flags for register AUTO_RSP_CFG */ +#define RT2860_CTRL_PWR_BIT (1 << 7) +#define RT2860_BAC_ACK_POLICY (1 << 6) +#define RT2860_CCK_SHORT_EN (1 << 4) +#define RT2860_CTS_40M_REF_EN (1 << 3) +#define RT2860_CTS_40M_MODE_EN (1 << 2) +#define RT2860_BAC_ACKPOLICY_EN (1 << 1) +#define RT2860_AUTO_RSP_EN (1 << 0) + +/* possible flags for register SIFS_COST_CFG */ +#define RT2860_OFDM_SIFS_COST_SHIFT 8 +#define RT2860_CCK_SIFS_COST_SHIFT 0 + +/* possible flags for register TXOP_HLDR_ET */ +#define RT2860_TXOP_ETM1_EN (1 << 25) +#define RT2860_TXOP_ETM0_EN (1 << 24) +#define RT2860_TXOP_ETM_THRES_SHIFT 16 +#define RT2860_TXOP_ETO_EN (1 << 8) +#define RT2860_TXOP_ETO_THRES_SHIFT 1 +#define RT2860_PER_RX_RST_EN (1 << 0) + +/* possible flags for register TX_STAT_FIFO */ +#define RT2860_TXQ_MCS_SHIFT 16 +#define RT2860_TXQ_WCID_SHIFT 8 +#define RT2860_TXQ_ACKREQ (1 << 7) +#define RT2860_TXQ_AGG (1 << 6) +#define RT2860_TXQ_OK (1 << 5) +#define RT2860_TXQ_PID_SHIFT 1 +#define RT2860_TXQ_VLD (1 << 0) + +/* possible flags for register WCID_ATTR */ +#define RT2860_MODE_NOSEC 0 +#define RT2860_MODE_WEP40 1 +#define RT2860_MODE_WEP104 2 +#define RT2860_MODE_TKIP 3 +#define RT2860_MODE_AES_CCMP 4 +#define RT2860_MODE_CKIP40 5 +#define RT2860_MODE_CKIP104 6 +#define RT2860_MODE_CKIP128 7 +#define RT2860_RX_PKEY_EN (1 << 0) + +/* possible flags for register H2M_MAILBOX */ +#define RT2860_H2M_BUSY (1 << 24) +#define RT2860_TOKEN_NO_INTR 0xff + + +/* possible flags for MCU command RT2860_MCU_CMD_LEDS */ +#define RT2860_LED_RADIO (1 << 13) +#define RT2860_LED_LINK_2GHZ (1 << 14) +#define RT2860_LED_LINK_5GHZ (1 << 15) + + +/* possible flags for RT3020 RF register 1 */ +#define RT3070_RF_BLOCK (1 << 0) +#define RT3070_RX0_PD (1 << 2) +#define RT3070_TX0_PD (1 << 3) +#define RT3070_RX1_PD (1 << 4) +#define RT3070_TX1_PD (1 << 5) + +/* possible flags for RT3020 RF register 15 */ +#define RT3070_TX_LO2 (1 << 3) + +/* possible flags for RT3020 RF register 17 */ +#define RT3070_TX_LO1 (1 << 3) + +/* possible flags for RT3020 RF register 20 */ +#define RT3070_RX_LO1 (1 << 3) + +/* possible flags for RT3020 RF register 21 */ +#define RT3070_RX_LO2 (1 << 3) + + +/* RT2860 TX descriptor */ +struct rt2860_txd { + uint32_t sdp0; /* Segment Data Pointer 0 */ + uint16_t sdl1; /* Segment Data Length 1 */ +#define RT2860_TX_BURST (1 << 15) +#define RT2860_TX_LS1 (1 << 14) /* SDP1 is the last segment */ + + uint16_t sdl0; /* Segment Data Length 0 */ +#define RT2860_TX_DDONE (1 << 15) +#define RT2860_TX_LS0 (1 << 14) /* SDP0 is the last segment */ + + uint32_t sdp1; /* Segment Data Pointer 1 */ + uint8_t reserved[3]; + uint8_t flags; +#define RT2860_TX_QSEL_SHIFT 1 +#define RT2860_TX_QSEL_MGMT (0 << 1) +#define RT2860_TX_QSEL_HCCA (1 << 1) +#define RT2860_TX_QSEL_EDCA (2 << 1) +#define RT2860_TX_WIV (1 << 0) +} __packed; + +/* RT2870 TX descriptor */ +struct rt2870_txd { + uint16_t len; + uint8_t pad; + uint8_t flags; +} __packed; + +/* TX Wireless Information */ +struct rt2860_txwi { + uint8_t flags; +#define RT2860_TX_MPDU_DSITY_SHIFT 5 +#define RT2860_TX_AMPDU (1 << 4) +#define RT2860_TX_TS (1 << 3) +#define RT2860_TX_CFACK (1 << 2) +#define RT2860_TX_MMPS (1 << 1) +#define RT2860_TX_FRAG (1 << 0) + + uint8_t txop; +#define RT2860_TX_TXOP_HT 0 +#define RT2860_TX_TXOP_PIFS 1 +#define RT2860_TX_TXOP_SIFS 2 +#define RT2860_TX_TXOP_BACKOFF 3 + + uint16_t phy; +#define RT2860_PHY_MODE 0xc000 +#define RT2860_PHY_CCK (0 << 14) +#define RT2860_PHY_OFDM (1 << 14) +#define RT2860_PHY_HT (2 << 14) +#define RT2860_PHY_HT_GF (3 << 14) +#define RT2860_PHY_SGI (1 << 8) +#define RT2860_PHY_BW40 (1 << 7) +#define RT2860_PHY_MCS 0x7f +#define RT2860_PHY_SHPRE (1 << 3) + + uint8_t xflags; +#define RT2860_TX_BAWINSIZE_SHIFT 2 +#define RT2860_TX_NSEQ (1 << 1) +#define RT2860_TX_ACK (1 << 0) + + uint8_t wcid; /* Wireless Client ID */ + uint16_t len; +#define RT2860_TX_PID_SHIFT 12 + + uint32_t iv; + uint32_t eiv; +} __packed; + +/* RT2860 RX descriptor */ +struct rt2860_rxd { + uint32_t sdp0; + uint16_t sdl1; /* unused */ + uint16_t sdl0; +#define RT2860_RX_DDONE (1 << 15) +#define RT2860_RX_LS0 (1 << 14) + + uint32_t sdp1; /* unused */ + uint32_t flags; +#define RT2860_RX_DEC (1 << 16) +#define RT2860_RX_AMPDU (1 << 15) +#define RT2860_RX_L2PAD (1 << 14) +#define RT2860_RX_RSSI (1 << 13) +#define RT2860_RX_HTC (1 << 12) +#define RT2860_RX_AMSDU (1 << 11) +#define RT2860_RX_MICERR (1 << 10) +#define RT2860_RX_ICVERR (1 << 9) +#define RT2860_RX_CRCERR (1 << 8) +#define RT2860_RX_MYBSS (1 << 7) +#define RT2860_RX_BC (1 << 6) +#define RT2860_RX_MC (1 << 5) +#define RT2860_RX_UC2ME (1 << 4) +#define RT2860_RX_FRAG (1 << 3) +#define RT2860_RX_NULL (1 << 2) +#define RT2860_RX_DATA (1 << 1) +#define RT2860_RX_BA (1 << 0) +} __packed; + +/* RT2870 RX descriptor */ +struct rt2870_rxd { + /* single 32-bit field */ + uint32_t flags; +} __packed; + +/* RX Wireless Information */ +struct rt2860_rxwi { + uint8_t wcid; + uint8_t keyidx; +#define RT2860_RX_UDF_SHIFT 5 +#define RT2860_RX_BSS_IDX_SHIFT 2 + + uint16_t len; +#define RT2860_RX_TID_SHIFT 12 + + uint16_t seq; + uint16_t phy; + uint8_t rssi[3]; + uint8_t reserved1; + uint8_t snr[2]; + uint16_t reserved2; +} __packed; + + +/* first DMA segment contains TXWI + 802.11 header + 32-bit padding */ +#define RT2860_TXWI_DMASZ \ + (sizeof (struct rt2860_txwi) + \ + sizeof (struct ieee80211_htframe) + \ + sizeof (uint16_t)) + +#define RT2860_RF1 0 +#define RT2860_RF2 2 +#define RT2860_RF3 1 +#define RT2860_RF4 3 + +#define RT2860_RF_2820 1 /* 2T3R */ +#define RT2860_RF_2850 2 /* dual-band 2T3R */ +#define RT2860_RF_2720 3 /* 1T2R */ +#define RT2860_RF_2750 4 /* dual-band 1T2R */ +#define RT3070_RF_3020 5 /* 1T1R */ +#define RT3070_RF_2020 6 /* b/g */ +#define RT3070_RF_3021 7 /* 1T2R */ +#define RT3070_RF_3022 8 /* 2T2R */ +#define RT3070_RF_3052 9 /* dual-band 2T2R */ + +/* USB commands for RT2870 only */ +#define RT2870_RESET 1 +#define RT2870_WRITE_2 2 +#define RT2870_WRITE_REGION_1 6 +#define RT2870_READ_REGION_1 7 +#define RT2870_EEPROM_READ 9 + +#define RT2860_EEPROM_DELAY 1 /* minimum hold time (microsecond) */ + +#define RT2860_EEPROM_VERSION 0x01 +#define RT2860_EEPROM_MAC01 0x02 +#define RT2860_EEPROM_MAC23 0x03 +#define RT2860_EEPROM_MAC45 0x04 +#define RT2860_EEPROM_PCIE_PSLEVEL 0x11 +#define RT2860_EEPROM_REV 0x12 +#define RT2860_EEPROM_ANTENNA 0x1a +#define RT2860_EEPROM_CONFIG 0x1b +#define RT2860_EEPROM_COUNTRY 0x1c +#define RT2860_EEPROM_FREQ_LEDS 0x1d +#define RT2860_EEPROM_LED1 0x1e +#define RT2860_EEPROM_LED2 0x1f +#define RT2860_EEPROM_LED3 0x20 +#define RT2860_EEPROM_LNA 0x22 +#define RT2860_EEPROM_RSSI1_2GHZ 0x23 +#define RT2860_EEPROM_RSSI2_2GHZ 0x24 +#define RT2860_EEPROM_RSSI1_5GHZ 0x25 +#define RT2860_EEPROM_RSSI2_5GHZ 0x26 +#define RT2860_EEPROM_DELTAPWR 0x28 +#define RT2860_EEPROM_PWR2GHZ_BASE1 0x29 +#define RT2860_EEPROM_PWR2GHZ_BASE2 0x30 +#define RT2860_EEPROM_TSSI1_2GHZ 0x37 +#define RT2860_EEPROM_TSSI2_2GHZ 0x38 +#define RT2860_EEPROM_TSSI3_2GHZ 0x39 +#define RT2860_EEPROM_TSSI4_2GHZ 0x3a +#define RT2860_EEPROM_TSSI5_2GHZ 0x3b +#define RT2860_EEPROM_PWR5GHZ_BASE1 0x3c +#define RT2860_EEPROM_PWR5GHZ_BASE2 0x53 +#define RT2860_EEPROM_TSSI1_5GHZ 0x6a +#define RT2860_EEPROM_TSSI2_5GHZ 0x6b +#define RT2860_EEPROM_TSSI3_5GHZ 0x6c +#define RT2860_EEPROM_TSSI4_5GHZ 0x6d +#define RT2860_EEPROM_TSSI5_5GHZ 0x6e +#define RT2860_EEPROM_RPWR 0x6f +#define RT2860_EEPROM_BBP_BASE 0x78 +#define RT3071_EEPROM_RF_BASE 0x82 + +#define RT2860_RIDX_CCK1 0 +#define RT2860_RIDX_CCK11 3 +#define RT2860_RIDX_OFDM6 4 +#define RT2860_RIDX_MAX 12 +static const struct rt2860_rate { + uint8_t rate; + uint8_t mcs; + enum ieee80211_phytype phy; + uint8_t ctl_ridx; + uint16_t sp_ack_dur; + uint16_t lp_ack_dur; +} rt2860_rates[] = { + { 2, 0, IEEE80211_T_DS, 0, 314, 314 }, + { 4, 1, IEEE80211_T_DS, 1, 258, 162 }, + { 11, 2, IEEE80211_T_DS, 2, 223, 127 }, + { 22, 3, IEEE80211_T_DS, 3, 213, 117 }, + { 12, 0, IEEE80211_T_OFDM, 4, 60, 60 }, + { 18, 1, IEEE80211_T_OFDM, 4, 52, 52 }, + { 24, 2, IEEE80211_T_OFDM, 6, 48, 48 }, + { 36, 3, IEEE80211_T_OFDM, 6, 44, 44 }, + { 48, 4, IEEE80211_T_OFDM, 8, 44, 44 }, + { 72, 5, IEEE80211_T_OFDM, 8, 40, 40 }, + { 96, 6, IEEE80211_T_OFDM, 8, 40, 40 }, + { 108, 7, IEEE80211_T_OFDM, 8, 40, 40 } +}; + +/* + * Control and status registers access macros. + */ +#define RAL_READ(sc, reg) \ + bus_space_read_4((sc)->sc_st, (sc)->sc_sh, (reg)) + +#define RAL_WRITE(sc, reg, val) \ + bus_space_write_4((sc)->sc_st, (sc)->sc_sh, (reg), (val)) + +#define RAL_BARRIER_WRITE(sc) \ + bus_space_barrier((sc)->sc_st, (sc)->sc_sh, 0, 0x1800, \ + BUS_SPACE_BARRIER_WRITE) + +#define RAL_BARRIER_READ_WRITE(sc) \ + bus_space_barrier((sc)->sc_st, (sc)->sc_sh, 0, 0x1800, \ + BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE) + +#define RAL_WRITE_REGION_1(sc, offset, datap, count) \ + bus_space_write_region_1((sc)->sc_st, (sc)->sc_sh, (offset), \ + (datap), (count)) + +#define RAL_SET_REGION_4(sc, offset, val, count) \ + bus_space_set_region_4((sc)->sc_st, (sc)->sc_sh, (offset), \ + (val), (count)) + +/* + * EEPROM access macro. + */ +#define RT2860_EEPROM_CTL(sc, val) do { \ + RAL_WRITE((sc), RT2860_PCI_EECTRL, (val)); \ + RAL_BARRIER_READ_WRITE((sc)); \ + DELAY(RT2860_EEPROM_DELAY); \ +} while (/* CONSTCOND */0) + +/* + * Default values for MAC registers; values taken from the reference driver. + */ +#define RT2860_DEF_MAC \ + { RT2860_BCN_OFFSET0, 0xf8f0e8e0 }, \ + { RT2860_LEGACY_BASIC_RATE, 0x0000013f }, \ + { RT2860_HT_BASIC_RATE, 0x00008003 }, \ + { RT2860_MAC_SYS_CTRL, 0x00000000 }, \ + { RT2860_BKOFF_SLOT_CFG, 0x00000209 }, \ + { RT2860_TX_SW_CFG0, 0x00000000 }, \ + { RT2860_TX_SW_CFG1, 0x00080606 }, \ + { RT2860_TX_LINK_CFG, 0x00001020 }, \ + { RT2860_TX_TIMEOUT_CFG, 0x000a2090 }, \ + { RT2860_LED_CFG, 0x7f031e46 }, \ + { RT2860_WMM_AIFSN_CFG, 0x00002273 }, \ + { RT2860_WMM_CWMIN_CFG, 0x00002344 }, \ + { RT2860_WMM_CWMAX_CFG, 0x000034aa }, \ + { RT2860_MAX_PCNT, 0x1f3fbf9f }, \ + { RT2860_TX_RTY_CFG, 0x47d01f0f }, \ + { RT2860_AUTO_RSP_CFG, 0x00000013 }, \ + { RT2860_CCK_PROT_CFG, 0x05740003 }, \ + { RT2860_OFDM_PROT_CFG, 0x05740003 }, \ + { RT2860_GF20_PROT_CFG, 0x01744004 }, \ + { RT2860_GF40_PROT_CFG, 0x03f44084 }, \ + { RT2860_MM20_PROT_CFG, 0x01744004 }, \ + { RT2860_MM40_PROT_CFG, 0x03f54084 }, \ + { RT2860_TXOP_CTRL_CFG, 0x0000583f }, \ + { RT2860_TXOP_HLDR_ET, 0x00000002 }, \ + { RT2860_TX_RTS_CFG, 0x00092b20 }, \ + { RT2860_EXP_ACK_TIME, 0x002400ca }, \ + { RT2860_XIFS_TIME_CFG, 0x33a41010 }, \ + { RT2860_PWR_PIN_CFG, 0x00000003 } + +/* XXX only a few registers differ from above, try to merge? */ +#define RT2870_DEF_MAC \ + { RT2860_BCN_OFFSET0, 0xf8f0e8e0 }, \ + { RT2860_LEGACY_BASIC_RATE, 0x0000013f }, \ + { RT2860_HT_BASIC_RATE, 0x00008003 }, \ + { RT2860_MAC_SYS_CTRL, 0x00000000 }, \ + { RT2860_BKOFF_SLOT_CFG, 0x00000209 }, \ + { RT2860_TX_SW_CFG0, 0x00000000 }, \ + { RT2860_TX_SW_CFG1, 0x00080606 }, \ + { RT2860_TX_LINK_CFG, 0x00001020 }, \ + { RT2860_TX_TIMEOUT_CFG, 0x000a2090 }, \ + { RT2860_LED_CFG, 0x7f031e46 }, \ + { RT2860_WMM_AIFSN_CFG, 0x00002273 }, \ + { RT2860_WMM_CWMIN_CFG, 0x00002344 }, \ + { RT2860_WMM_CWMAX_CFG, 0x000034aa }, \ + { RT2860_MAX_PCNT, 0x1f3fbf9f }, \ + { RT2860_TX_RTY_CFG, 0x47d01f0f }, \ + { RT2860_AUTO_RSP_CFG, 0x00000013 }, \ + { RT2860_CCK_PROT_CFG, 0x05740003 }, \ + { RT2860_OFDM_PROT_CFG, 0x05740003 }, \ + { RT2860_PBF_CFG, 0x00f40006 }, \ + { RT2860_WPDMA_GLO_CFG, 0x00000030 }, \ + { RT2860_GF20_PROT_CFG, 0x01744004 }, \ + { RT2860_GF40_PROT_CFG, 0x03f44084 }, \ + { RT2860_MM20_PROT_CFG, 0x01744004 }, \ + { RT2860_MM40_PROT_CFG, 0x03f44084 }, \ + { RT2860_TXOP_CTRL_CFG, 0x0000583f }, \ + { RT2860_TXOP_HLDR_ET, 0x00000002 }, \ + { RT2860_TX_RTS_CFG, 0x00092b20 }, \ + { RT2860_EXP_ACK_TIME, 0x002400ca }, \ + { RT2860_XIFS_TIME_CFG, 0x33a41010 }, \ + { RT2860_PWR_PIN_CFG, 0x00000003 } + +/* + * Default values for BBP registers; values taken from the reference driver. + */ +#define RT2860_DEF_BBP \ + { 65, 0x2c }, \ + { 66, 0x38 }, \ + { 69, 0x12 }, \ + { 70, 0x0a }, \ + { 73, 0x10 }, \ + { 81, 0x37 }, \ + { 82, 0x62 }, \ + { 83, 0x6a }, \ + { 84, 0x99 }, \ + { 86, 0x00 }, \ + { 91, 0x04 }, \ + { 92, 0x00 }, \ + { 103, 0x00 }, \ + { 105, 0x05 }, \ + { 106, 0x35 } + +/* + * Default settings for RF registers; values derived from the reference driver. + */ +#define RT2860_RF2850 \ + { 1, 0x100bb3, 0x1301e1, 0x05a014, 0x001402 }, \ + { 2, 0x100bb3, 0x1301e1, 0x05a014, 0x001407 }, \ + { 3, 0x100bb3, 0x1301e2, 0x05a014, 0x001402 }, \ + { 4, 0x100bb3, 0x1301e2, 0x05a014, 0x001407 }, \ + { 5, 0x100bb3, 0x1301e3, 0x05a014, 0x001402 }, \ + { 6, 0x100bb3, 0x1301e3, 0x05a014, 0x001407 }, \ + { 7, 0x100bb3, 0x1301e4, 0x05a014, 0x001402 }, \ + { 8, 0x100bb3, 0x1301e4, 0x05a014, 0x001407 }, \ + { 9, 0x100bb3, 0x1301e5, 0x05a014, 0x001402 }, \ + { 10, 0x100bb3, 0x1301e5, 0x05a014, 0x001407 }, \ + { 11, 0x100bb3, 0x1301e6, 0x05a014, 0x001402 }, \ + { 12, 0x100bb3, 0x1301e6, 0x05a014, 0x001407 }, \ + { 13, 0x100bb3, 0x1301e7, 0x05a014, 0x001402 }, \ + { 14, 0x100bb3, 0x1301e8, 0x05a014, 0x001404 }, \ + { 36, 0x100bb3, 0x130266, 0x056014, 0x001408 }, \ + { 38, 0x100bb3, 0x130267, 0x056014, 0x001404 }, \ + { 40, 0x100bb2, 0x1301a0, 0x056014, 0x001400 }, \ + { 44, 0x100bb2, 0x1301a0, 0x056014, 0x001408 }, \ + { 46, 0x100bb2, 0x1301a1, 0x056014, 0x001402 }, \ + { 48, 0x100bb2, 0x1301a1, 0x056014, 0x001406 }, \ + { 52, 0x100bb2, 0x1301a2, 0x056014, 0x001404 }, \ + { 54, 0x100bb2, 0x1301a2, 0x056014, 0x001408 }, \ + { 56, 0x100bb2, 0x1301a3, 0x056014, 0x001402 }, \ + { 60, 0x100bb2, 0x1301a4, 0x056014, 0x001400 }, \ + { 62, 0x100bb2, 0x1301a4, 0x056014, 0x001404 }, \ + { 64, 0x100bb2, 0x1301a4, 0x056014, 0x001408 }, \ + { 100, 0x100bb2, 0x1301ac, 0x05e014, 0x001400 }, \ + { 102, 0x100bb2, 0x1701ac, 0x15e014, 0x001404 }, \ + { 104, 0x100bb2, 0x1701ac, 0x15e014, 0x001408 }, \ + { 108, 0x100bb3, 0x17028c, 0x15e014, 0x001404 }, \ + { 110, 0x100bb3, 0x13028d, 0x05e014, 0x001400 }, \ + { 112, 0x100bb3, 0x13028d, 0x05e014, 0x001406 }, \ + { 116, 0x100bb3, 0x13028e, 0x05e014, 0x001408 }, \ + { 118, 0x100bb3, 0x13028f, 0x05e014, 0x001404 }, \ + { 120, 0x100bb1, 0x1300e0, 0x05e014, 0x001400 }, \ + { 124, 0x100bb1, 0x1300e0, 0x05e014, 0x001404 }, \ + { 126, 0x100bb1, 0x1300e0, 0x05e014, 0x001406 }, \ + { 128, 0x100bb1, 0x1300e0, 0x05e014, 0x001408 }, \ + { 132, 0x100bb1, 0x1300e1, 0x05e014, 0x001402 }, \ + { 134, 0x100bb1, 0x1300e1, 0x05e014, 0x001404 }, \ + { 136, 0x100bb1, 0x1300e1, 0x05e014, 0x001406 }, \ + { 140, 0x100bb1, 0x1300e2, 0x05e014, 0x001400 }, \ + { 149, 0x100bb1, 0x1300e2, 0x05e014, 0x001409 }, \ + { 151, 0x100bb1, 0x1300e3, 0x05e014, 0x001401 }, \ + { 153, 0x100bb1, 0x1300e3, 0x05e014, 0x001403 }, \ + { 157, 0x100bb1, 0x1300e3, 0x05e014, 0x001407 }, \ + { 159, 0x100bb1, 0x1300e3, 0x05e014, 0x001409 }, \ + { 161, 0x100bb1, 0x1300e4, 0x05e014, 0x001401 }, \ + { 165, 0x100bb1, 0x1300e4, 0x05e014, 0x001405 }, \ + { 167, 0x100bb1, 0x1300f4, 0x05e014, 0x001407 }, \ + { 169, 0x100bb1, 0x1300f4, 0x05e014, 0x001409 }, \ + { 171, 0x100bb1, 0x1300f5, 0x05e014, 0x001401 }, \ + { 173, 0x100bb1, 0x1300f5, 0x05e014, 0x001403 } + +#define RT3070_RF3052 \ + { 0xf1, 2, 2 }, \ + { 0xf1, 2, 7 }, \ + { 0xf2, 2, 2 }, \ + { 0xf2, 2, 7 }, \ + { 0xf3, 2, 2 }, \ + { 0xf3, 2, 7 }, \ + { 0xf4, 2, 2 }, \ + { 0xf4, 2, 7 }, \ + { 0xf5, 2, 2 }, \ + { 0xf5, 2, 7 }, \ + { 0xf6, 2, 2 }, \ + { 0xf6, 2, 7 }, \ + { 0xf7, 2, 2 }, \ + { 0xf8, 2, 4 }, \ + { 0x56, 0, 4 }, \ + { 0x56, 0, 6 }, \ + { 0x56, 0, 8 }, \ + { 0x57, 0, 0 }, \ + { 0x57, 0, 2 }, \ + { 0x57, 0, 4 }, \ + { 0x57, 0, 8 }, \ + { 0x57, 0, 10 }, \ + { 0x58, 0, 0 }, \ + { 0x58, 0, 4 }, \ + { 0x58, 0, 6 }, \ + { 0x58, 0, 8 }, \ + { 0x5b, 0, 8 }, \ + { 0x5b, 0, 10 }, \ + { 0x5c, 0, 0 }, \ + { 0x5c, 0, 4 }, \ + { 0x5c, 0, 6 }, \ + { 0x5c, 0, 8 }, \ + { 0x5d, 0, 0 }, \ + { 0x5d, 0, 2 }, \ + { 0x5d, 0, 4 }, \ + { 0x5d, 0, 8 }, \ + { 0x5d, 0, 10 }, \ + { 0x5e, 0, 0 }, \ + { 0x5e, 0, 4 }, \ + { 0x5e, 0, 6 }, \ + { 0x5e, 0, 8 }, \ + { 0x5f, 0, 0 }, \ + { 0x5f, 0, 9 }, \ + { 0x5f, 0, 11 }, \ + { 0x60, 0, 1 }, \ + { 0x60, 0, 5 }, \ + { 0x60, 0, 7 }, \ + { 0x60, 0, 9 }, \ + { 0x61, 0, 1 }, \ + { 0x61, 0, 3 }, \ + { 0x61, 0, 5 }, \ + { 0x61, 0, 7 }, \ + { 0x61, 0, 9 } + +#define RT3070_DEF_RF \ + { 4, 0x40 }, \ + { 5, 0x03 }, \ + { 6, 0x02 }, \ + { 7, 0x70 }, \ + { 9, 0x0f }, \ + { 10, 0x41 }, \ + { 11, 0x21 }, \ + { 12, 0x7b }, \ + { 14, 0x90 }, \ + { 15, 0x58 }, \ + { 16, 0xb3 }, \ + { 17, 0x92 }, \ + { 18, 0x2c }, \ + { 19, 0x02 }, \ + { 20, 0xba }, \ + { 21, 0xdb }, \ + { 24, 0x16 }, \ + { 25, 0x01 }, \ + { 29, 0x1f } + +#define RT3572_DEF_RF \ + { 0, 0x70 }, \ + { 1, 0x81 }, \ + { 2, 0xf1 }, \ + { 3, 0x02 }, \ + { 4, 0x4c }, \ + { 5, 0x05 }, \ + { 6, 0x4a }, \ + { 7, 0xd8 }, \ + { 9, 0xc3 }, \ + { 10, 0xf1 }, \ + { 11, 0xb9 }, \ + { 12, 0x70 }, \ + { 13, 0x65 }, \ + { 14, 0xa0 }, \ + { 15, 0x53 }, \ + { 16, 0x4c }, \ + { 17, 0x23 }, \ + { 18, 0xac }, \ + { 19, 0x93 }, \ + { 20, 0xb3 }, \ + { 21, 0xd0 }, \ + { 22, 0x00 }, \ + { 23, 0x3c }, \ + { 24, 0x16 }, \ + { 25, 0x15 }, \ + { 26, 0x85 }, \ + { 27, 0x00 }, \ + { 28, 0x00 }, \ + { 29, 0x9b }, \ + { 30, 0x09 }, \ + { 31, 0x10 } + + +union run_stats { + uint32_t raw; + struct { + uint16_t fail; + uint16_t pad; + } error; + struct { + uint16_t success; + uint16_t retry; + } tx; +} __aligned(4); + +#endif /* _IF_RUNREG_H_ */ diff --git a/sys/bus/u4b/wlan/if_runvar.h b/sys/bus/u4b/wlan/if_runvar.h new file mode 100644 index 0000000000..8ee3679437 --- /dev/null +++ b/sys/bus/u4b/wlan/if_runvar.h @@ -0,0 +1,262 @@ +/* $OpenBSD: if_runvar.h,v 1.3 2009/03/26 20:17:27 damien Exp $ */ + +/*- + * Copyright (c) 2008,2009 Damien Bergamini + * ported to FreeBSD by Akinori Furukoshi + * USB Consulting, Hans Petter Selasky + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $FreeBSD$ + */ + +#ifndef _IF_RUNVAR_H_ +#define _IF_RUNVAR_H_ + +#define RUN_MAX_RXSZ \ + MIN(4096, MJUMPAGESIZE) +#if 0 + (sizeof (uint32_t) + \ + sizeof (struct rt2860_rxwi) + \ + sizeof (uint16_t) + \ + MCLBYTES + \ + sizeof (struct rt2870_rxd)) +#endif +/* NB: "11" is the maximum number of padding bytes needed for Tx */ +#define RUN_MAX_TXSZ \ + (sizeof (struct rt2870_txd) + \ + sizeof (struct rt2860_rxwi) + \ + MCLBYTES + 11) + +#define RUN_TX_TIMEOUT 5000 /* ms */ + +/* Tx ring count was 8/endpoint, now 32 for all 4 (or 6) endpoints. */ +#define RUN_TX_RING_COUNT 32 +#define RUN_RX_RING_COUNT 1 + +#define RT2870_WCID_MAX 64 +#define RUN_AID2WCID(aid) ((aid) & 0xff) + +#define RUN_VAP_MAX 8 + +struct run_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint8_t wr_rate; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + int8_t wr_dbm_antsignal; + uint8_t wr_antenna; + uint8_t wr_antsignal; +} __packed; + +#define RUN_RX_RADIOTAP_PRESENT \ + (1 << IEEE80211_RADIOTAP_FLAGS | \ + 1 << IEEE80211_RADIOTAP_RATE | \ + 1 << IEEE80211_RADIOTAP_CHANNEL | \ + 1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL | \ + 1 << IEEE80211_RADIOTAP_ANTENNA | \ + 1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL) + +struct run_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint8_t wt_rate; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; + uint8_t wt_hwqueue; +} __packed; + +#define IEEE80211_RADIOTAP_HWQUEUE 15 + +#define RUN_TX_RADIOTAP_PRESENT \ + (1 << IEEE80211_RADIOTAP_FLAGS | \ + 1 << IEEE80211_RADIOTAP_RATE | \ + 1 << IEEE80211_RADIOTAP_CHANNEL | \ + 1 << IEEE80211_RADIOTAP_HWQUEUE) + +struct run_softc; + +struct run_tx_data { + STAILQ_ENTRY(run_tx_data) next; + struct run_softc *sc; + struct mbuf *m; + struct ieee80211_node *ni; + uint32_t align[0]; /* dummy field */ + uint8_t desc[sizeof(struct rt2870_txd) + + sizeof(struct rt2860_txwi)]; + uint8_t ridx; +}; +STAILQ_HEAD(run_tx_data_head, run_tx_data); + +struct run_node { + struct ieee80211_node ni; + uint8_t ridx[IEEE80211_RATE_MAXSIZE]; + uint8_t ctl_ridx[IEEE80211_RATE_MAXSIZE]; + uint8_t amrr_ridx; + uint8_t mgt_ridx; + uint8_t fix_ridx; +}; + +struct run_cmdq { + void *arg0; + void *arg1; + void (*func)(void *); + struct ieee80211_key *k; + struct ieee80211_key key; + uint8_t mac[IEEE80211_ADDR_LEN]; + uint8_t wcid; +}; + +struct run_vap { + struct ieee80211vap vap; + struct ieee80211_beacon_offsets bo; + struct mbuf *beacon_mbuf; + + int (*newstate)(struct ieee80211vap *, + enum ieee80211_state, int); + + uint8_t rvp_id; +}; +#define RUN_VAP(vap) ((struct run_vap *)(vap)) + +/* + * There are 7 bulk endpoints: 1 for RX + * and 6 for TX (4 EDCAs + HCCA + Prio). + * Update 03-14-2009: some devices like the Planex GW-US300MiniS + * seem to have only 4 TX bulk endpoints (Fukaumi Naoki). + */ +enum { + RUN_BULK_TX_BE, /* = WME_AC_BE */ + RUN_BULK_TX_BK, /* = WME_AC_BK */ + RUN_BULK_TX_VI, /* = WME_AC_VI */ + RUN_BULK_TX_VO, /* = WME_AC_VO */ + RUN_BULK_TX_HCCA, + RUN_BULK_TX_PRIO, + RUN_BULK_RX, + RUN_N_XFER, +}; + +#define RUN_EP_QUEUES RUN_BULK_RX + +struct run_endpoint_queue { + struct run_tx_data tx_data[RUN_TX_RING_COUNT]; + struct run_tx_data_head tx_qh; + struct run_tx_data_head tx_fh; + uint32_t tx_nfree; +}; + +struct run_softc { + device_t sc_dev; + struct usb_device *sc_udev; + struct ifnet *sc_ifp; + uint16_t wcid_stats[RT2870_WCID_MAX + 1][3]; +#define RUN_TXCNT 0 +#define RUN_SUCCESS 1 +#define RUN_RETRY 2 + + int (*sc_srom_read)(struct run_softc *, + uint16_t, uint16_t *); + + uint16_t mac_ver; + uint16_t mac_rev; + uint8_t rf_rev; + uint8_t freq; + uint8_t ntxchains; + uint8_t nrxchains; + + uint8_t bbp25; + uint8_t bbp26; + uint8_t rf24_20mhz; + uint8_t rf24_40mhz; + uint8_t patch_dac; + uint8_t rfswitch; + uint8_t ext_2ghz_lna; + uint8_t ext_5ghz_lna; + uint8_t calib_2ghz; + uint8_t calib_5ghz; + uint8_t txmixgain_2ghz; + uint8_t txmixgain_5ghz; + int8_t txpow1[54]; + int8_t txpow2[54]; + int8_t rssi_2ghz[3]; + int8_t rssi_5ghz[3]; + uint8_t lna[4]; + + struct { + uint8_t reg; + uint8_t val; + } bbp[10], rf[10]; + uint8_t leds; + uint16_t led[3]; + uint32_t txpow20mhz[5]; + uint32_t txpow40mhz_2ghz[5]; + uint32_t txpow40mhz_5ghz[5]; + + uint8_t sc_bssid[6]; + + struct mtx sc_mtx; + + struct run_endpoint_queue sc_epq[RUN_EP_QUEUES]; + + struct task ratectl_task; + struct usb_callout ratectl_ch; + uint8_t ratectl_run; +#define RUN_RATECTL_OFF 0 + +/* need to be power of 2, otherwise RUN_CMDQ_GET fails */ +#define RUN_CMDQ_MAX 16 +#define RUN_CMDQ_MASQ (RUN_CMDQ_MAX - 1) + struct run_cmdq cmdq[RUN_CMDQ_MAX]; + struct task cmdq_task; + uint32_t cmdq_store; + uint8_t cmdq_exec; + uint8_t cmdq_run; + uint8_t cmdq_key_set; +#define RUN_CMDQ_ABORT 0 +#define RUN_CMDQ_GO 1 + + struct usb_xfer *sc_xfer[RUN_N_XFER]; + + struct mbuf *rx_m; + + uint8_t fifo_cnt; + + uint8_t running; + uint8_t runbmap; + uint8_t ap_running; + uint8_t adhoc_running; + uint8_t sta_running; + uint8_t rvp_cnt; + uint8_t rvp_bmap; + + union { + struct run_rx_radiotap_header th; + uint8_t pad[64]; + } sc_rxtapu; +#define sc_rxtap sc_rxtapu.th + int sc_rxtap_len; + + union { + struct run_tx_radiotap_header th; + uint8_t pad[64]; + } sc_txtapu; +#define sc_txtap sc_txtapu.th + int sc_txtap_len; +}; + +#define RUN_LOCK(sc) mtx_lock(&(sc)->sc_mtx) +#define RUN_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) +#define RUN_LOCK_ASSERT(sc, t) mtx_assert(&(sc)->sc_mtx, t) + +#endif /* _IF_RUNVAR_H_ */ diff --git a/sys/bus/u4b/wlan/if_uath.c b/sys/bus/u4b/wlan/if_uath.c new file mode 100644 index 0000000000..cfa8494446 --- /dev/null +++ b/sys/bus/u4b/wlan/if_uath.c @@ -0,0 +1,2900 @@ +/*- + * Copyright (c) 2006 Sam Leffler, Errno Consulting + * Copyright (c) 2008-2009 Weongyo Jeong + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + */ + +/* + * This driver is distantly derived from a driver of the same name + * by Damien Bergamini. The original copyright is included below: + * + * Copyright (c) 2006 + * Damien Bergamini + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Driver for Atheros AR5523 USB parts. + * + * The driver requires firmware to be loaded into the device. This + * is done on device discovery from a user application (uathload) + * that is launched by devd when a device with suitable product ID + * is recognized. Once firmware has been loaded the device will + * reset the USB port and re-attach with the original product ID+1 + * and this driver will be attached. The firmware is licensed for + * general use (royalty free) and may be incorporated in products. + * Note that the firmware normally packaged with the NDIS drivers + * for these devices does not work in this way and so does not work + * with this driver. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef INET +#include +#include +#include +#include +#include +#endif + +#include +#include +#include + +#include +#include +#include "usbdevs.h" + +#include +#include + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uath, CTLFLAG_RW, 0, "USB Atheros"); + +static int uath_countrycode = CTRY_DEFAULT; /* country code */ +SYSCTL_INT(_hw_usb_uath, OID_AUTO, countrycode, CTLFLAG_RW, &uath_countrycode, + 0, "country code"); +TUNABLE_INT("hw.usb.uath.countrycode", &uath_countrycode); +static int uath_regdomain = 0; /* regulatory domain */ +SYSCTL_INT(_hw_usb_uath, OID_AUTO, regdomain, CTLFLAG_RD, &uath_regdomain, + 0, "regulatory domain"); + +#ifdef UATH_DEBUG +int uath_debug = 0; +SYSCTL_INT(_hw_usb_uath, OID_AUTO, debug, CTLFLAG_RW, &uath_debug, 0, + "uath debug level"); +TUNABLE_INT("hw.usb.uath.debug", &uath_debug); +enum { + UATH_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ + UATH_DEBUG_XMIT_DUMP = 0x00000002, /* xmit dump */ + UATH_DEBUG_RECV = 0x00000004, /* basic recv operation */ + UATH_DEBUG_TX_PROC = 0x00000008, /* tx ISR proc */ + UATH_DEBUG_RX_PROC = 0x00000010, /* rx ISR proc */ + UATH_DEBUG_RECV_ALL = 0x00000020, /* trace all frames (beacons) */ + UATH_DEBUG_INIT = 0x00000040, /* initialization of dev */ + UATH_DEBUG_DEVCAP = 0x00000080, /* dev caps */ + UATH_DEBUG_CMDS = 0x00000100, /* commands */ + UATH_DEBUG_CMDS_DUMP = 0x00000200, /* command buffer dump */ + UATH_DEBUG_RESET = 0x00000400, /* reset processing */ + UATH_DEBUG_STATE = 0x00000800, /* 802.11 state transitions */ + UATH_DEBUG_MULTICAST = 0x00001000, /* multicast */ + UATH_DEBUG_WME = 0x00002000, /* WME */ + UATH_DEBUG_CHANNEL = 0x00004000, /* channel */ + UATH_DEBUG_RATES = 0x00008000, /* rates */ + UATH_DEBUG_CRYPTO = 0x00010000, /* crypto */ + UATH_DEBUG_LED = 0x00020000, /* LED */ + UATH_DEBUG_ANY = 0xffffffff +}; +#define DPRINTF(sc, m, fmt, ...) do { \ + if (sc->sc_debug & (m)) \ + printf(fmt, __VA_ARGS__); \ +} while (0) +#else +#define DPRINTF(sc, m, fmt, ...) do { \ + (void) sc; \ +} while (0) +#endif + +/* unaligned little endian access */ +#define LE_READ_2(p) \ + ((u_int16_t) \ + ((((u_int8_t *)(p))[0] ) | (((u_int8_t *)(p))[1] << 8))) +#define LE_READ_4(p) \ + ((u_int32_t) \ + ((((u_int8_t *)(p))[0] ) | (((u_int8_t *)(p))[1] << 8) | \ + (((u_int8_t *)(p))[2] << 16) | (((u_int8_t *)(p))[3] << 24))) + +/* recognized device vendors/products */ +static const STRUCT_USB_HOST_ID uath_devs[] = { +#define UATH_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } + UATH_DEV(ACCTON, SMCWUSBTG2), + UATH_DEV(ATHEROS, AR5523), + UATH_DEV(ATHEROS2, AR5523_1), + UATH_DEV(ATHEROS2, AR5523_2), + UATH_DEV(ATHEROS2, AR5523_3), + UATH_DEV(CONCEPTRONIC, AR5523_1), + UATH_DEV(CONCEPTRONIC, AR5523_2), + UATH_DEV(DLINK, DWLAG122), + UATH_DEV(DLINK, DWLAG132), + UATH_DEV(DLINK, DWLG132), + UATH_DEV(DLINK2, DWA120), + UATH_DEV(GIGASET, AR5523), + UATH_DEV(GIGASET, SMCWUSBTG), + UATH_DEV(GLOBALSUN, AR5523_1), + UATH_DEV(GLOBALSUN, AR5523_2), + UATH_DEV(NETGEAR, WG111U), + UATH_DEV(NETGEAR3, WG111T), + UATH_DEV(NETGEAR3, WPN111), + UATH_DEV(NETGEAR3, WPN111_2), + UATH_DEV(UMEDIA, TEW444UBEU), + UATH_DEV(UMEDIA, AR5523_2), + UATH_DEV(WISTRONNEWEB, AR5523_1), + UATH_DEV(WISTRONNEWEB, AR5523_2), + UATH_DEV(ZCOM, AR5523) +#undef UATH_DEV +}; + +static usb_callback_t uath_intr_rx_callback; +static usb_callback_t uath_intr_tx_callback; +static usb_callback_t uath_bulk_rx_callback; +static usb_callback_t uath_bulk_tx_callback; + +static const struct usb_config uath_usbconfig[UATH_N_XFERS] = { + [UATH_INTR_RX] = { + .type = UE_BULK, + .endpoint = 0x1, + .direction = UE_DIR_IN, + .bufsize = UATH_MAX_CMDSZ, + .flags = { + .pipe_bof = 1, + .short_xfer_ok = 1 + }, + .callback = uath_intr_rx_callback + }, + [UATH_INTR_TX] = { + .type = UE_BULK, + .endpoint = 0x1, + .direction = UE_DIR_OUT, + .bufsize = UATH_MAX_CMDSZ, + .flags = { + .ext_buffer = 1, + .force_short_xfer = 1, + .pipe_bof = 1, + }, + .callback = uath_intr_tx_callback, + .timeout = UATH_CMD_TIMEOUT + }, + [UATH_BULK_RX] = { + .type = UE_BULK, + .endpoint = 0x2, + .direction = UE_DIR_IN, + .bufsize = MCLBYTES, + .flags = { + .ext_buffer = 1, + .pipe_bof = 1, + .short_xfer_ok = 1 + }, + .callback = uath_bulk_rx_callback + }, + [UATH_BULK_TX] = { + .type = UE_BULK, + .endpoint = 0x2, + .direction = UE_DIR_OUT, + .bufsize = UATH_MAX_TXBUFSZ, + .flags = { + .ext_buffer = 1, + .force_short_xfer = 1, + .pipe_bof = 1 + }, + .callback = uath_bulk_tx_callback, + .timeout = UATH_DATA_TIMEOUT + } +}; + +static struct ieee80211vap *uath_vap_create(struct ieee80211com *, + const char [IFNAMSIZ], int, enum ieee80211_opmode, int, + const uint8_t [IEEE80211_ADDR_LEN], + const uint8_t [IEEE80211_ADDR_LEN]); +static void uath_vap_delete(struct ieee80211vap *); +static int uath_alloc_cmd_list(struct uath_softc *, struct uath_cmd [], + int, int); +static void uath_free_cmd_list(struct uath_softc *, struct uath_cmd [], + int); +static int uath_host_available(struct uath_softc *); +static int uath_get_capability(struct uath_softc *, uint32_t, uint32_t *); +static int uath_get_devcap(struct uath_softc *); +static struct uath_cmd * + uath_get_cmdbuf(struct uath_softc *); +static int uath_cmd_read(struct uath_softc *, uint32_t, const void *, + int, void *, int, int); +static int uath_cmd_write(struct uath_softc *, uint32_t, const void *, + int, int); +static void uath_stat(void *); +#ifdef UATH_DEBUG +static void uath_dump_cmd(const uint8_t *, int, char); +static const char * + uath_codename(int); +#endif +static int uath_get_devstatus(struct uath_softc *, + uint8_t macaddr[IEEE80211_ADDR_LEN]); +static int uath_get_status(struct uath_softc *, uint32_t, void *, int); +static int uath_alloc_rx_data_list(struct uath_softc *); +static int uath_alloc_tx_data_list(struct uath_softc *); +static void uath_free_rx_data_list(struct uath_softc *); +static void uath_free_tx_data_list(struct uath_softc *); +static int uath_init_locked(void *); +static void uath_init(void *); +static void uath_stop_locked(struct ifnet *); +static void uath_stop(struct ifnet *); +static int uath_ioctl(struct ifnet *, u_long, caddr_t); +static void uath_start(struct ifnet *); +static int uath_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static void uath_scan_start(struct ieee80211com *); +static void uath_scan_end(struct ieee80211com *); +static void uath_set_channel(struct ieee80211com *); +static void uath_update_mcast(struct ifnet *); +static void uath_update_promisc(struct ifnet *); +static int uath_config(struct uath_softc *, uint32_t, uint32_t); +static int uath_config_multi(struct uath_softc *, uint32_t, const void *, + int); +static int uath_switch_channel(struct uath_softc *, + struct ieee80211_channel *); +static int uath_set_rxfilter(struct uath_softc *, uint32_t, uint32_t); +static void uath_watchdog(void *); +static void uath_abort_xfers(struct uath_softc *); +static int uath_dataflush(struct uath_softc *); +static int uath_cmdflush(struct uath_softc *); +static int uath_flush(struct uath_softc *); +static int uath_set_ledstate(struct uath_softc *, int); +static int uath_set_chan(struct uath_softc *, struct ieee80211_channel *); +static int uath_reset_tx_queues(struct uath_softc *); +static int uath_wme_init(struct uath_softc *); +static struct uath_data * + uath_getbuf(struct uath_softc *); +static int uath_newstate(struct ieee80211vap *, enum ieee80211_state, + int); +static int uath_set_key(struct uath_softc *, + const struct ieee80211_key *, int); +static int uath_set_keys(struct uath_softc *, struct ieee80211vap *); +static void uath_sysctl_node(struct uath_softc *); + +static int +uath_match(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != UATH_CONFIG_INDEX) + return (ENXIO); + if (uaa->info.bIfaceIndex != UATH_IFACE_INDEX) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(uath_devs, sizeof(uath_devs), uaa)); +} + +static int +uath_attach(device_t dev) +{ + struct uath_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ieee80211com *ic; + struct ifnet *ifp; + uint8_t bands, iface_index = UATH_IFACE_INDEX; /* XXX */ + usb_error_t error; + uint8_t macaddr[IEEE80211_ADDR_LEN]; + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; +#ifdef UATH_DEBUG + sc->sc_debug = uath_debug; +#endif + device_set_usb_desc(dev); + + /* + * Only post-firmware devices here. + */ + mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK, + MTX_DEF); + callout_init(&sc->stat_ch, 0); + callout_init_mtx(&sc->watchdog_ch, &sc->sc_mtx, 0); + + /* + * Allocate xfers for firmware commands. + */ + error = uath_alloc_cmd_list(sc, sc->sc_cmd, UATH_CMD_LIST_COUNT, + UATH_MAX_CMDSZ); + if (error != 0) { + device_printf(sc->sc_dev, + "could not allocate Tx command list\n"); + goto fail; + } + + error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, + uath_usbconfig, UATH_N_XFERS, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "could not allocate USB transfers, " + "err=%s\n", usbd_errstr(error)); + goto fail1; + } + + /* + * We're now ready to send+receive firmware commands. + */ + UATH_LOCK(sc); + error = uath_host_available(sc); + if (error != 0) { + device_printf(sc->sc_dev, "could not initialize adapter\n"); + goto fail3; + } + error = uath_get_devcap(sc); + if (error != 0) { + device_printf(sc->sc_dev, + "could not get device capabilities\n"); + goto fail3; + } + UATH_UNLOCK(sc); + + /* Create device sysctl node. */ + uath_sysctl_node(sc); + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(sc->sc_dev, "can not allocate ifnet\n"); + error = ENXIO; + goto fail2; + } + + UATH_LOCK(sc); + error = uath_get_devstatus(sc, macaddr); + if (error != 0) { + device_printf(sc->sc_dev, "could not get device status\n"); + goto fail4; + } + + /* + * Allocate xfers for Rx/Tx data pipes. + */ + error = uath_alloc_rx_data_list(sc); + if (error != 0) { + device_printf(sc->sc_dev, "could not allocate Rx data list\n"); + goto fail4; + } + error = uath_alloc_tx_data_list(sc); + if (error != 0) { + device_printf(sc->sc_dev, "could not allocate Tx data list\n"); + goto fail4; + } + UATH_UNLOCK(sc); + + ifp->if_softc = sc; + if_initname(ifp, "uath", device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_init = uath_init; + ifp->if_ioctl = uath_ioctl; + ifp->if_start = uath_start; + /* XXX UATH_TX_DATA_LIST_COUNT */ + IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); + ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; + IFQ_SET_READY(&ifp->if_snd); + + ic = ifp->if_l2com; + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */ + + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA | /* station mode */ + IEEE80211_C_MONITOR | /* monitor mode supported */ + IEEE80211_C_TXPMGT | /* tx power management */ + IEEE80211_C_SHPREAMBLE | /* short preamble supported */ + IEEE80211_C_SHSLOT | /* short slot time supported */ + IEEE80211_C_WPA | /* 802.11i */ + IEEE80211_C_BGSCAN | /* capable of bg scanning */ + IEEE80211_C_TXFRAG; /* handle tx frags */ + + /* put a regulatory domain to reveal informations. */ + uath_regdomain = sc->sc_devcap.regDomain; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + if ((sc->sc_devcap.analog5GhzRevision & 0xf0) == 0x30) + setbit(&bands, IEEE80211_MODE_11A); + /* XXX turbo */ + ieee80211_init_channels(ic, NULL, &bands); + + ieee80211_ifattach(ic, macaddr); + ic->ic_raw_xmit = uath_raw_xmit; + ic->ic_scan_start = uath_scan_start; + ic->ic_scan_end = uath_scan_end; + ic->ic_set_channel = uath_set_channel; + + ic->ic_vap_create = uath_vap_create; + ic->ic_vap_delete = uath_vap_delete; + ic->ic_update_mcast = uath_update_mcast; + ic->ic_update_promisc = uath_update_promisc; + + ieee80211_radiotap_attach(ic, + &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), + UATH_TX_RADIOTAP_PRESENT, + &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), + UATH_RX_RADIOTAP_PRESENT); + + if (bootverbose) + ieee80211_announce(ic); + + return (0); + +fail4: if_free(ifp); +fail3: UATH_UNLOCK(sc); +fail2: usbd_transfer_unsetup(sc->sc_xfer, UATH_N_XFERS); +fail1: uath_free_cmd_list(sc, sc->sc_cmd, UATH_CMD_LIST_COUNT); +fail: + return (error); +} + +static int +uath_detach(device_t dev) +{ + struct uath_softc *sc = device_get_softc(dev); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + if (!device_is_attached(dev)) + return (0); + + UATH_LOCK(sc); + sc->sc_flags |= UATH_FLAG_INVALID; + UATH_UNLOCK(sc); + + ieee80211_ifdetach(ic); + uath_stop(ifp); + + callout_drain(&sc->stat_ch); + callout_drain(&sc->watchdog_ch); + + usbd_transfer_unsetup(sc->sc_xfer, UATH_N_XFERS); + + /* free buffers */ + UATH_LOCK(sc); + uath_free_rx_data_list(sc); + uath_free_tx_data_list(sc); + uath_free_cmd_list(sc, sc->sc_cmd, UATH_CMD_LIST_COUNT); + UATH_UNLOCK(sc); + + if_free(ifp); + mtx_destroy(&sc->sc_mtx); + return (0); +} + +static void +uath_free_cmd_list(struct uath_softc *sc, struct uath_cmd cmds[], int ncmd) +{ + int i; + + for (i = 0; i < ncmd; i++) + if (cmds[i].buf != NULL) + free(cmds[i].buf, M_USBDEV); +} + +static int +uath_alloc_cmd_list(struct uath_softc *sc, struct uath_cmd cmds[], + int ncmd, int maxsz) +{ + int i, error; + + STAILQ_INIT(&sc->sc_cmd_active); + STAILQ_INIT(&sc->sc_cmd_pending); + STAILQ_INIT(&sc->sc_cmd_waiting); + STAILQ_INIT(&sc->sc_cmd_inactive); + + for (i = 0; i < ncmd; i++) { + struct uath_cmd *cmd = &cmds[i]; + + cmd->sc = sc; /* backpointer for callbacks */ + cmd->msgid = i; + cmd->buf = malloc(maxsz, M_USBDEV, M_NOWAIT); + if (cmd->buf == NULL) { + device_printf(sc->sc_dev, + "could not allocate xfer buffer\n"); + error = ENOMEM; + goto fail; + } + STAILQ_INSERT_TAIL(&sc->sc_cmd_inactive, cmd, next); + UATH_STAT_INC(sc, st_cmd_inactive); + } + return (0); + +fail: uath_free_cmd_list(sc, cmds, ncmd); + return (error); +} + +static int +uath_host_available(struct uath_softc *sc) +{ + struct uath_cmd_host_available setup; + + UATH_ASSERT_LOCKED(sc); + + /* inform target the host is available */ + setup.sw_ver_major = htobe32(ATH_SW_VER_MAJOR); + setup.sw_ver_minor = htobe32(ATH_SW_VER_MINOR); + setup.sw_ver_patch = htobe32(ATH_SW_VER_PATCH); + setup.sw_ver_build = htobe32(ATH_SW_VER_BUILD); + return uath_cmd_read(sc, WDCMSG_HOST_AVAILABLE, + &setup, sizeof setup, NULL, 0, 0); +} + +#ifdef UATH_DEBUG +static void +uath_dump_cmd(const uint8_t *buf, int len, char prefix) +{ + const char *sep = ""; + int i; + + for (i = 0; i < len; i++) { + if ((i % 16) == 0) { + printf("%s%c ", sep, prefix); + sep = "\n"; + } + else if ((i % 4) == 0) + printf(" "); + printf("%02x", buf[i]); + } + printf("\n"); +} + +static const char * +uath_codename(int code) +{ +#define N(a) (sizeof(a)/sizeof(a[0])) + static const char *names[] = { + "0x00", + "HOST_AVAILABLE", + "BIND", + "TARGET_RESET", + "TARGET_GET_CAPABILITY", + "TARGET_SET_CONFIG", + "TARGET_GET_STATUS", + "TARGET_GET_STATS", + "TARGET_START", + "TARGET_STOP", + "TARGET_ENABLE", + "TARGET_DISABLE", + "CREATE_CONNECTION", + "UPDATE_CONNECT_ATTR", + "DELETE_CONNECT", + "SEND", + "FLUSH", + "STATS_UPDATE", + "BMISS", + "DEVICE_AVAIL", + "SEND_COMPLETE", + "DATA_AVAIL", + "SET_PWR_MODE", + "BMISS_ACK", + "SET_LED_STEADY", + "SET_LED_BLINK", + "SETUP_BEACON_DESC", + "BEACON_INIT", + "RESET_KEY_CACHE", + "RESET_KEY_CACHE_ENTRY", + "SET_KEY_CACHE_ENTRY", + "SET_DECOMP_MASK", + "SET_REGULATORY_DOMAIN", + "SET_LED_STATE", + "WRITE_ASSOCID", + "SET_STA_BEACON_TIMERS", + "GET_TSF", + "RESET_TSF", + "SET_ADHOC_MODE", + "SET_BASIC_RATE", + "MIB_CONTROL", + "GET_CHANNEL_DATA", + "GET_CUR_RSSI", + "SET_ANTENNA_SWITCH", + "0x2c", "0x2d", "0x2e", + "USE_SHORT_SLOT_TIME", + "SET_POWER_MODE", + "SETUP_PSPOLL_DESC", + "SET_RX_MULTICAST_FILTER", + "RX_FILTER", + "PER_CALIBRATION", + "RESET", + "DISABLE", + "PHY_DISABLE", + "SET_TX_POWER_LIMIT", + "SET_TX_QUEUE_PARAMS", + "SETUP_TX_QUEUE", + "RELEASE_TX_QUEUE", + }; + static char buf[8]; + + if (code < N(names)) + return names[code]; + if (code == WDCMSG_SET_DEFAULT_KEY) + return "SET_DEFAULT_KEY"; + snprintf(buf, sizeof(buf), "0x%02x", code); + return buf; +#undef N +} +#endif + +/* + * Low-level function to send read or write commands to the firmware. + */ +static int +uath_cmdsend(struct uath_softc *sc, uint32_t code, const void *idata, int ilen, + void *odata, int olen, int flags) +{ + struct uath_cmd_hdr *hdr; + struct uath_cmd *cmd; + int error; + + UATH_ASSERT_LOCKED(sc); + + /* grab a xfer */ + cmd = uath_get_cmdbuf(sc); + if (cmd == NULL) { + device_printf(sc->sc_dev, "%s: empty inactive queue\n", + __func__); + return (ENOBUFS); + } + cmd->flags = flags; + /* always bulk-out a multiple of 4 bytes */ + cmd->buflen = roundup2(sizeof(struct uath_cmd_hdr) + ilen, 4); + + hdr = (struct uath_cmd_hdr *)cmd->buf; + memset(hdr, 0, sizeof(struct uath_cmd_hdr)); + hdr->len = htobe32(cmd->buflen); + hdr->code = htobe32(code); + hdr->msgid = cmd->msgid; /* don't care about endianness */ + hdr->magic = htobe32((cmd->flags & UATH_CMD_FLAG_MAGIC) ? 1 << 24 : 0); + memcpy((uint8_t *)(hdr + 1), idata, ilen); + +#ifdef UATH_DEBUG + if (sc->sc_debug & UATH_DEBUG_CMDS) { + printf("%s: send %s [flags 0x%x] olen %d\n", + __func__, uath_codename(code), cmd->flags, olen); + if (sc->sc_debug & UATH_DEBUG_CMDS_DUMP) + uath_dump_cmd(cmd->buf, cmd->buflen, '+'); + } +#endif + cmd->odata = odata; + KASSERT(odata == NULL || + olen < UATH_MAX_CMDSZ - sizeof(*hdr) + sizeof(uint32_t), + ("odata %p olen %u", odata, olen)); + cmd->olen = olen; + + STAILQ_INSERT_TAIL(&sc->sc_cmd_pending, cmd, next); + UATH_STAT_INC(sc, st_cmd_pending); + usbd_transfer_start(sc->sc_xfer[UATH_INTR_TX]); + + if (cmd->flags & UATH_CMD_FLAG_READ) { + usbd_transfer_start(sc->sc_xfer[UATH_INTR_RX]); + + /* wait at most two seconds for command reply */ + error = mtx_sleep(cmd, &sc->sc_mtx, 0, "uathcmd", 2 * hz); + cmd->odata = NULL; /* in case reply comes too late */ + if (error != 0) { + device_printf(sc->sc_dev, "timeout waiting for reply " + "to cmd 0x%x (%u)\n", code, code); + } else if (cmd->olen != olen) { + device_printf(sc->sc_dev, "unexpected reply data count " + "to cmd 0x%x (%u), got %u, expected %u\n", + code, code, cmd->olen, olen); + error = EINVAL; + } + return (error); + } + return (0); +} + +static int +uath_cmd_read(struct uath_softc *sc, uint32_t code, const void *idata, + int ilen, void *odata, int olen, int flags) +{ + + flags |= UATH_CMD_FLAG_READ; + return uath_cmdsend(sc, code, idata, ilen, odata, olen, flags); +} + +static int +uath_cmd_write(struct uath_softc *sc, uint32_t code, const void *data, int len, + int flags) +{ + + flags &= ~UATH_CMD_FLAG_READ; + return uath_cmdsend(sc, code, data, len, NULL, 0, flags); +} + +static struct uath_cmd * +uath_get_cmdbuf(struct uath_softc *sc) +{ + struct uath_cmd *uc; + + UATH_ASSERT_LOCKED(sc); + + uc = STAILQ_FIRST(&sc->sc_cmd_inactive); + if (uc != NULL) { + STAILQ_REMOVE_HEAD(&sc->sc_cmd_inactive, next); + UATH_STAT_DEC(sc, st_cmd_inactive); + } else + uc = NULL; + if (uc == NULL) + DPRINTF(sc, UATH_DEBUG_XMIT, "%s: %s\n", __func__, + "out of command xmit buffers"); + return (uc); +} + +/* + * This function is called periodically (every second) when associated to + * query device statistics. + */ +static void +uath_stat(void *arg) +{ + struct uath_softc *sc = arg; + int error; + + UATH_LOCK(sc); + /* + * Send request for statistics asynchronously. The timer will be + * restarted when we'll get the stats notification. + */ + error = uath_cmd_write(sc, WDCMSG_TARGET_GET_STATS, NULL, 0, + UATH_CMD_FLAG_ASYNC); + if (error != 0) { + device_printf(sc->sc_dev, + "could not query stats, error %d\n", error); + } + UATH_UNLOCK(sc); +} + +static int +uath_get_capability(struct uath_softc *sc, uint32_t cap, uint32_t *val) +{ + int error; + + cap = htobe32(cap); + error = uath_cmd_read(sc, WDCMSG_TARGET_GET_CAPABILITY, + &cap, sizeof cap, val, sizeof(uint32_t), UATH_CMD_FLAG_MAGIC); + if (error != 0) { + device_printf(sc->sc_dev, "could not read capability %u\n", + be32toh(cap)); + return (error); + } + *val = be32toh(*val); + return (error); +} + +static int +uath_get_devcap(struct uath_softc *sc) +{ +#define GETCAP(x, v) do { \ + error = uath_get_capability(sc, x, &v); \ + if (error != 0) \ + return (error); \ + DPRINTF(sc, UATH_DEBUG_DEVCAP, \ + "%s: %s=0x%08x\n", __func__, #x, v); \ +} while (0) + struct uath_devcap *cap = &sc->sc_devcap; + int error; + + /* collect device capabilities */ + GETCAP(CAP_TARGET_VERSION, cap->targetVersion); + GETCAP(CAP_TARGET_REVISION, cap->targetRevision); + GETCAP(CAP_MAC_VERSION, cap->macVersion); + GETCAP(CAP_MAC_REVISION, cap->macRevision); + GETCAP(CAP_PHY_REVISION, cap->phyRevision); + GETCAP(CAP_ANALOG_5GHz_REVISION, cap->analog5GhzRevision); + GETCAP(CAP_ANALOG_2GHz_REVISION, cap->analog2GhzRevision); + + GETCAP(CAP_REG_DOMAIN, cap->regDomain); + GETCAP(CAP_REG_CAP_BITS, cap->regCapBits); +#if 0 + /* NB: not supported in rev 1.5 */ + GETCAP(CAP_COUNTRY_CODE, cap->countryCode); +#endif + GETCAP(CAP_WIRELESS_MODES, cap->wirelessModes); + GETCAP(CAP_CHAN_SPREAD_SUPPORT, cap->chanSpreadSupport); + GETCAP(CAP_COMPRESS_SUPPORT, cap->compressSupport); + GETCAP(CAP_BURST_SUPPORT, cap->burstSupport); + GETCAP(CAP_FAST_FRAMES_SUPPORT, cap->fastFramesSupport); + GETCAP(CAP_CHAP_TUNING_SUPPORT, cap->chapTuningSupport); + GETCAP(CAP_TURBOG_SUPPORT, cap->turboGSupport); + GETCAP(CAP_TURBO_PRIME_SUPPORT, cap->turboPrimeSupport); + GETCAP(CAP_DEVICE_TYPE, cap->deviceType); + GETCAP(CAP_WME_SUPPORT, cap->wmeSupport); + GETCAP(CAP_TOTAL_QUEUES, cap->numTxQueues); + GETCAP(CAP_CONNECTION_ID_MAX, cap->connectionIdMax); + + GETCAP(CAP_LOW_5GHZ_CHAN, cap->low5GhzChan); + GETCAP(CAP_HIGH_5GHZ_CHAN, cap->high5GhzChan); + GETCAP(CAP_LOW_2GHZ_CHAN, cap->low2GhzChan); + GETCAP(CAP_HIGH_2GHZ_CHAN, cap->high2GhzChan); + GETCAP(CAP_TWICE_ANTENNAGAIN_5G, cap->twiceAntennaGain5G); + GETCAP(CAP_TWICE_ANTENNAGAIN_2G, cap->twiceAntennaGain2G); + + GETCAP(CAP_CIPHER_AES_CCM, cap->supportCipherAES_CCM); + GETCAP(CAP_CIPHER_TKIP, cap->supportCipherTKIP); + GETCAP(CAP_MIC_TKIP, cap->supportMicTKIP); + + cap->supportCipherWEP = 1; /* NB: always available */ + + return (0); +} + +static int +uath_get_devstatus(struct uath_softc *sc, uint8_t macaddr[IEEE80211_ADDR_LEN]) +{ + int error; + + /* retrieve MAC address */ + error = uath_get_status(sc, ST_MAC_ADDR, macaddr, IEEE80211_ADDR_LEN); + if (error != 0) { + device_printf(sc->sc_dev, "could not read MAC address\n"); + return (error); + } + + error = uath_get_status(sc, ST_SERIAL_NUMBER, + &sc->sc_serial[0], sizeof(sc->sc_serial)); + if (error != 0) { + device_printf(sc->sc_dev, + "could not read device serial number\n"); + return (error); + } + return (0); +} + +static int +uath_get_status(struct uath_softc *sc, uint32_t which, void *odata, int olen) +{ + int error; + + which = htobe32(which); + error = uath_cmd_read(sc, WDCMSG_TARGET_GET_STATUS, + &which, sizeof(which), odata, olen, UATH_CMD_FLAG_MAGIC); + if (error != 0) + device_printf(sc->sc_dev, + "could not read EEPROM offset 0x%02x\n", be32toh(which)); + return (error); +} + +static void +uath_free_data_list(struct uath_softc *sc, struct uath_data data[], int ndata, + int fillmbuf) +{ + int i; + + for (i = 0; i < ndata; i++) { + struct uath_data *dp = &data[i]; + + if (fillmbuf == 1) { + if (dp->m != NULL) { + m_freem(dp->m); + dp->m = NULL; + dp->buf = NULL; + } + } else { + if (dp->buf != NULL) { + free(dp->buf, M_USBDEV); + dp->buf = NULL; + } + } +#ifdef UATH_DEBUG + if (dp->ni != NULL) + device_printf(sc->sc_dev, "Node isn't NULL\n"); +#endif + } +} + +static int +uath_alloc_data_list(struct uath_softc *sc, struct uath_data data[], + int ndata, int maxsz, int fillmbuf) +{ + int i, error; + + for (i = 0; i < ndata; i++) { + struct uath_data *dp = &data[i]; + + dp->sc = sc; + if (fillmbuf) { + /* XXX check maxsz */ + dp->m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (dp->m == NULL) { + device_printf(sc->sc_dev, + "could not allocate rx mbuf\n"); + error = ENOMEM; + goto fail; + } + dp->buf = mtod(dp->m, uint8_t *); + } else { + dp->m = NULL; + dp->buf = malloc(maxsz, M_USBDEV, M_NOWAIT); + if (dp->buf == NULL) { + device_printf(sc->sc_dev, + "could not allocate buffer\n"); + error = ENOMEM; + goto fail; + } + } + dp->ni = NULL; + } + + return (0); + +fail: uath_free_data_list(sc, data, ndata, fillmbuf); + return (error); +} + +static int +uath_alloc_rx_data_list(struct uath_softc *sc) +{ + int error, i; + + /* XXX is it enough to store the RX packet with MCLBYTES bytes? */ + error = uath_alloc_data_list(sc, + sc->sc_rx, UATH_RX_DATA_LIST_COUNT, MCLBYTES, + 1 /* setup mbufs */); + if (error != 0) + return (error); + + STAILQ_INIT(&sc->sc_rx_active); + STAILQ_INIT(&sc->sc_rx_inactive); + + for (i = 0; i < UATH_RX_DATA_LIST_COUNT; i++) { + STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i], + next); + UATH_STAT_INC(sc, st_rx_inactive); + } + + return (0); +} + +static int +uath_alloc_tx_data_list(struct uath_softc *sc) +{ + int error, i; + + error = uath_alloc_data_list(sc, + sc->sc_tx, UATH_TX_DATA_LIST_COUNT, UATH_MAX_TXBUFSZ, + 0 /* no mbufs */); + if (error != 0) + return (error); + + STAILQ_INIT(&sc->sc_tx_active); + STAILQ_INIT(&sc->sc_tx_inactive); + STAILQ_INIT(&sc->sc_tx_pending); + + for (i = 0; i < UATH_TX_DATA_LIST_COUNT; i++) { + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i], + next); + UATH_STAT_INC(sc, st_tx_inactive); + } + + return (0); +} + +static void +uath_free_rx_data_list(struct uath_softc *sc) +{ + + STAILQ_INIT(&sc->sc_rx_active); + STAILQ_INIT(&sc->sc_rx_inactive); + + uath_free_data_list(sc, sc->sc_rx, UATH_RX_DATA_LIST_COUNT, + 1 /* free mbufs */); +} + +static void +uath_free_tx_data_list(struct uath_softc *sc) +{ + + STAILQ_INIT(&sc->sc_tx_active); + STAILQ_INIT(&sc->sc_tx_inactive); + STAILQ_INIT(&sc->sc_tx_pending); + + uath_free_data_list(sc, sc->sc_tx, UATH_TX_DATA_LIST_COUNT, + 0 /* no mbufs */); +} + +static struct ieee80211vap * +uath_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, + enum ieee80211_opmode opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct uath_vap *uvp; + struct ieee80211vap *vap; + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return (NULL); + uvp = (struct uath_vap *) malloc(sizeof(struct uath_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (uvp == NULL) + return (NULL); + vap = &uvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + uvp->newstate = vap->iv_newstate; + vap->iv_newstate = uath_newstate; + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, + ieee80211_media_status); + ic->ic_opmode = opmode; + return (vap); +} + +static void +uath_vap_delete(struct ieee80211vap *vap) +{ + struct uath_vap *uvp = UATH_VAP(vap); + + ieee80211_vap_detach(vap); + free(uvp, M_80211_VAP); +} + +static int +uath_init_locked(void *arg) +{ + struct uath_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t val; + int error; + + UATH_ASSERT_LOCKED(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + uath_stop_locked(ifp); + + /* reset variables */ + sc->sc_intrx_nextnum = sc->sc_msgid = 0; + + val = htobe32(0); + uath_cmd_write(sc, WDCMSG_BIND, &val, sizeof val, 0); + + /* set MAC address */ + uath_config_multi(sc, CFG_MAC_ADDR, IF_LLADDR(ifp), IEEE80211_ADDR_LEN); + + /* XXX honor net80211 state */ + uath_config(sc, CFG_RATE_CONTROL_ENABLE, 0x00000001); + uath_config(sc, CFG_DIVERSITY_CTL, 0x00000001); + uath_config(sc, CFG_ABOLT, 0x0000003f); + uath_config(sc, CFG_WME_ENABLED, 0x00000001); + + uath_config(sc, CFG_SERVICE_TYPE, 1); + uath_config(sc, CFG_TP_SCALE, 0x00000000); + uath_config(sc, CFG_TPC_HALF_DBM5, 0x0000003c); + uath_config(sc, CFG_TPC_HALF_DBM2, 0x0000003c); + uath_config(sc, CFG_OVERRD_TX_POWER, 0x00000000); + uath_config(sc, CFG_GMODE_PROTECTION, 0x00000000); + uath_config(sc, CFG_GMODE_PROTECT_RATE_INDEX, 0x00000003); + uath_config(sc, CFG_PROTECTION_TYPE, 0x00000000); + uath_config(sc, CFG_MODE_CTS, 0x00000002); + + error = uath_cmd_read(sc, WDCMSG_TARGET_START, NULL, 0, + &val, sizeof(val), UATH_CMD_FLAG_MAGIC); + if (error) { + device_printf(sc->sc_dev, + "could not start target, error %d\n", error); + goto fail; + } + DPRINTF(sc, UATH_DEBUG_INIT, "%s returns handle: 0x%x\n", + uath_codename(WDCMSG_TARGET_START), be32toh(val)); + + /* set default channel */ + error = uath_switch_channel(sc, ic->ic_curchan); + if (error) { + device_printf(sc->sc_dev, + "could not switch channel, error %d\n", error); + goto fail; + } + + val = htobe32(TARGET_DEVICE_AWAKE); + uath_cmd_write(sc, WDCMSG_SET_PWR_MODE, &val, sizeof val, 0); + /* XXX? check */ + uath_cmd_write(sc, WDCMSG_RESET_KEY_CACHE, NULL, 0, 0); + + usbd_transfer_start(sc->sc_xfer[UATH_BULK_RX]); + /* enable Rx */ + uath_set_rxfilter(sc, 0x0, UATH_FILTER_OP_INIT); + uath_set_rxfilter(sc, + UATH_FILTER_RX_UCAST | UATH_FILTER_RX_MCAST | + UATH_FILTER_RX_BCAST | UATH_FILTER_RX_BEACON, + UATH_FILTER_OP_SET); + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + sc->sc_flags |= UATH_FLAG_INITDONE; + + callout_reset(&sc->watchdog_ch, hz, uath_watchdog, sc); + + return (0); + +fail: + uath_stop_locked(ifp); + return (error); +} + +static void +uath_init(void *arg) +{ + struct uath_softc *sc = arg; + + UATH_LOCK(sc); + (void)uath_init_locked(sc); + UATH_UNLOCK(sc); +} + +static void +uath_stop_locked(struct ifnet *ifp) +{ + struct uath_softc *sc = ifp->if_softc; + + UATH_ASSERT_LOCKED(sc); + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + sc->sc_flags &= ~UATH_FLAG_INITDONE; + + callout_stop(&sc->stat_ch); + callout_stop(&sc->watchdog_ch); + sc->sc_tx_timer = 0; + /* abort pending transmits */ + uath_abort_xfers(sc); + /* flush data & control requests into the target */ + (void)uath_flush(sc); + /* set a LED status to the disconnected. */ + uath_set_ledstate(sc, 0); + /* stop the target */ + uath_cmd_write(sc, WDCMSG_TARGET_STOP, NULL, 0, 0); +} + +static void +uath_stop(struct ifnet *ifp) +{ + struct uath_softc *sc = ifp->if_softc; + + UATH_LOCK(sc); + uath_stop_locked(ifp); + UATH_UNLOCK(sc); +} + +static int +uath_config(struct uath_softc *sc, uint32_t reg, uint32_t val) +{ + struct uath_write_mac write; + int error; + + write.reg = htobe32(reg); + write.len = htobe32(0); /* 0 = single write */ + *(uint32_t *)write.data = htobe32(val); + + error = uath_cmd_write(sc, WDCMSG_TARGET_SET_CONFIG, &write, + 3 * sizeof (uint32_t), 0); + if (error != 0) { + device_printf(sc->sc_dev, "could not write register 0x%02x\n", + reg); + } + return (error); +} + +static int +uath_config_multi(struct uath_softc *sc, uint32_t reg, const void *data, + int len) +{ + struct uath_write_mac write; + int error; + + write.reg = htobe32(reg); + write.len = htobe32(len); + bcopy(data, write.data, len); + + /* properly handle the case where len is zero (reset) */ + error = uath_cmd_write(sc, WDCMSG_TARGET_SET_CONFIG, &write, + (len == 0) ? sizeof (uint32_t) : 2 * sizeof (uint32_t) + len, 0); + if (error != 0) { + device_printf(sc->sc_dev, + "could not write %d bytes to register 0x%02x\n", len, reg); + } + return (error); +} + +static int +uath_switch_channel(struct uath_softc *sc, struct ieee80211_channel *c) +{ + int error; + + UATH_ASSERT_LOCKED(sc); + + /* set radio frequency */ + error = uath_set_chan(sc, c); + if (error) { + device_printf(sc->sc_dev, + "could not set channel, error %d\n", error); + goto failed; + } + /* reset Tx rings */ + error = uath_reset_tx_queues(sc); + if (error) { + device_printf(sc->sc_dev, + "could not reset Tx queues, error %d\n", error); + goto failed; + } + /* set Tx rings WME properties */ + error = uath_wme_init(sc); + if (error) { + device_printf(sc->sc_dev, + "could not init Tx queues, error %d\n", error); + goto failed; + } + error = uath_set_ledstate(sc, 0); + if (error) { + device_printf(sc->sc_dev, + "could not set led state, error %d\n", error); + goto failed; + } + error = uath_flush(sc); + if (error) { + device_printf(sc->sc_dev, + "could not flush pipes, error %d\n", error); + goto failed; + } +failed: + return (error); +} + +static int +uath_set_rxfilter(struct uath_softc *sc, uint32_t bits, uint32_t op) +{ + struct uath_cmd_rx_filter rxfilter; + + rxfilter.bits = htobe32(bits); + rxfilter.op = htobe32(op); + + DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, + "setting Rx filter=0x%x flags=0x%x\n", bits, op); + return uath_cmd_write(sc, WDCMSG_RX_FILTER, &rxfilter, + sizeof rxfilter, 0); +} + +static void +uath_watchdog(void *arg) +{ + struct uath_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + + if (sc->sc_tx_timer > 0) { + if (--sc->sc_tx_timer == 0) { + device_printf(sc->sc_dev, "device timeout\n"); + /*uath_init(ifp); XXX needs a process context! */ + ifp->if_oerrors++; + return; + } + callout_reset(&sc->watchdog_ch, hz, uath_watchdog, sc); + } +} + +static void +uath_abort_xfers(struct uath_softc *sc) +{ + int i; + + UATH_ASSERT_LOCKED(sc); + /* abort any pending transfers */ + for (i = 0; i < UATH_N_XFERS; i++) + usbd_transfer_stop(sc->sc_xfer[i]); +} + +static int +uath_flush(struct uath_softc *sc) +{ + int error; + + error = uath_dataflush(sc); + if (error != 0) + goto failed; + + error = uath_cmdflush(sc); + if (error != 0) + goto failed; + +failed: + return (error); +} + +static int +uath_cmdflush(struct uath_softc *sc) +{ + + return uath_cmd_write(sc, WDCMSG_FLUSH, NULL, 0, 0); +} + +static int +uath_dataflush(struct uath_softc *sc) +{ + struct uath_data *data; + struct uath_chunk *chunk; + struct uath_tx_desc *desc; + + UATH_ASSERT_LOCKED(sc); + + data = uath_getbuf(sc); + if (data == NULL) + return (ENOBUFS); + data->buflen = sizeof(struct uath_chunk) + sizeof(struct uath_tx_desc); + data->m = NULL; + data->ni = NULL; + chunk = (struct uath_chunk *)data->buf; + desc = (struct uath_tx_desc *)(chunk + 1); + + /* one chunk only */ + chunk->seqnum = 0; + chunk->flags = UATH_CFLAGS_FINAL; + chunk->length = htobe16(sizeof (struct uath_tx_desc)); + + memset(desc, 0, sizeof(struct uath_tx_desc)); + desc->msglen = htobe32(sizeof(struct uath_tx_desc)); + desc->msgid = (sc->sc_msgid++) + 1; /* don't care about endianness */ + desc->type = htobe32(WDCMSG_FLUSH); + desc->txqid = htobe32(0); + desc->connid = htobe32(0); + desc->flags = htobe32(0); + +#ifdef UATH_DEBUG + if (sc->sc_debug & UATH_DEBUG_CMDS) { + DPRINTF(sc, UATH_DEBUG_RESET, "send flush ix %d\n", + desc->msgid); + if (sc->sc_debug & UATH_DEBUG_CMDS_DUMP) + uath_dump_cmd(data->buf, data->buflen, '+'); + } +#endif + + STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next); + UATH_STAT_INC(sc, st_tx_pending); + sc->sc_tx_timer = 5; + usbd_transfer_start(sc->sc_xfer[UATH_BULK_TX]); + + return (0); +} + +static struct uath_data * +_uath_getbuf(struct uath_softc *sc) +{ + struct uath_data *bf; + + bf = STAILQ_FIRST(&sc->sc_tx_inactive); + if (bf != NULL) { + STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next); + UATH_STAT_DEC(sc, st_tx_inactive); + } else + bf = NULL; + if (bf == NULL) + DPRINTF(sc, UATH_DEBUG_XMIT, "%s: %s\n", __func__, + "out of xmit buffers"); + return (bf); +} + +static struct uath_data * +uath_getbuf(struct uath_softc *sc) +{ + struct uath_data *bf; + + UATH_ASSERT_LOCKED(sc); + + bf = _uath_getbuf(sc); + if (bf == NULL) { + struct ifnet *ifp = sc->sc_ifp; + + DPRINTF(sc, UATH_DEBUG_XMIT, "%s: stop queue\n", __func__); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + } + return (bf); +} + +static int +uath_set_ledstate(struct uath_softc *sc, int connected) +{ + + DPRINTF(sc, UATH_DEBUG_LED, + "set led state %sconnected\n", connected ? "" : "!"); + connected = htobe32(connected); + return uath_cmd_write(sc, WDCMSG_SET_LED_STATE, + &connected, sizeof connected, 0); +} + +static int +uath_set_chan(struct uath_softc *sc, struct ieee80211_channel *c) +{ +#ifdef UATH_DEBUG + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; +#endif + struct uath_cmd_reset reset; + + memset(&reset, 0, sizeof(reset)); + if (IEEE80211_IS_CHAN_2GHZ(c)) + reset.flags |= htobe32(UATH_CHAN_2GHZ); + if (IEEE80211_IS_CHAN_5GHZ(c)) + reset.flags |= htobe32(UATH_CHAN_5GHZ); + /* NB: 11g =>'s 11b so don't specify both OFDM and CCK */ + if (IEEE80211_IS_CHAN_OFDM(c)) + reset.flags |= htobe32(UATH_CHAN_OFDM); + else if (IEEE80211_IS_CHAN_CCK(c)) + reset.flags |= htobe32(UATH_CHAN_CCK); + /* turbo can be used in either 2GHz or 5GHz */ + if (c->ic_flags & IEEE80211_CHAN_TURBO) + reset.flags |= htobe32(UATH_CHAN_TURBO); + reset.freq = htobe32(c->ic_freq); + reset.maxrdpower = htobe32(50); /* XXX */ + reset.channelchange = htobe32(1); + reset.keeprccontent = htobe32(0); + + DPRINTF(sc, UATH_DEBUG_CHANNEL, "set channel %d, flags 0x%x freq %u\n", + ieee80211_chan2ieee(ic, c), + be32toh(reset.flags), be32toh(reset.freq)); + return uath_cmd_write(sc, WDCMSG_RESET, &reset, sizeof reset, 0); +} + +static int +uath_reset_tx_queues(struct uath_softc *sc) +{ + int ac, error; + + DPRINTF(sc, UATH_DEBUG_RESET, "%s: reset Tx queues\n", __func__); + for (ac = 0; ac < 4; ac++) { + const uint32_t qid = htobe32(ac); + + error = uath_cmd_write(sc, WDCMSG_RELEASE_TX_QUEUE, &qid, + sizeof qid, 0); + if (error != 0) + break; + } + return (error); +} + +static int +uath_wme_init(struct uath_softc *sc) +{ + /* XXX get from net80211 */ + static const struct uath_wme_settings uath_wme_11g[4] = { + { 7, 4, 10, 0, 0 }, /* Background */ + { 3, 4, 10, 0, 0 }, /* Best-Effort */ + { 3, 3, 4, 26, 0 }, /* Video */ + { 2, 2, 3, 47, 0 } /* Voice */ + }; + struct uath_cmd_txq_setup qinfo; + int ac, error; + + DPRINTF(sc, UATH_DEBUG_WME, "%s: setup Tx queues\n", __func__); + for (ac = 0; ac < 4; ac++) { + qinfo.qid = htobe32(ac); + qinfo.len = htobe32(sizeof(qinfo.attr)); + qinfo.attr.priority = htobe32(ac); /* XXX */ + qinfo.attr.aifs = htobe32(uath_wme_11g[ac].aifsn); + qinfo.attr.logcwmin = htobe32(uath_wme_11g[ac].logcwmin); + qinfo.attr.logcwmax = htobe32(uath_wme_11g[ac].logcwmax); + qinfo.attr.bursttime = htobe32(UATH_TXOP_TO_US( + uath_wme_11g[ac].txop)); + qinfo.attr.mode = htobe32(uath_wme_11g[ac].acm);/*XXX? */ + qinfo.attr.qflags = htobe32(1); /* XXX? */ + + error = uath_cmd_write(sc, WDCMSG_SETUP_TX_QUEUE, &qinfo, + sizeof qinfo, 0); + if (error != 0) + break; + } + return (error); +} + +static int +uath_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct ieee80211com *ic = ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *) data; + int error = 0, startall = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + uath_init(ifp->if_softc); + startall = 1; + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + uath_stop(ifp); + } + if (startall) + ieee80211_start_all(ic); + break; + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + case SIOCGIFADDR: + error = ether_ioctl(ifp, cmd, data); + break; + default: + error = EINVAL; + break; + } + + return (error); +} + +static int +uath_tx_start(struct uath_softc *sc, struct mbuf *m0, struct ieee80211_node *ni, + struct uath_data *data) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct uath_chunk *chunk; + struct uath_tx_desc *desc; + const struct ieee80211_frame *wh; + struct ieee80211_key *k; + int framelen, msglen; + + UATH_ASSERT_LOCKED(sc); + + data->ni = ni; + data->m = m0; + chunk = (struct uath_chunk *)data->buf; + desc = (struct uath_tx_desc *)(chunk + 1); + + if (ieee80211_radiotap_active_vap(vap)) { + struct uath_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + if (m0->m_flags & M_FRAG) + tap->wt_flags |= IEEE80211_RADIOTAP_F_FRAG; + + ieee80211_radiotap_tx(vap, m0); + } + + wh = mtod(m0, struct ieee80211_frame *); + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + m_freem(m0); + return (ENOBUFS); + } + + /* packet header may have moved, reset our local pointer */ + wh = mtod(m0, struct ieee80211_frame *); + } + m_copydata(m0, 0, m0->m_pkthdr.len, (uint8_t *)(desc + 1)); + + framelen = m0->m_pkthdr.len + IEEE80211_CRC_LEN; + msglen = framelen + sizeof (struct uath_tx_desc); + data->buflen = msglen + sizeof (struct uath_chunk); + + /* one chunk only for now */ + chunk->seqnum = sc->sc_seqnum++; + chunk->flags = (m0->m_flags & M_FRAG) ? 0 : UATH_CFLAGS_FINAL; + if (m0->m_flags & M_LASTFRAG) + chunk->flags |= UATH_CFLAGS_FINAL; + chunk->flags = UATH_CFLAGS_FINAL; + chunk->length = htobe16(msglen); + + /* fill Tx descriptor */ + desc->msglen = htobe32(msglen); + /* NB: to get UATH_TX_NOTIFY reply, `msgid' must be larger than 0 */ + desc->msgid = (sc->sc_msgid++) + 1; /* don't care about endianness */ + desc->type = htobe32(WDCMSG_SEND); + switch (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) { + case IEEE80211_FC0_TYPE_CTL: + case IEEE80211_FC0_TYPE_MGT: + /* NB: force all management frames to highest queue */ + if (ni->ni_flags & IEEE80211_NODE_QOS) { + /* NB: force all management frames to highest queue */ + desc->txqid = htobe32(WME_AC_VO | UATH_TXQID_MINRATE); + } else + desc->txqid = htobe32(WME_AC_BE | UATH_TXQID_MINRATE); + break; + case IEEE80211_FC0_TYPE_DATA: + /* XXX multicast frames should honor mcastrate */ + desc->txqid = htobe32(M_WME_GETAC(m0)); + break; + default: + device_printf(sc->sc_dev, "bogus frame type 0x%x (%s)\n", + wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK, __func__); + m_freem(m0); + return (EIO); + } + if (vap->iv_state == IEEE80211_S_AUTH || + vap->iv_state == IEEE80211_S_ASSOC || + vap->iv_state == IEEE80211_S_RUN) + desc->connid = htobe32(UATH_ID_BSS); + else + desc->connid = htobe32(UATH_ID_INVALID); + desc->flags = htobe32(0 /* no UATH_TX_NOTIFY */); + desc->buflen = htobe32(m0->m_pkthdr.len); + +#ifdef UATH_DEBUG + DPRINTF(sc, UATH_DEBUG_XMIT, + "send frame ix %u framelen %d msglen %d connid 0x%x txqid 0x%x\n", + desc->msgid, framelen, msglen, be32toh(desc->connid), + be32toh(desc->txqid)); + if (sc->sc_debug & UATH_DEBUG_XMIT_DUMP) + uath_dump_cmd(data->buf, data->buflen, '+'); +#endif + + STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next); + UATH_STAT_INC(sc, st_tx_pending); + usbd_transfer_start(sc->sc_xfer[UATH_BULK_TX]); + + return (0); +} + +/* + * Cleanup driver resources when we run out of buffers while processing + * fragments; return the tx buffers allocated and drop node references. + */ +static void +uath_txfrag_cleanup(struct uath_softc *sc, + uath_datahead *frags, struct ieee80211_node *ni) +{ + struct uath_data *bf, *next; + + UATH_ASSERT_LOCKED(sc); + + STAILQ_FOREACH_SAFE(bf, frags, next, next) { + /* NB: bf assumed clean */ + STAILQ_REMOVE_HEAD(frags, next); + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); + UATH_STAT_INC(sc, st_tx_inactive); + ieee80211_node_decref(ni); + } +} + +/* + * Setup xmit of a fragmented frame. Allocate a buffer for each frag and bump + * the node reference count to reflect the held reference to be setup by + * uath_tx_start. + */ +static int +uath_txfrag_setup(struct uath_softc *sc, uath_datahead *frags, + struct mbuf *m0, struct ieee80211_node *ni) +{ + struct mbuf *m; + struct uath_data *bf; + + UATH_ASSERT_LOCKED(sc); + for (m = m0->m_nextpkt; m != NULL; m = m->m_nextpkt) { + bf = uath_getbuf(sc); + if (bf == NULL) { /* out of buffers, cleanup */ + uath_txfrag_cleanup(sc, frags, ni); + break; + } + ieee80211_node_incref(ni); + STAILQ_INSERT_TAIL(frags, bf, next); + } + + return !STAILQ_EMPTY(frags); +} + +/* + * Reclaim mbuf resources. For fragmented frames we need to claim each frag + * chained with m_nextpkt. + */ +static void +uath_freetx(struct mbuf *m) +{ + struct mbuf *next; + + do { + next = m->m_nextpkt; + m->m_nextpkt = NULL; + m_freem(m); + } while ((m = next) != NULL); +} + +static void +uath_start(struct ifnet *ifp) +{ + struct uath_data *bf; + struct uath_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + struct mbuf *m, *next; + uath_datahead frags; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 || + (sc->sc_flags & UATH_FLAG_INVALID)) + return; + + UATH_LOCK(sc); + for (;;) { + bf = uath_getbuf(sc); + if (bf == NULL) + break; + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) { + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); + UATH_STAT_INC(sc, st_tx_inactive); + break; + } + ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; + m->m_pkthdr.rcvif = NULL; + + /* + * Check for fragmentation. If this frame has been broken up + * verify we have enough buffers to send all the fragments + * so all go out or none... + */ + STAILQ_INIT(&frags); + if ((m->m_flags & M_FRAG) && + !uath_txfrag_setup(sc, &frags, m, ni)) { + DPRINTF(sc, UATH_DEBUG_XMIT, + "%s: out of txfrag buffers\n", __func__); + uath_freetx(m); + goto bad; + } + sc->sc_seqnum = 0; + nextfrag: + /* + * Pass the frame to the h/w for transmission. + * Fragmented frames have each frag chained together + * with m_nextpkt. We know there are sufficient uath_data's + * to send all the frags because of work done by + * uath_txfrag_setup. + */ + next = m->m_nextpkt; + if (uath_tx_start(sc, m, ni, bf) != 0) { + bad: + ifp->if_oerrors++; + reclaim: + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); + UATH_STAT_INC(sc, st_tx_inactive); + uath_txfrag_cleanup(sc, &frags, ni); + ieee80211_free_node(ni); + continue; + } + + if (next != NULL) { + /* + * Beware of state changing between frags. + XXX check sta power-save state? + */ + if (ni->ni_vap->iv_state != IEEE80211_S_RUN) { + DPRINTF(sc, UATH_DEBUG_XMIT, + "%s: flush fragmented packet, state %s\n", + __func__, + ieee80211_state_name[ni->ni_vap->iv_state]); + uath_freetx(next); + goto reclaim; + } + m = next; + bf = STAILQ_FIRST(&frags); + KASSERT(bf != NULL, ("no buf for txfrag")); + STAILQ_REMOVE_HEAD(&frags, next); + goto nextfrag; + } + + sc->sc_tx_timer = 5; + } + UATH_UNLOCK(sc); +} + +static int +uath_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct uath_data *bf; + struct uath_softc *sc = ifp->if_softc; + + /* prevent management frames from being sent if we're not ready */ + if ((sc->sc_flags & UATH_FLAG_INVALID) || + !(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + m_freem(m); + ieee80211_free_node(ni); + return (ENETDOWN); + } + + UATH_LOCK(sc); + /* grab a TX buffer */ + bf = uath_getbuf(sc); + if (bf == NULL) { + ieee80211_free_node(ni); + m_freem(m); + UATH_UNLOCK(sc); + return (ENOBUFS); + } + + sc->sc_seqnum = 0; + if (uath_tx_start(sc, m, ni, bf) != 0) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); + UATH_STAT_INC(sc, st_tx_inactive); + UATH_UNLOCK(sc); + return (EIO); + } + UATH_UNLOCK(sc); + + sc->sc_tx_timer = 5; + return (0); +} + +static void +uath_scan_start(struct ieee80211com *ic) +{ + /* do nothing */ +} + +static void +uath_scan_end(struct ieee80211com *ic) +{ + /* do nothing */ +} + +static void +uath_set_channel(struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct uath_softc *sc = ifp->if_softc; + + UATH_LOCK(sc); + if ((sc->sc_flags & UATH_FLAG_INVALID) || + (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + UATH_UNLOCK(sc); + return; + } + (void)uath_switch_channel(sc, ic->ic_curchan); + UATH_UNLOCK(sc); +} + +static int +uath_set_rxmulti_filter(struct uath_softc *sc) +{ + /* XXX broken */ + return (0); +} +static void +uath_update_mcast(struct ifnet *ifp) +{ + struct uath_softc *sc = ifp->if_softc; + + UATH_LOCK(sc); + if ((sc->sc_flags & UATH_FLAG_INVALID) || + (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + UATH_UNLOCK(sc); + return; + } + /* + * this is for avoiding the race condition when we're try to + * connect to the AP with WPA. + */ + if (sc->sc_flags & UATH_FLAG_INITDONE) + (void)uath_set_rxmulti_filter(sc); + UATH_UNLOCK(sc); +} + +static void +uath_update_promisc(struct ifnet *ifp) +{ + struct uath_softc *sc = ifp->if_softc; + + UATH_LOCK(sc); + if ((sc->sc_flags & UATH_FLAG_INVALID) || + (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + UATH_UNLOCK(sc); + return; + } + if (sc->sc_flags & UATH_FLAG_INITDONE) { + uath_set_rxfilter(sc, + UATH_FILTER_RX_UCAST | UATH_FILTER_RX_MCAST | + UATH_FILTER_RX_BCAST | UATH_FILTER_RX_BEACON | + UATH_FILTER_RX_PROM, UATH_FILTER_OP_SET); + } + UATH_UNLOCK(sc); +} + +static int +uath_create_connection(struct uath_softc *sc, uint32_t connid) +{ + const struct ieee80211_rateset *rs; + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct ieee80211_node *ni; + struct uath_cmd_create_connection create; + + ni = ieee80211_ref_node(vap->iv_bss); + memset(&create, 0, sizeof(create)); + create.connid = htobe32(connid); + create.bssid = htobe32(0); + /* XXX packed or not? */ + create.size = htobe32(sizeof(struct uath_cmd_rateset)); + + rs = &ni->ni_rates; + create.connattr.rateset.length = rs->rs_nrates; + bcopy(rs->rs_rates, &create.connattr.rateset.set[0], + rs->rs_nrates); + + /* XXX turbo */ + if (IEEE80211_IS_CHAN_A(ni->ni_chan)) + create.connattr.wlanmode = htobe32(WLAN_MODE_11a); + else if (IEEE80211_IS_CHAN_ANYG(ni->ni_chan)) + create.connattr.wlanmode = htobe32(WLAN_MODE_11g); + else + create.connattr.wlanmode = htobe32(WLAN_MODE_11b); + ieee80211_free_node(ni); + + return uath_cmd_write(sc, WDCMSG_CREATE_CONNECTION, &create, + sizeof create, 0); +} + +static int +uath_set_rates(struct uath_softc *sc, const struct ieee80211_rateset *rs) +{ + struct uath_cmd_rates rates; + + memset(&rates, 0, sizeof(rates)); + rates.connid = htobe32(UATH_ID_BSS); /* XXX */ + rates.size = htobe32(sizeof(struct uath_cmd_rateset)); + /* XXX bounds check rs->rs_nrates */ + rates.rateset.length = rs->rs_nrates; + bcopy(rs->rs_rates, &rates.rateset.set[0], rs->rs_nrates); + + DPRINTF(sc, UATH_DEBUG_RATES, + "setting supported rates nrates=%d\n", rs->rs_nrates); + return uath_cmd_write(sc, WDCMSG_SET_BASIC_RATE, + &rates, sizeof rates, 0); +} + +static int +uath_write_associd(struct uath_softc *sc) +{ + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct ieee80211_node *ni; + struct uath_cmd_set_associd associd; + + ni = ieee80211_ref_node(vap->iv_bss); + memset(&associd, 0, sizeof(associd)); + associd.defaultrateix = htobe32(1); /* XXX */ + associd.associd = htobe32(ni->ni_associd); + associd.timoffset = htobe32(0x3b); /* XXX */ + IEEE80211_ADDR_COPY(associd.bssid, ni->ni_bssid); + ieee80211_free_node(ni); + return uath_cmd_write(sc, WDCMSG_WRITE_ASSOCID, &associd, + sizeof associd, 0); +} + +static int +uath_set_ledsteady(struct uath_softc *sc, int lednum, int ledmode) +{ + struct uath_cmd_ledsteady led; + + led.lednum = htobe32(lednum); + led.ledmode = htobe32(ledmode); + + DPRINTF(sc, UATH_DEBUG_LED, "set %s led %s (steady)\n", + (lednum == UATH_LED_LINK) ? "link" : "activity", + ledmode ? "on" : "off"); + return uath_cmd_write(sc, WDCMSG_SET_LED_STEADY, &led, sizeof led, 0); +} + +static int +uath_set_ledblink(struct uath_softc *sc, int lednum, int ledmode, + int blinkrate, int slowmode) +{ + struct uath_cmd_ledblink led; + + led.lednum = htobe32(lednum); + led.ledmode = htobe32(ledmode); + led.blinkrate = htobe32(blinkrate); + led.slowmode = htobe32(slowmode); + + DPRINTF(sc, UATH_DEBUG_LED, "set %s led %s (blink)\n", + (lednum == UATH_LED_LINK) ? "link" : "activity", + ledmode ? "on" : "off"); + return uath_cmd_write(sc, WDCMSG_SET_LED_BLINK, &led, sizeof led, 0); +} + +static int +uath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + enum ieee80211_state ostate = vap->iv_state; + int error; + struct ieee80211_node *ni; + struct ieee80211com *ic = vap->iv_ic; + struct uath_softc *sc = ic->ic_ifp->if_softc; + struct uath_vap *uvp = UATH_VAP(vap); + + DPRINTF(sc, UATH_DEBUG_STATE, + "%s: %s -> %s\n", __func__, ieee80211_state_name[vap->iv_state], + ieee80211_state_name[nstate]); + + IEEE80211_UNLOCK(ic); + UATH_LOCK(sc); + callout_stop(&sc->stat_ch); + callout_stop(&sc->watchdog_ch); + ni = ieee80211_ref_node(vap->iv_bss); + + switch (nstate) { + case IEEE80211_S_INIT: + if (ostate == IEEE80211_S_RUN) { + /* turn link and activity LEDs off */ + uath_set_ledstate(sc, 0); + } + break; + + case IEEE80211_S_SCAN: + break; + + case IEEE80211_S_AUTH: + /* XXX good place? set RTS threshold */ + uath_config(sc, CFG_USER_RTS_THRESHOLD, vap->iv_rtsthreshold); + /* XXX bad place */ + error = uath_set_keys(sc, vap); + if (error != 0) { + device_printf(sc->sc_dev, + "could not set crypto keys, error %d\n", error); + break; + } + if (uath_switch_channel(sc, ni->ni_chan) != 0) { + device_printf(sc->sc_dev, "could not switch channel\n"); + break; + } + if (uath_create_connection(sc, UATH_ID_BSS) != 0) { + device_printf(sc->sc_dev, + "could not create connection\n"); + break; + } + break; + + case IEEE80211_S_ASSOC: + if (uath_set_rates(sc, &ni->ni_rates) != 0) { + device_printf(sc->sc_dev, + "could not set negotiated rate set\n"); + break; + } + break; + + case IEEE80211_S_RUN: + /* XXX monitor mode doesn't be tested */ + if (ic->ic_opmode == IEEE80211_M_MONITOR) { + uath_set_ledstate(sc, 1); + break; + } + + /* + * Tx rate is controlled by firmware, report the maximum + * negotiated rate in ifconfig output. + */ + ni->ni_txrate = ni->ni_rates.rs_rates[ni->ni_rates.rs_nrates-1]; + + if (uath_write_associd(sc) != 0) { + device_printf(sc->sc_dev, + "could not write association id\n"); + break; + } + /* turn link LED on */ + uath_set_ledsteady(sc, UATH_LED_LINK, UATH_LED_ON); + /* make activity LED blink */ + uath_set_ledblink(sc, UATH_LED_ACTIVITY, UATH_LED_ON, 1, 2); + /* set state to associated */ + uath_set_ledstate(sc, 1); + + /* start statistics timer */ + callout_reset(&sc->stat_ch, hz, uath_stat, sc); + break; + default: + break; + } + ieee80211_free_node(ni); + UATH_UNLOCK(sc); + IEEE80211_LOCK(ic); + return (uvp->newstate(vap, nstate, arg)); +} + +static int +uath_set_key(struct uath_softc *sc, const struct ieee80211_key *wk, + int index) +{ +#if 0 + struct uath_cmd_crypto crypto; + int i; + + memset(&crypto, 0, sizeof(crypto)); + crypto.keyidx = htobe32(index); + crypto.magic1 = htobe32(1); + crypto.size = htobe32(368); + crypto.mask = htobe32(0xffff); + crypto.flags = htobe32(0x80000068); + if (index != UATH_DEFAULT_KEY) + crypto.flags |= htobe32(index << 16); + memset(crypto.magic2, 0xff, sizeof(crypto.magic2)); + + /* + * Each byte of the key must be XOR'ed with 10101010 before being + * transmitted to the firmware. + */ + for (i = 0; i < wk->wk_keylen; i++) + crypto.key[i] = wk->wk_key[i] ^ 0xaa; + + DPRINTF(sc, UATH_DEBUG_CRYPTO, + "setting crypto key index=%d len=%d\n", index, wk->wk_keylen); + return uath_cmd_write(sc, WDCMSG_SET_KEY_CACHE_ENTRY, &crypto, + sizeof crypto, 0); +#else + /* XXX support H/W cryto */ + return (0); +#endif +} + +static int +uath_set_keys(struct uath_softc *sc, struct ieee80211vap *vap) +{ + int i, error; + + error = 0; + for (i = 0; i < IEEE80211_WEP_NKID; i++) { + const struct ieee80211_key *wk = &vap->iv_nw_keys[i]; + + if (wk->wk_flags & (IEEE80211_KEY_XMIT|IEEE80211_KEY_RECV)) { + error = uath_set_key(sc, wk, i); + if (error) + return (error); + } + } + if (vap->iv_def_txkey != IEEE80211_KEYIX_NONE) { + error = uath_set_key(sc, &vap->iv_nw_keys[vap->iv_def_txkey], + UATH_DEFAULT_KEY); + } + return (error); +} + +#define UATH_SYSCTL_STAT_ADD32(c, h, n, p, d) \ + SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d) + +static void +uath_sysctl_node(struct uath_softc *sc) +{ + struct sysctl_ctx_list *ctx; + struct sysctl_oid_list *child; + struct sysctl_oid *tree; + struct uath_stat *stats; + + stats = &sc->sc_stat; + ctx = device_get_sysctl_ctx(sc->sc_dev); + child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)); + + tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", CTLFLAG_RD, + NULL, "UATH statistics"); + child = SYSCTL_CHILDREN(tree); + UATH_SYSCTL_STAT_ADD32(ctx, child, "badchunkseqnum", + &stats->st_badchunkseqnum, "Bad chunk sequence numbers"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "invalidlen", &stats->st_invalidlen, + "Invalid length"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "multichunk", &stats->st_multichunk, + "Multi chunks"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "toobigrxpkt", + &stats->st_toobigrxpkt, "Too big rx packets"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "stopinprogress", + &stats->st_stopinprogress, "Stop in progress"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "crcerrs", &stats->st_crcerr, + "CRC errors"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "phyerr", &stats->st_phyerr, + "PHY errors"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "decrypt_crcerr", + &stats->st_decrypt_crcerr, "Decryption CRC errors"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "decrypt_micerr", + &stats->st_decrypt_micerr, "Decryption Misc errors"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "decomperr", &stats->st_decomperr, + "Decomp errors"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "keyerr", &stats->st_keyerr, + "Key errors"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "err", &stats->st_err, + "Unknown errors"); + + UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_active", + &stats->st_cmd_active, "Active numbers in Command queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_inactive", + &stats->st_cmd_inactive, "Inactive numbers in Command queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_pending", + &stats->st_cmd_pending, "Pending numbers in Command queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_waiting", + &stats->st_cmd_waiting, "Waiting numbers in Command queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "rx_active", + &stats->st_rx_active, "Active numbers in RX queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "rx_inactive", + &stats->st_rx_inactive, "Inactive numbers in RX queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "tx_active", + &stats->st_tx_active, "Active numbers in TX queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "tx_inactive", + &stats->st_tx_inactive, "Inactive numbers in TX queue"); + UATH_SYSCTL_STAT_ADD32(ctx, child, "tx_pending", + &stats->st_tx_pending, "Pending numbers in TX queue"); +} + +#undef UATH_SYSCTL_STAT_ADD32 + +static void +uath_cmdeof(struct uath_softc *sc, struct uath_cmd *cmd) +{ + struct uath_cmd_hdr *hdr; + int dlen; + + hdr = (struct uath_cmd_hdr *)cmd->buf; + /* NB: msgid is passed thru w/o byte swapping */ +#ifdef UATH_DEBUG + if (sc->sc_debug & UATH_DEBUG_CMDS) { + int len = be32toh(hdr->len); + printf("%s: %s [ix %u] len %u status %u\n", + __func__, uath_codename(be32toh(hdr->code)), + hdr->msgid, len, be32toh(hdr->magic)); + if (sc->sc_debug & UATH_DEBUG_CMDS_DUMP) + uath_dump_cmd(cmd->buf, + len > UATH_MAX_CMDSZ ? sizeof(*hdr) : len, '-'); + } +#endif + hdr->code = be32toh(hdr->code); + hdr->len = be32toh(hdr->len); + hdr->magic = be32toh(hdr->magic); /* target status on return */ + + switch (hdr->code & 0xff) { + /* reply to a read command */ + default: + dlen = hdr->len - sizeof(*hdr); + DPRINTF(sc, UATH_DEBUG_RX_PROC | UATH_DEBUG_RECV_ALL, + "%s: code %d data len %u\n", + __func__, hdr->code & 0xff, dlen); + /* + * The first response from the target after the + * HOST_AVAILABLE has an invalid msgid so we must + * treat it specially. + */ + if (hdr->msgid < UATH_CMD_LIST_COUNT) { + uint32_t *rp = (uint32_t *)(hdr+1); + u_int olen; + + if (!(sizeof(*hdr) <= hdr->len && + hdr->len < UATH_MAX_CMDSZ)) { + device_printf(sc->sc_dev, + "%s: invalid WDC msg length %u; " + "msg ignored\n", __func__, hdr->len); + return; + } + /* + * Calculate return/receive payload size; the + * first word, if present, always gives the + * number of bytes--unless it's 0 in which + * case a single 32-bit word should be present. + */ + if (dlen >= sizeof(uint32_t)) { + olen = be32toh(rp[0]); + dlen -= sizeof(uint32_t); + if (olen == 0) { + /* convention is 0 =>'s one word */ + olen = sizeof(uint32_t); + /* XXX KASSERT(olen == dlen ) */ + } + } else + olen = 0; + if (cmd->odata != NULL) { + /* NB: cmd->olen validated in uath_cmd */ + if (olen > cmd->olen) { + /* XXX complain? */ + device_printf(sc->sc_dev, + "%s: cmd 0x%x olen %u cmd olen %u\n", + __func__, hdr->code, olen, + cmd->olen); + olen = cmd->olen; + } + if (olen > dlen) { + /* XXX complain, shouldn't happen */ + device_printf(sc->sc_dev, + "%s: cmd 0x%x olen %u dlen %u\n", + __func__, hdr->code, olen, dlen); + olen = dlen; + } + /* XXX have submitter do this */ + /* copy answer into caller's supplied buffer */ + bcopy(&rp[1], cmd->odata, olen); + cmd->olen = olen; + } + } + wakeup_one(cmd); /* wake up caller */ + break; + + case WDCMSG_TARGET_START: + if (hdr->msgid >= UATH_CMD_LIST_COUNT) { + /* XXX */ + return; + } + dlen = hdr->len - sizeof(*hdr); + if (dlen != sizeof(uint32_t)) { + /* XXX something wrong */ + return; + } + /* XXX have submitter do this */ + /* copy answer into caller's supplied buffer */ + bcopy(hdr+1, cmd->odata, sizeof(uint32_t)); + cmd->olen = sizeof(uint32_t); + wakeup_one(cmd); /* wake up caller */ + break; + + case WDCMSG_SEND_COMPLETE: + /* this notification is sent when UATH_TX_NOTIFY is set */ + DPRINTF(sc, UATH_DEBUG_RX_PROC | UATH_DEBUG_RECV_ALL, + "%s: received Tx notification\n", __func__); + break; + + case WDCMSG_TARGET_GET_STATS: + DPRINTF(sc, UATH_DEBUG_RX_PROC | UATH_DEBUG_RECV_ALL, + "%s: received device statistics\n", __func__); + callout_reset(&sc->stat_ch, hz, uath_stat, sc); + break; + } +} + +static void +uath_intr_rx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uath_softc *sc = usbd_xfer_softc(xfer); + struct uath_cmd *cmd; + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + UATH_ASSERT_LOCKED(sc); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + cmd = STAILQ_FIRST(&sc->sc_cmd_waiting); + if (cmd == NULL) + goto setup; + STAILQ_REMOVE_HEAD(&sc->sc_cmd_waiting, next); + UATH_STAT_DEC(sc, st_cmd_waiting); + STAILQ_INSERT_TAIL(&sc->sc_cmd_inactive, cmd, next); + UATH_STAT_INC(sc, st_cmd_inactive); + + KASSERT(actlen >= sizeof(struct uath_cmd_hdr), + ("short xfer error")); + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, cmd->buf, actlen); + uath_cmdeof(sc, cmd); + case USB_ST_SETUP: +setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + default: + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto setup; + } + break; + } +} + +static void +uath_intr_tx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uath_softc *sc = usbd_xfer_softc(xfer); + struct uath_cmd *cmd; + + UATH_ASSERT_LOCKED(sc); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + cmd = STAILQ_FIRST(&sc->sc_cmd_active); + if (cmd == NULL) + goto setup; + STAILQ_REMOVE_HEAD(&sc->sc_cmd_active, next); + UATH_STAT_DEC(sc, st_cmd_active); + STAILQ_INSERT_TAIL((cmd->flags & UATH_CMD_FLAG_READ) ? + &sc->sc_cmd_waiting : &sc->sc_cmd_inactive, cmd, next); + if (cmd->flags & UATH_CMD_FLAG_READ) + UATH_STAT_INC(sc, st_cmd_waiting); + else + UATH_STAT_INC(sc, st_cmd_inactive); + /* FALLTHROUGH */ + case USB_ST_SETUP: +setup: + cmd = STAILQ_FIRST(&sc->sc_cmd_pending); + if (cmd == NULL) { + DPRINTF(sc, UATH_DEBUG_XMIT, "%s: empty pending queue\n", + __func__); + return; + } + STAILQ_REMOVE_HEAD(&sc->sc_cmd_pending, next); + UATH_STAT_DEC(sc, st_cmd_pending); + STAILQ_INSERT_TAIL((cmd->flags & UATH_CMD_FLAG_ASYNC) ? + &sc->sc_cmd_inactive : &sc->sc_cmd_active, cmd, next); + if (cmd->flags & UATH_CMD_FLAG_ASYNC) + UATH_STAT_INC(sc, st_cmd_inactive); + else + UATH_STAT_INC(sc, st_cmd_active); + + usbd_xfer_set_frame_data(xfer, 0, cmd->buf, cmd->buflen); + usbd_transfer_submit(xfer); + break; + default: + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto setup; + } + break; + } +} + +static void +uath_update_rxstat(struct uath_softc *sc, uint32_t status) +{ + + switch (status) { + case UATH_STATUS_STOP_IN_PROGRESS: + UATH_STAT_INC(sc, st_stopinprogress); + break; + case UATH_STATUS_CRC_ERR: + UATH_STAT_INC(sc, st_crcerr); + break; + case UATH_STATUS_PHY_ERR: + UATH_STAT_INC(sc, st_phyerr); + break; + case UATH_STATUS_DECRYPT_CRC_ERR: + UATH_STAT_INC(sc, st_decrypt_crcerr); + break; + case UATH_STATUS_DECRYPT_MIC_ERR: + UATH_STAT_INC(sc, st_decrypt_micerr); + break; + case UATH_STATUS_DECOMP_ERR: + UATH_STAT_INC(sc, st_decomperr); + break; + case UATH_STATUS_KEY_ERR: + UATH_STAT_INC(sc, st_keyerr); + break; + case UATH_STATUS_ERR: + UATH_STAT_INC(sc, st_err); + break; + default: + break; + } +} + +static struct mbuf * +uath_data_rxeof(struct usb_xfer *xfer, struct uath_data *data, + struct uath_rx_desc **pdesc) +{ + struct uath_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct uath_chunk *chunk; + struct uath_rx_desc *desc; + struct mbuf *m = data->m, *mnew, *mp; + uint16_t chunklen; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + if (actlen < UATH_MIN_RXBUFSZ) { + DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, + "%s: wrong xfer size (len=%d)\n", __func__, actlen); + ifp->if_ierrors++; + return (NULL); + } + + chunk = (struct uath_chunk *)data->buf; + if (chunk->seqnum == 0 && chunk->flags == 0 && chunk->length == 0) { + device_printf(sc->sc_dev, "%s: strange response\n", __func__); + ifp->if_ierrors++; + UATH_RESET_INTRX(sc); + return (NULL); + } + + if (chunk->seqnum != sc->sc_intrx_nextnum) { + DPRINTF(sc, UATH_DEBUG_XMIT, "invalid seqnum %d, expected %d\n", + chunk->seqnum, sc->sc_intrx_nextnum); + UATH_STAT_INC(sc, st_badchunkseqnum); + if (sc->sc_intrx_head != NULL) + m_freem(sc->sc_intrx_head); + UATH_RESET_INTRX(sc); + return (NULL); + } + + /* check multi-chunk frames */ + if ((chunk->seqnum == 0 && !(chunk->flags & UATH_CFLAGS_FINAL)) || + (chunk->seqnum != 0 && (chunk->flags & UATH_CFLAGS_FINAL)) || + chunk->flags & UATH_CFLAGS_RXMSG) + UATH_STAT_INC(sc, st_multichunk); + + chunklen = be16toh(chunk->length); + if (chunk->flags & UATH_CFLAGS_FINAL) + chunklen -= sizeof(struct uath_rx_desc); + + if (chunklen > 0 && + (!(chunk->flags & UATH_CFLAGS_FINAL) || !(chunk->seqnum == 0))) { + /* we should use intermediate RX buffer */ + if (chunk->seqnum == 0) + UATH_RESET_INTRX(sc); + if ((sc->sc_intrx_len + sizeof(struct uath_rx_desc) + + chunklen) > UATH_MAX_INTRX_SIZE) { + UATH_STAT_INC(sc, st_invalidlen); + ifp->if_iqdrops++; + if (sc->sc_intrx_head != NULL) + m_freem(sc->sc_intrx_head); + UATH_RESET_INTRX(sc); + return (NULL); + } + + m->m_len = chunklen; + m->m_data += sizeof(struct uath_chunk); + + if (sc->sc_intrx_head == NULL) { + sc->sc_intrx_head = m; + sc->sc_intrx_tail = m; + } else { + m->m_flags &= ~M_PKTHDR; + sc->sc_intrx_tail->m_next = m; + sc->sc_intrx_tail = m; + } + } + sc->sc_intrx_len += chunklen; + + mnew = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (mnew == NULL) { + DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, + "%s: can't get new mbuf, drop frame\n", __func__); + ifp->if_ierrors++; + if (sc->sc_intrx_head != NULL) + m_freem(sc->sc_intrx_head); + UATH_RESET_INTRX(sc); + return (NULL); + } + + data->m = mnew; + data->buf = mtod(mnew, uint8_t *); + + /* if the frame is not final continue the transfer */ + if (!(chunk->flags & UATH_CFLAGS_FINAL)) { + sc->sc_intrx_nextnum++; + UATH_RESET_INTRX(sc); + return (NULL); + } + + /* + * if the frame is not set UATH_CFLAGS_RXMSG, then rx descriptor is + * located at the end, 32-bit aligned + */ + desc = (chunk->flags & UATH_CFLAGS_RXMSG) ? + (struct uath_rx_desc *)(chunk + 1) : + (struct uath_rx_desc *)(((uint8_t *)chunk) + + sizeof(struct uath_chunk) + be16toh(chunk->length) - + sizeof(struct uath_rx_desc)); + *pdesc = desc; + + DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, + "%s: frame len %u code %u status %u rate %u antenna %u " + "rssi %d channel %u phyerror %u connix %u decrypterror %u " + "keycachemiss %u\n", __func__, be32toh(desc->framelen) + , be32toh(desc->code), be32toh(desc->status), be32toh(desc->rate) + , be32toh(desc->antenna), be32toh(desc->rssi), be32toh(desc->channel) + , be32toh(desc->phyerror), be32toh(desc->connix) + , be32toh(desc->decrypterror), be32toh(desc->keycachemiss)); + + if (be32toh(desc->len) > MCLBYTES) { + DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL, + "%s: bad descriptor (len=%d)\n", __func__, + be32toh(desc->len)); + ifp->if_iqdrops++; + UATH_STAT_INC(sc, st_toobigrxpkt); + if (sc->sc_intrx_head != NULL) + m_freem(sc->sc_intrx_head); + UATH_RESET_INTRX(sc); + return (NULL); + } + + uath_update_rxstat(sc, be32toh(desc->status)); + + /* finalize mbuf */ + if (sc->sc_intrx_head == NULL) { + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = + be32toh(desc->framelen) - UATH_RX_DUMMYSIZE; + m->m_data += sizeof(struct uath_chunk); + } else { + mp = sc->sc_intrx_head; + mp->m_pkthdr.rcvif = ifp; + mp->m_flags |= M_PKTHDR; + mp->m_pkthdr.len = sc->sc_intrx_len; + m = mp; + } + + /* there are a lot more fields in the RX descriptor */ + if ((sc->sc_flags & UATH_FLAG_INVALID) == 0 && + ieee80211_radiotap_active(ic)) { + struct uath_rx_radiotap_header *tap = &sc->sc_rxtap; + uint32_t tsf_hi = be32toh(desc->tstamp_high); + uint32_t tsf_lo = be32toh(desc->tstamp_low); + + /* XXX only get low order 24bits of tsf from h/w */ + tap->wr_tsf = htole64(((uint64_t)tsf_hi << 32) | tsf_lo); + tap->wr_flags = 0; + if (be32toh(desc->status) == UATH_STATUS_CRC_ERR) + tap->wr_flags |= IEEE80211_RADIOTAP_F_BADFCS; + /* XXX map other status to BADFCS? */ + /* XXX ath h/w rate code, need to map */ + tap->wr_rate = be32toh(desc->rate); + tap->wr_antenna = be32toh(desc->antenna); + tap->wr_antsignal = -95 + be32toh(desc->rssi); + tap->wr_antnoise = -95; + } + + ifp->if_ipackets++; + UATH_RESET_INTRX(sc); + + return (m); +} + +static void +uath_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uath_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211_frame *wh; + struct ieee80211_node *ni; + struct mbuf *m = NULL; + struct uath_data *data; + struct uath_rx_desc *desc = NULL; + int8_t nf; + + UATH_ASSERT_LOCKED(sc); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + data = STAILQ_FIRST(&sc->sc_rx_active); + if (data == NULL) + goto setup; + STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); + UATH_STAT_DEC(sc, st_rx_active); + m = uath_data_rxeof(xfer, data, &desc); + STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); + UATH_STAT_INC(sc, st_rx_inactive); + /* FALLTHROUGH */ + case USB_ST_SETUP: +setup: + data = STAILQ_FIRST(&sc->sc_rx_inactive); + if (data == NULL) + return; + STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next); + UATH_STAT_DEC(sc, st_rx_inactive); + STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next); + UATH_STAT_INC(sc, st_rx_active); + usbd_xfer_set_frame_data(xfer, 0, data->buf, + usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + + /* + * To avoid LOR we should unlock our private mutex here to call + * ieee80211_input() because here is at the end of a USB + * callback and safe to unlock. + */ + if (sc->sc_flags & UATH_FLAG_INVALID) { + if (m != NULL) + m_freem(m); + return; + } + UATH_UNLOCK(sc); + if (m != NULL && desc != NULL) { + wh = mtod(m, struct ieee80211_frame *); + ni = ieee80211_find_rxnode(ic, + (struct ieee80211_frame_min *)wh); + nf = -95; /* XXX */ + if (ni != NULL) { + (void) ieee80211_input(ni, m, + (int)be32toh(desc->rssi), nf); + /* node is no longer needed */ + ieee80211_free_node(ni); + } else + (void) ieee80211_input_all(ic, m, + (int)be32toh(desc->rssi), nf); + m = NULL; + desc = NULL; + } + if ((ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0 && + !IFQ_IS_EMPTY(&ifp->if_snd)) + uath_start(ifp); + UATH_LOCK(sc); + break; + default: + /* needs it to the inactive queue due to a error. */ + data = STAILQ_FIRST(&sc->sc_rx_active); + if (data != NULL) { + STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); + UATH_STAT_DEC(sc, st_rx_active); + STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); + UATH_STAT_INC(sc, st_rx_inactive); + } + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + ifp->if_ierrors++; + goto setup; + } + break; + } +} + +static void +uath_data_txeof(struct usb_xfer *xfer, struct uath_data *data) +{ + struct uath_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + + UATH_ASSERT_LOCKED(sc); + + /* + * Do any tx complete callback. Note this must be done before releasing + * the node reference. + */ + if (data->m) { + m = data->m; + if (m->m_flags & M_TXCB && + (sc->sc_flags & UATH_FLAG_INVALID) == 0) { + /* XXX status? */ + ieee80211_process_callback(data->ni, m, 0); + } + m_freem(m); + data->m = NULL; + } + if (data->ni) { + if ((sc->sc_flags & UATH_FLAG_INVALID) == 0) + ieee80211_free_node(data->ni); + data->ni = NULL; + } + sc->sc_tx_timer = 0; + ifp->if_opackets++; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; +} + +static void +uath_bulk_tx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uath_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct uath_data *data; + + UATH_ASSERT_LOCKED(sc); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + data = STAILQ_FIRST(&sc->sc_tx_active); + if (data == NULL) + goto setup; + STAILQ_REMOVE_HEAD(&sc->sc_tx_active, next); + UATH_STAT_DEC(sc, st_tx_active); + uath_data_txeof(xfer, data); + STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next); + UATH_STAT_INC(sc, st_tx_inactive); + /* FALLTHROUGH */ + case USB_ST_SETUP: +setup: + data = STAILQ_FIRST(&sc->sc_tx_pending); + if (data == NULL) { + DPRINTF(sc, UATH_DEBUG_XMIT, "%s: empty pending queue\n", + __func__); + return; + } + STAILQ_REMOVE_HEAD(&sc->sc_tx_pending, next); + UATH_STAT_DEC(sc, st_tx_pending); + STAILQ_INSERT_TAIL(&sc->sc_tx_active, data, next); + UATH_STAT_INC(sc, st_tx_active); + + usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen); + usbd_transfer_submit(xfer); + + UATH_UNLOCK(sc); + uath_start(ifp); + UATH_LOCK(sc); + break; + default: + data = STAILQ_FIRST(&sc->sc_tx_active); + if (data == NULL) + goto setup; + if (data->ni != NULL) { + if ((sc->sc_flags & UATH_FLAG_INVALID) == 0) + ieee80211_free_node(data->ni); + data->ni = NULL; + ifp->if_oerrors++; + } + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto setup; + } + break; + } +} + +static device_method_t uath_methods[] = { + DEVMETHOD(device_probe, uath_match), + DEVMETHOD(device_attach, uath_attach), + DEVMETHOD(device_detach, uath_detach), + { 0, 0 } +}; +static driver_t uath_driver = { + "uath", + uath_methods, + sizeof(struct uath_softc) +}; +static devclass_t uath_devclass; + +DRIVER_MODULE(uath, uhub, uath_driver, uath_devclass, NULL, 0); +MODULE_DEPEND(uath, wlan, 1, 1, 1); +MODULE_DEPEND(uath, usb, 1, 1, 1); +MODULE_VERSION(uath, 1); diff --git a/sys/bus/u4b/wlan/if_uathreg.h b/sys/bus/u4b/wlan/if_uathreg.h new file mode 100644 index 0000000000..1e7929bc7e --- /dev/null +++ b/sys/bus/u4b/wlan/if_uathreg.h @@ -0,0 +1,601 @@ +/* $OpenBSD: if_uathreg.h,v 1.2 2006/09/18 16:34:23 damien Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2006 + * Damien Bergamini + * Copyright (c) 2006 Sam Leffler, Errno Consulting + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define UATH_CONFIG_INDEX 0 +#define UATH_IFACE_INDEX 0 + +/* all fields are big endian */ +struct uath_fwblock { + uint32_t flags; +#define UATH_WRITE_BLOCK (1 << 4) + + uint32_t len; +#define UATH_MAX_FWBLOCK_SIZE 2048 + + uint32_t total; + uint32_t remain; + uint32_t rxtotal; + uint32_t pad[123]; +} __packed; + +#define UATH_MAX_CMDSZ 512 + +/* + * Messages are passed in Target Endianness. All fixed-size + * fields of a WDS Control Message are treated as 32-bit + * values and Control Msgs are guaranteed to be 32-bit aligned. + * + * The format of a WDS Control Message is as follows: + * Message Length 32 bits + * Message Opcode 32 bits + * Message ID 32 bits + * parameter 1 + * parameter 2 + * ... + * + * A variable-length parameter, or a parmeter that is larger than + * 32 bits is passed as pair, where length is a + * 32-bit quantity and data is padded to 32 bits. + */ +struct uath_cmd_hdr { + uint32_t len; /* msg length including header */ + uint32_t code; /* operation code */ +/* NB: these are defined for rev 1.5 firmware; rev 1.6 is different */ +/* messages from Host -> Target */ +#define WDCMSG_HOST_AVAILABLE 0x01 +#define WDCMSG_BIND 0x02 +#define WDCMSG_TARGET_RESET 0x03 +#define WDCMSG_TARGET_GET_CAPABILITY 0x04 +#define WDCMSG_TARGET_SET_CONFIG 0x05 +#define WDCMSG_TARGET_GET_STATUS 0x06 +#define WDCMSG_TARGET_GET_STATS 0x07 +#define WDCMSG_TARGET_START 0x08 +#define WDCMSG_TARGET_STOP 0x09 +#define WDCMSG_TARGET_ENABLE 0x0a +#define WDCMSG_TARGET_DISABLE 0x0b +#define WDCMSG_CREATE_CONNECTION 0x0c +#define WDCMSG_UPDATE_CONNECT_ATTR 0x0d +#define WDCMSG_DELETE_CONNECT 0x0e +#define WDCMSG_SEND 0x0f +#define WDCMSG_FLUSH 0x10 +/* messages from Target -> Host */ +#define WDCMSG_STATS_UPDATE 0x11 +#define WDCMSG_BMISS 0x12 +#define WDCMSG_DEVICE_AVAIL 0x13 +#define WDCMSG_SEND_COMPLETE 0x14 +#define WDCMSG_DATA_AVAIL 0x15 +#define WDCMSG_SET_PWR_MODE 0x16 +#define WDCMSG_BMISS_ACK 0x17 +#define WDCMSG_SET_LED_STEADY 0x18 +#define WDCMSG_SET_LED_BLINK 0x19 +/* more messages */ +#define WDCMSG_SETUP_BEACON_DESC 0x1a +#define WDCMSG_BEACON_INIT 0x1b +#define WDCMSG_RESET_KEY_CACHE 0x1c +#define WDCMSG_RESET_KEY_CACHE_ENTRY 0x1d +#define WDCMSG_SET_KEY_CACHE_ENTRY 0x1e +#define WDCMSG_SET_DECOMP_MASK 0x1f +#define WDCMSG_SET_REGULATORY_DOMAIN 0x20 +#define WDCMSG_SET_LED_STATE 0x21 +#define WDCMSG_WRITE_ASSOCID 0x22 +#define WDCMSG_SET_STA_BEACON_TIMERS 0x23 +#define WDCMSG_GET_TSF 0x24 +#define WDCMSG_RESET_TSF 0x25 +#define WDCMSG_SET_ADHOC_MODE 0x26 +#define WDCMSG_SET_BASIC_RATE 0x27 +#define WDCMSG_MIB_CONTROL 0x28 +#define WDCMSG_GET_CHANNEL_DATA 0x29 +#define WDCMSG_GET_CUR_RSSI 0x2a +#define WDCMSG_SET_ANTENNA_SWITCH 0x2b +#define WDCMSG_USE_SHORT_SLOT_TIME 0x2f +#define WDCMSG_SET_POWER_MODE 0x30 +#define WDCMSG_SETUP_PSPOLL_DESC 0x31 +#define WDCMSG_SET_RX_MULTICAST_FILTER 0x32 +#define WDCMSG_RX_FILTER 0x33 +#define WDCMSG_PER_CALIBRATION 0x34 +#define WDCMSG_RESET 0x35 +#define WDCMSG_DISABLE 0x36 +#define WDCMSG_PHY_DISABLE 0x37 +#define WDCMSG_SET_TX_POWER_LIMIT 0x38 +#define WDCMSG_SET_TX_QUEUE_PARAMS 0x39 +#define WDCMSG_SETUP_TX_QUEUE 0x3a +#define WDCMSG_RELEASE_TX_QUEUE 0x3b +#define WDCMSG_SET_DEFAULT_KEY 0x43 + uint32_t msgid; /* msg id (supplied by host) */ + uint32_t magic; /* response desired/target status */ + uint32_t debug[4]; /* debug data area */ + /* msg data follows */ +} __packed; + +struct uath_chunk { + uint8_t seqnum; /* sequence number for ordering */ + uint8_t flags; +#define UATH_CFLAGS_FINAL 0x01 /* final chunk of a msg */ +#define UATH_CFLAGS_RXMSG 0x02 /* chunk contains rx completion */ +#define UATH_CFLAGS_DEBUG 0x04 /* for debugging */ + uint16_t length; /* chunk size in bytes */ + /* chunk data follows */ +} __packed; + +#define UATH_RX_DUMMYSIZE 4 + +/* + * Message format for a WDCMSG_DATA_AVAIL message from Target to Host. + */ +struct uath_rx_desc { + uint32_t len; /* msg length including header */ + uint32_t code; /* WDCMSG_DATA_AVAIL */ + uint32_t gennum; /* generation number */ + uint32_t status; /* start of RECEIVE_INFO */ +#define UATH_STATUS_OK 0 +#define UATH_STATUS_STOP_IN_PROGRESS 1 +#define UATH_STATUS_CRC_ERR 2 +#define UATH_STATUS_PHY_ERR 3 +#define UATH_STATUS_DECRYPT_CRC_ERR 4 +#define UATH_STATUS_DECRYPT_MIC_ERR 5 +#define UATH_STATUS_DECOMP_ERR 6 +#define UATH_STATUS_KEY_ERR 7 +#define UATH_STATUS_ERR 8 + uint32_t tstamp_low; /* low-order 32-bits of rx timestamp */ + uint32_t tstamp_high; /* high-order 32-bits of rx timestamp */ + uint32_t framelen; /* frame length */ + uint32_t rate; /* rx rate code */ + uint32_t antenna; + int32_t rssi; + uint32_t channel; + uint32_t phyerror; + uint32_t connix; /* key table ix for bss traffic */ + uint32_t decrypterror; + uint32_t keycachemiss; + uint32_t pad; /* XXX? */ +} __packed; + +struct uath_tx_desc { + uint32_t msglen; + uint32_t msgid; /* msg id (supplied by host) */ + uint32_t type; /* opcode: WDMSG_SEND or WDCMSG_FLUSH */ + uint32_t txqid; /* tx queue id and flags */ +#define UATH_TXQID_MASK 0x0f +#define UATH_TXQID_MINRATE 0x10 /* use min tx rate */ +#define UATH_TXQID_FF 0x20 /* content is fast frame */ + uint32_t connid; /* tx connection id */ +#define UATH_ID_INVALID 0xffffffff /* for sending prior to connection */ + uint32_t flags; /* non-zero if response desired */ +#define UATH_TX_NOTIFY (1 << 24) /* f/w will send a UATH_NOTIF_TX */ + uint32_t buflen; /* payload length */ +} __packed; + +struct uath_cmd_host_available { + uint32_t sw_ver_major; + uint32_t sw_ver_minor; + uint32_t sw_ver_patch; + uint32_t sw_ver_build; +} __packed; +#define ATH_SW_VER_MAJOR 1 +#define ATH_SW_VER_MINOR 5 +#define ATH_SW_VER_PATCH 0 +#define ATH_SW_VER_BUILD 9999 + +struct uath_cmd_bind { + uint32_t targethandle; + uint32_t hostapiversion; +} __packed; + +/* structure for command WDCMSG_RESET */ +struct uath_cmd_reset { + uint32_t flags; /* channel flags */ +#define UATH_CHAN_TURBO 0x0100 +#define UATH_CHAN_CCK 0x0200 +#define UATH_CHAN_OFDM 0x0400 +#define UATH_CHAN_2GHZ 0x1000 +#define UATH_CHAN_5GHZ 0x2000 + uint32_t freq; /* channel frequency */ + uint32_t maxrdpower; + uint32_t cfgctl; + uint32_t twiceantennareduction; + uint32_t channelchange; + uint32_t keeprccontent; +} __packed; + +/* structure for commands UATH_CMD_READ_MAC and UATH_CMD_READ_EEPROM */ +struct uath_read_mac { + uint32_t len; + uint8_t data[32]; +} __packed; + +/* structure for command UATH_CMD_WRITE_MAC */ +struct uath_write_mac { + uint32_t reg; + uint32_t len; + uint8_t data[32]; +} __packed; + +/* structure for command UATH_CMD_STA_JOIN */ +struct uath_cmd_join_bss { + uint32_t bssid; /* NB: use zero */ + uint32_t bssmac[2]; /* bssid mac address */ + uint32_t bsstype; + uint32_t wlanmode; + uint32_t beaconinterval; + uint32_t dtiminterval; + uint32_t cfpinterval; + uint32_t atimwindow; + uint32_t defaultrateix; + uint32_t shortslottime11g; + uint32_t sleepduration; + uint32_t bmissthreshold; + uint32_t tcppowerlimit; + uint32_t quietduration; + uint32_t quietoffset; + uint32_t quietackctsallow; + uint32_t bssdefaultkey; /* XXX? */ +} __packed; + +struct uath_cmd_assoc_bss { + uint32_t bssid; + uint32_t associd; +} __packed; + +struct uath_cmd_start_bss { + uint32_t bssid; +} __packed; + +/* structure for command UATH_CMD_0C */ +struct uath_cmd_0c { + uint32_t magic1; + uint32_t magic2; + uint32_t magic3; +} __packed; + +struct uath_cmd_ledsteady { /* WDCMSG_SET_LED_STEADY */ + uint32_t lednum; +#define UATH_LED_LINK 0 +#define UATH_LED_ACTIVITY 1 + uint32_t ledmode; +#define UATH_LED_OFF 0 +#define UATH_LED_ON 1 +} __packed; + +struct uath_cmd_ledblink { /* WDCMSG_SET_LED_BLINK */ + uint32_t lednum; + uint32_t ledmode; + uint32_t blinkrate; + uint32_t slowmode; +} __packed; + +struct uath_cmd_ledstate { /* WDCMSG_SET_LED_STATE */ + uint32_t connected; +} __packed; + +struct uath_connkey_rec { + uint8_t bssid[IEEE80211_ADDR_LEN]; + uint32_t keyiv; + uint32_t extkeyiv; + uint16_t keyflags; + uint16_t keylen; + uint16_t keytype; /* WEP, TKIP or AES */ + /* As far as I know, MIPS 4Kp is 32-bit processor */ + uint32_t priv; + uint8_t keyval[32]; + uint16_t aes_keylen; + uint8_t aes_keyval[16]; + uint8_t mic_txkeyval[8]; + uint8_t mic_rxkeyval[8]; + int64_t keyrsc[17]; + int32_t keytsc[17]; + int32_t keyexttsc[17]; +} __packed; + +/* structure for command UATH_CMD_CRYPTO */ +struct uath_cmd_crypto { + uint32_t keyidx; +#define UATH_DEFAULT_KEY 6 + uint32_t xorkey; + uint32_t size; + struct uath_connkey_rec rec; +} __packed; + +struct uath_cmd_rateset { + uint8_t length; +#define UATH_MAX_NRATES 32 + uint8_t set[UATH_MAX_NRATES]; +}; + +/* structure for command WDCMSG_SET_BASIC_RATE */ +struct uath_cmd_rates { + uint32_t connid; + uint32_t keeprccontent; + uint32_t size; + struct uath_cmd_rateset rateset; +} __packed; + +enum { + WLAN_MODE_NONE = 0, + WLAN_MODE_11b, + WLAN_MODE_11a, + WLAN_MODE_11g, + WLAN_MODE_11a_TURBO, + WLAN_MODE_11g_TURBO, + WLAN_MODE_11a_TURBO_PRIME, + WLAN_MODE_11g_TURBO_PRIME, + WLAN_MODE_11a_XR, + WLAN_MODE_11g_XR, +}; + +struct uath_cmd_connection_attr { + uint32_t longpreambleonly; + struct uath_cmd_rateset rateset; + uint32_t wlanmode; +} __packed; + +/* structure for command WDCMSG_CREATE_CONNECTION */ +struct uath_cmd_create_connection { + uint32_t connid; + uint32_t bssid; + uint32_t size; + struct uath_cmd_connection_attr connattr; +} __packed; + +struct uath_cmd_txq_setparams { /* WDCMSG_SET_TX_QUEUE_PARAMS */ + uint32_t qnum; + uint32_t aifs; + uint32_t logcwmin; + uint32_t logcwmax; + uint32_t bursttime; + uint32_t qflags; +} __packed; + +struct uath_cmd_txq_attr { + uint32_t priority; + uint32_t aifs; + uint32_t logcwmin; + uint32_t logcwmax; + uint32_t bursttime; + uint32_t mode; + uint32_t qflags; +} __packed; + +struct uath_cmd_txq_setup { /* WDCMSG_SETUP_TX_QUEUE */ + uint32_t qid; + uint32_t len; + struct uath_cmd_txq_attr attr; +} __packed; + +struct uath_cmd_stoptxdma { /* WDCMSG_STOP_TX_DMA */ + uint32_t qnum; + uint32_t msec; +} __packed; + +/* structure for command UATH_CMD_31 */ +struct uath_cmd_31 { + uint32_t magic1; + uint32_t magic2; +} __packed; + +struct uath_cmd_rx_filter { /* WDCMSG_RX_FILTER */ + uint32_t bits; +#define UATH_FILTER_RX_UCAST 0x00000001 +#define UATH_FILTER_RX_MCAST 0x00000002 +#define UATH_FILTER_RX_BCAST 0x00000004 +#define UATH_FILTER_RX_CONTROL 0x00000008 +#define UATH_FILTER_RX_BEACON 0x00000010 /* beacon frames */ +#define UATH_FILTER_RX_PROM 0x00000020 /* promiscuous mode */ +#define UATH_FILTER_RX_PHY_ERR 0x00000040 /* phy errors */ +#define UATH_FILTER_RX_PHY_RADAR 0x00000080 /* radar phy errors */ +#define UATH_FILTER_RX_XR_POOL 0x00000400 /* XR group polls */ +#define UATH_FILTER_RX_PROBE_REQ 0x00000800 + uint32_t op; +#define UATH_FILTER_OP_INIT 0x0 +#define UATH_FILTER_OP_SET 0x1 +#define UATH_FILTER_OP_CLEAR 0x2 +#define UATH_FILTER_OP_TEMP 0x3 +#define UATH_FILTER_OP_RESTORE 0x4 +} __packed; + +struct uath_cmd_rx_mcast_filter { /* WDCMSG_SET_RX_MCAST_FILTER */ + uint32_t filter0; + uint32_t filter1; +} __packed; + +struct uath_cmd_set_associd { /* WDCMSG_WRITE_ASSOCID */ + uint32_t defaultrateix; + uint32_t associd; + uint32_t timoffset; + uint32_t turboprime; + uint32_t bssid[2]; +} __packed; + +struct uath_cmd_set_stabeacon_timers { /* WDCMSG_SET_STA_BEACON_TIMERS */ + uint32_t nexttbtt; + uint32_t nextdtim; + uint32_t nextcfp; + uint32_t beaconperiod; + uint32_t dtimperiod; + uint32_t cfpperiod; + uint32_t cfpduration; + uint32_t sleepduration; + uint32_t bsmissthreshold; +} __packed; + +enum { + CFG_NONE, /* Sentinal to indicate "no config" */ + CFG_REG_DOMAIN, /* Regulatory Domain */ + CFG_RATE_CONTROL_ENABLE, + CFG_DEF_XMIT_DATA_RATE, /* NB: if rate control is not enabled */ + CFG_HW_TX_RETRIES, + CFG_SW_TX_RETRIES, + CFG_SLOW_CLOCK_ENABLE, + CFG_COMP_PROC, + CFG_USER_RTS_THRESHOLD, + CFG_XR2NORM_RATE_THRESHOLD, + CFG_XRMODE_SWITCH_COUNT, + CFG_PROTECTION_TYPE, + CFG_BURST_SEQ_THRESHOLD, + CFG_ABOLT, + CFG_IQ_LOG_COUNT_MAX, + CFG_MODE_CTS, + CFG_WME_ENABLED, + CFG_GPRS_CBR_PERIOD, + CFG_SERVICE_TYPE, + /* MAC Address to use. Overrides EEPROM */ + CFG_MAC_ADDR, + CFG_DEBUG_EAR, + CFG_INIT_REGS, + /* An ID for use in error & debug messages */ + CFG_DEBUG_ID, + CFG_COMP_WIN_SZ, + CFG_DIVERSITY_CTL, + CFG_TP_SCALE, + CFG_TPC_HALF_DBM5, + CFG_TPC_HALF_DBM2, + CFG_OVERRD_TX_POWER, + CFG_USE_32KHZ_CLOCK, + CFG_GMODE_PROTECTION, + CFG_GMODE_PROTECT_RATE_INDEX, + CFG_GMODE_NON_ERP_PREAMBLE, + CFG_WDC_TRANSPORT_CHUNK_SIZE, +}; + +enum { + /* Sentinal to indicate "no capability" */ + CAP_NONE, + CAP_ALL, /* ALL capabilities */ + CAP_TARGET_VERSION, + CAP_TARGET_REVISION, + CAP_MAC_VERSION, + CAP_MAC_REVISION, + CAP_PHY_REVISION, + CAP_ANALOG_5GHz_REVISION, + CAP_ANALOG_2GHz_REVISION, + /* Target supports WDC message debug features */ + CAP_DEBUG_WDCMSG_SUPPORT, + + CAP_REG_DOMAIN, + CAP_COUNTRY_CODE, + CAP_REG_CAP_BITS, + + CAP_WIRELESS_MODES, + CAP_CHAN_SPREAD_SUPPORT, + CAP_SLEEP_AFTER_BEACON_BROKEN, + CAP_COMPRESS_SUPPORT, + CAP_BURST_SUPPORT, + CAP_FAST_FRAMES_SUPPORT, + CAP_CHAP_TUNING_SUPPORT, + CAP_TURBOG_SUPPORT, + CAP_TURBO_PRIME_SUPPORT, + CAP_DEVICE_TYPE, + CAP_XR_SUPPORT, + CAP_WME_SUPPORT, + CAP_TOTAL_QUEUES, + CAP_CONNECTION_ID_MAX, /* Should absorb CAP_KEY_CACHE_SIZE */ + + CAP_LOW_5GHZ_CHAN, + CAP_HIGH_5GHZ_CHAN, + CAP_LOW_2GHZ_CHAN, + CAP_HIGH_2GHZ_CHAN, + + CAP_MIC_AES_CCM, + CAP_MIC_CKIP, + CAP_MIC_TKIP, + CAP_MIC_TKIP_WME, + CAP_CIPHER_AES_CCM, + CAP_CIPHER_CKIP, + CAP_CIPHER_TKIP, + + CAP_TWICE_ANTENNAGAIN_5G, + CAP_TWICE_ANTENNAGAIN_2G, +}; + +enum { + ST_NONE, /* Sentinal to indicate "no status" */ + ST_ALL, + ST_SERVICE_TYPE, + ST_WLAN_MODE, + ST_FREQ, + ST_BAND, + ST_LAST_RSSI, + ST_PS_FRAMES_DROPPED, + ST_CACHED_DEF_ANT, + ST_COUNT_OTHER_RX_ANT, + ST_USE_FAST_DIVERSITY, + ST_MAC_ADDR, + ST_RX_GENERATION_NUM, + ST_TX_QUEUE_DEPTH, + ST_SERIAL_NUMBER, + ST_WDC_TRANSPORT_CHUNK_SIZE, +}; + +enum { + BSS_ATTR_BEACON_INTERVAL, + BSS_ATTR_DTIM_INTERVAL, + BSS_ATTR_CFP_INTERVAL, + BSS_ATTR_CFP_MAX_DURATION, + BSS_ATTR_ATIM_WINDOW, + BSS_ATTR_DEFAULT_RATE_INDEX, + BSS_ATTR_SHORT_SLOT_TIME_11g, + BSS_ATTR_SLEEP_DURATION, + BSS_ATTR_BMISS_THRESHOLD, + BSS_ATTR_TPC_POWER_LIMIT, + BSS_ATTR_BSS_KEY_UPDATE, +}; + +struct uath_cmd_update_bss_attribute { + uint32_t bssid; + uint32_t attribute; /* BSS_ATTR_BEACON_INTERVAL, et al. */ + uint32_t cfgsize; /* should be zero 0 */ + uint32_t cfgdata; +}; + +struct uath_cmd_update_bss_attribute_key { + uint32_t bssid; + uint32_t attribute; /* BSS_ATTR_BSS_KEY_UPDATE */ + uint32_t cfgsize; /* size of remaining data */ + uint32_t bsskeyix; + uint32_t isdefaultkey; + uint32_t keyiv; /* IV generation control */ + uint32_t extkeyiv; /* extended IV for TKIP & CCM */ + uint32_t keyflags; + uint32_t keytype; + uint32_t initvalue; /* XXX */ + uint32_t keyval[4]; + uint32_t mictxkeyval[2]; + uint32_t micrxkeyval[2]; + uint32_t keyrsc[2]; +}; + +enum { + TARGET_DEVICE_AWAKE, + TARGET_DEVICE_SLEEP, + TARGET_DEVICE_PWRDN, + TARGET_DEVICE_PWRSAVE, + TARGET_DEVICE_SUSPEND, + TARGET_DEVICE_RESUME, +}; + +#define UATH_MAX_TXBUFSZ \ + (sizeof(struct uath_chunk) + sizeof(struct uath_tx_desc) + \ + IEEE80211_MAX_LEN) + +/* + * it's not easy to measure how the chunk is passed into the host if the target + * passed the multi-chunks so just we check a minimal size we can imagine. + */ +#define UATH_MIN_RXBUFSZ (sizeof(struct uath_chunk)) diff --git a/sys/bus/u4b/wlan/if_uathvar.h b/sys/bus/u4b/wlan/if_uathvar.h new file mode 100644 index 0000000000..6e58d93a89 --- /dev/null +++ b/sys/bus/u4b/wlan/if_uathvar.h @@ -0,0 +1,245 @@ +/* $OpenBSD: if_uathvar.h,v 1.3 2006/09/20 19:47:17 damien Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2006 + * Damien Bergamini + * Copyright (c) 2006 Sam Leffler, Errno Consulting + * Copyright (c) 2008-2009 Weongyo Jeong + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +enum { + UATH_INTR_RX, + UATH_INTR_TX, + UATH_BULK_RX, + UATH_BULK_TX, + UATH_N_XFERS = 4, +}; + +#define UATH_ID_BSS 2 /* Connection ID */ + +#define UATH_RX_DATA_LIST_COUNT 128 +#define UATH_TX_DATA_LIST_COUNT 16 +#define UATH_CMD_LIST_COUNT 60 + +#define UATH_DATA_TIMEOUT 10000 +#define UATH_CMD_TIMEOUT 1000 + +/* flags for sending firmware commands */ +#define UATH_CMD_FLAG_ASYNC (1 << 0) +#define UATH_CMD_FLAG_READ (1 << 1) +#define UATH_CMD_FLAG_MAGIC (1 << 2) + +struct uath_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + u_int64_t wr_tsf; + u_int8_t wr_flags; + u_int8_t wr_rate; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + int8_t wr_antsignal; + int8_t wr_antnoise; + u_int8_t wr_antenna; +} __packed; + +#define UATH_RX_RADIOTAP_PRESENT ( \ + (1 << IEEE80211_RADIOTAP_TSFT) | \ + (1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \ + 0) + +struct uath_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; +} __packed; + +#define UATH_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL)) + +struct uath_data { + struct uath_softc *sc; + uint8_t *buf; + uint16_t buflen; + struct mbuf *m; + struct ieee80211_node *ni; /* NB: tx only */ + STAILQ_ENTRY(uath_data) next; +}; +typedef STAILQ_HEAD(, uath_data) uath_datahead; + +struct uath_cmd { + struct uath_softc *sc; + uint32_t flags; + uint32_t msgid; + uint8_t *buf; + uint16_t buflen; + void *odata; /* NB: tx only */ + int olen; /* space in odata */ + STAILQ_ENTRY(uath_cmd) next; +}; +typedef STAILQ_HEAD(, uath_cmd) uath_cmdhead; + +struct uath_wme_settings { + uint8_t aifsn; + uint8_t logcwmin; + uint8_t logcwmax; + uint16_t txop; +#define UATH_TXOP_TO_US(txop) ((txop) << 5) + uint8_t acm; +}; + +struct uath_devcap { + uint32_t targetVersion; + uint32_t targetRevision; + uint32_t macVersion; + uint32_t macRevision; + uint32_t phyRevision; + uint32_t analog5GhzRevision; + uint32_t analog2GhzRevision; + uint32_t regDomain; + uint32_t regCapBits; + uint32_t countryCode; + uint32_t keyCacheSize; + uint32_t numTxQueues; + uint32_t connectionIdMax; + uint32_t wirelessModes; +#define UATH_WIRELESS_MODE_11A 0x01 +#define UATH_WIRELESS_MODE_TURBO 0x02 +#define UATH_WIRELESS_MODE_11B 0x04 +#define UATH_WIRELESS_MODE_11G 0x08 +#define UATH_WIRELESS_MODE_108G 0x10 + uint32_t chanSpreadSupport; + uint32_t compressSupport; + uint32_t burstSupport; + uint32_t fastFramesSupport; + uint32_t chapTuningSupport; + uint32_t turboGSupport; + uint32_t turboPrimeSupport; + uint32_t deviceType; + uint32_t wmeSupport; + uint32_t low2GhzChan; + uint32_t high2GhzChan; + uint32_t low5GhzChan; + uint32_t high5GhzChan; + uint32_t supportCipherWEP; + uint32_t supportCipherAES_CCM; + uint32_t supportCipherTKIP; + uint32_t supportCipherMicAES_CCM; + uint32_t supportMicTKIP; + uint32_t twiceAntennaGain5G; + uint32_t twiceAntennaGain2G; +}; + +struct uath_stat { + uint32_t st_badchunkseqnum; + uint32_t st_invalidlen; + uint32_t st_multichunk; + uint32_t st_toobigrxpkt; + uint32_t st_stopinprogress; + uint32_t st_crcerr; + uint32_t st_phyerr; + uint32_t st_decrypt_crcerr; + uint32_t st_decrypt_micerr; + uint32_t st_decomperr; + uint32_t st_keyerr; + uint32_t st_err; + /* CMD/RX/TX queues */ + uint32_t st_cmd_active; + uint32_t st_cmd_inactive; + uint32_t st_cmd_pending; + uint32_t st_cmd_waiting; + uint32_t st_rx_active; + uint32_t st_rx_inactive; + uint32_t st_tx_active; + uint32_t st_tx_inactive; + uint32_t st_tx_pending; +}; +#define UATH_STAT_INC(sc, var) (sc)->sc_stat.var++ +#define UATH_STAT_DEC(sc, var) (sc)->sc_stat.var-- + +struct uath_vap { + struct ieee80211vap vap; + int (*newstate)(struct ieee80211vap *, + enum ieee80211_state, int); +}; +#define UATH_VAP(vap) ((struct uath_vap *)(vap)) + +struct uath_softc { + struct ifnet *sc_ifp; + device_t sc_dev; + struct usb_device *sc_udev; + struct mtx sc_mtx; + uint32_t sc_debug; + + struct uath_stat sc_stat; + int (*sc_newstate)(struct ieee80211com *, + enum ieee80211_state, int); + + struct usb_xfer *sc_xfer[UATH_N_XFERS]; + struct uath_cmd sc_cmd[UATH_CMD_LIST_COUNT]; + uath_cmdhead sc_cmd_active; + uath_cmdhead sc_cmd_inactive; + uath_cmdhead sc_cmd_pending; + uath_cmdhead sc_cmd_waiting; + struct uath_data sc_rx[UATH_RX_DATA_LIST_COUNT]; + uath_datahead sc_rx_active; + uath_datahead sc_rx_inactive; + struct uath_data sc_tx[UATH_TX_DATA_LIST_COUNT]; + uath_datahead sc_tx_active; + uath_datahead sc_tx_inactive; + uath_datahead sc_tx_pending; + + uint32_t sc_msgid; + uint32_t sc_seqnum; + int sc_tx_timer; + struct callout watchdog_ch; + struct callout stat_ch; + /* multi-chunked support */ + struct mbuf *sc_intrx_head; + struct mbuf *sc_intrx_tail; + uint8_t sc_intrx_nextnum; + uint32_t sc_intrx_len; +#define UATH_MAX_INTRX_SIZE 3616 + + struct uath_devcap sc_devcap; + uint8_t sc_serial[16]; + + /* unsorted */ + uint32_t sc_flags; +#define UATH_FLAG_INVALID (1 << 1) +#define UATH_FLAG_INITDONE (1 << 2) + + struct uath_rx_radiotap_header sc_rxtap; + int sc_rxtap_len; + struct uath_tx_radiotap_header sc_txtap; + int sc_txtap_len; +}; + +#define UATH_LOCK(sc) mtx_lock(&(sc)->sc_mtx) +#define UATH_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) +#define UATH_ASSERT_LOCKED(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED) + +#define UATH_RESET_INTRX(sc) do { \ + (sc)->sc_intrx_head = NULL; \ + (sc)->sc_intrx_tail = NULL; \ + (sc)->sc_intrx_nextnum = 0; \ + (sc)->sc_intrx_len = 0; \ +} while (0) diff --git a/sys/bus/u4b/wlan/if_upgt.c b/sys/bus/u4b/wlan/if_upgt.c new file mode 100644 index 0000000000..678e9c09a4 --- /dev/null +++ b/sys/bus/u4b/wlan/if_upgt.c @@ -0,0 +1,2396 @@ +/* $OpenBSD: if_upgt.c,v 1.35 2008/04/16 18:32:15 damien Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2007 Marcus Glocker + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include "usbdevs.h" + +#include + +/* + * Driver for the USB PrismGT devices. + * + * For now just USB 2.0 devices with the GW3887 chipset are supported. + * The driver has been written based on the firmware version 2.13.1.0_LM87. + * + * TODO's: + * - MONITOR mode test. + * - Add HOSTAP mode. + * - Add IBSS mode. + * - Support the USB 1.0 devices (NET2280, ISL3880, ISL3886 chipsets). + * + * Parts of this driver has been influenced by reading the p54u driver + * written by Jean-Baptiste Note and + * Sebastien Bourdeauducq . + */ + +static SYSCTL_NODE(_hw, OID_AUTO, upgt, CTLFLAG_RD, 0, + "USB PrismGT GW3887 driver parameters"); + +#ifdef UPGT_DEBUG +int upgt_debug = 0; +SYSCTL_INT(_hw_upgt, OID_AUTO, debug, CTLFLAG_RW, &upgt_debug, + 0, "control debugging printfs"); +TUNABLE_INT("hw.upgt.debug", &upgt_debug); +enum { + UPGT_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ + UPGT_DEBUG_RECV = 0x00000002, /* basic recv operation */ + UPGT_DEBUG_RESET = 0x00000004, /* reset processing */ + UPGT_DEBUG_INTR = 0x00000008, /* INTR */ + UPGT_DEBUG_TX_PROC = 0x00000010, /* tx ISR proc */ + UPGT_DEBUG_RX_PROC = 0x00000020, /* rx ISR proc */ + UPGT_DEBUG_STATE = 0x00000040, /* 802.11 state transitions */ + UPGT_DEBUG_STAT = 0x00000080, /* statistic */ + UPGT_DEBUG_FW = 0x00000100, /* firmware */ + UPGT_DEBUG_ANY = 0xffffffff +}; +#define DPRINTF(sc, m, fmt, ...) do { \ + if (sc->sc_debug & (m)) \ + printf(fmt, __VA_ARGS__); \ +} while (0) +#else +#define DPRINTF(sc, m, fmt, ...) do { \ + (void) sc; \ +} while (0) +#endif + +/* + * Prototypes. + */ +static device_probe_t upgt_match; +static device_attach_t upgt_attach; +static device_detach_t upgt_detach; +static int upgt_alloc_tx(struct upgt_softc *); +static int upgt_alloc_rx(struct upgt_softc *); +static int upgt_device_reset(struct upgt_softc *); +static void upgt_bulk_tx(struct upgt_softc *, struct upgt_data *); +static int upgt_fw_verify(struct upgt_softc *); +static int upgt_mem_init(struct upgt_softc *); +static int upgt_fw_load(struct upgt_softc *); +static int upgt_fw_copy(const uint8_t *, char *, int); +static uint32_t upgt_crc32_le(const void *, size_t); +static struct mbuf * + upgt_rxeof(struct usb_xfer *, struct upgt_data *, int *); +static struct mbuf * + upgt_rx(struct upgt_softc *, uint8_t *, int, int *); +static void upgt_txeof(struct usb_xfer *, struct upgt_data *); +static int upgt_eeprom_read(struct upgt_softc *); +static int upgt_eeprom_parse(struct upgt_softc *); +static void upgt_eeprom_parse_hwrx(struct upgt_softc *, uint8_t *); +static void upgt_eeprom_parse_freq3(struct upgt_softc *, uint8_t *, int); +static void upgt_eeprom_parse_freq4(struct upgt_softc *, uint8_t *, int); +static void upgt_eeprom_parse_freq6(struct upgt_softc *, uint8_t *, int); +static uint32_t upgt_chksum_le(const uint32_t *, size_t); +static void upgt_tx_done(struct upgt_softc *, uint8_t *); +static void upgt_init(void *); +static void upgt_init_locked(struct upgt_softc *); +static int upgt_ioctl(struct ifnet *, u_long, caddr_t); +static void upgt_start(struct ifnet *); +static int upgt_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static void upgt_scan_start(struct ieee80211com *); +static void upgt_scan_end(struct ieee80211com *); +static void upgt_set_channel(struct ieee80211com *); +static struct ieee80211vap *upgt_vap_create(struct ieee80211com *, + const char [IFNAMSIZ], int, enum ieee80211_opmode, int, + const uint8_t [IEEE80211_ADDR_LEN], + const uint8_t [IEEE80211_ADDR_LEN]); +static void upgt_vap_delete(struct ieee80211vap *); +static void upgt_update_mcast(struct ifnet *); +static uint8_t upgt_rx_rate(struct upgt_softc *, const int); +static void upgt_set_multi(void *); +static void upgt_stop(struct upgt_softc *); +static void upgt_setup_rates(struct ieee80211vap *, struct ieee80211com *); +static int upgt_set_macfilter(struct upgt_softc *, uint8_t); +static int upgt_newstate(struct ieee80211vap *, enum ieee80211_state, int); +static void upgt_set_chan(struct upgt_softc *, struct ieee80211_channel *); +static void upgt_set_led(struct upgt_softc *, int); +static void upgt_set_led_blink(void *); +static void upgt_get_stats(struct upgt_softc *); +static void upgt_mem_free(struct upgt_softc *, uint32_t); +static uint32_t upgt_mem_alloc(struct upgt_softc *); +static void upgt_free_tx(struct upgt_softc *); +static void upgt_free_rx(struct upgt_softc *); +static void upgt_watchdog(void *); +static void upgt_abort_xfers(struct upgt_softc *); +static void upgt_abort_xfers_locked(struct upgt_softc *); +static void upgt_sysctl_node(struct upgt_softc *); +static struct upgt_data * + upgt_getbuf(struct upgt_softc *); +static struct upgt_data * + upgt_gettxbuf(struct upgt_softc *); +static int upgt_tx_start(struct upgt_softc *, struct mbuf *, + struct ieee80211_node *, struct upgt_data *); + +static const char *upgt_fwname = "upgt-gw3887"; + +static const STRUCT_USB_HOST_ID upgt_devs[] = { +#define UPGT_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } + /* version 2 devices */ + UPGT_DEV(ACCTON, PRISM_GT), + UPGT_DEV(BELKIN, F5D7050), + UPGT_DEV(CISCOLINKSYS, WUSB54AG), + UPGT_DEV(CONCEPTRONIC, PRISM_GT), + UPGT_DEV(DELL, PRISM_GT_1), + UPGT_DEV(DELL, PRISM_GT_2), + UPGT_DEV(FSC, E5400), + UPGT_DEV(GLOBESPAN, PRISM_GT_1), + UPGT_DEV(GLOBESPAN, PRISM_GT_2), + UPGT_DEV(NETGEAR, WG111V2_2), + UPGT_DEV(INTERSIL, PRISM_GT), + UPGT_DEV(SMC, 2862WG), + UPGT_DEV(USR, USR5422), + UPGT_DEV(WISTRONNEWEB, UR045G), + UPGT_DEV(XYRATEX, PRISM_GT_1), + UPGT_DEV(XYRATEX, PRISM_GT_2), + UPGT_DEV(ZCOM, XG703A), + UPGT_DEV(ZCOM, XM142) +}; + +static usb_callback_t upgt_bulk_rx_callback; +static usb_callback_t upgt_bulk_tx_callback; + +static const struct usb_config upgt_config[UPGT_N_XFERS] = { + [UPGT_BULK_TX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = MCLBYTES, + .flags = { + .ext_buffer = 1, + .force_short_xfer = 1, + .pipe_bof = 1 + }, + .callback = upgt_bulk_tx_callback, + .timeout = UPGT_USB_TIMEOUT, /* ms */ + }, + [UPGT_BULK_RX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = MCLBYTES, + .flags = { + .ext_buffer = 1, + .pipe_bof = 1, + .short_xfer_ok = 1 + }, + .callback = upgt_bulk_rx_callback, + }, +}; + +static int +upgt_match(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != UPGT_CONFIG_INDEX) + return (ENXIO); + if (uaa->info.bIfaceIndex != UPGT_IFACE_INDEX) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(upgt_devs, sizeof(upgt_devs), uaa)); +} + +static int +upgt_attach(device_t dev) +{ + int error; + struct ieee80211com *ic; + struct ifnet *ifp; + struct upgt_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + uint8_t bands, iface_index = UPGT_IFACE_INDEX; + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; +#ifdef UPGT_DEBUG + sc->sc_debug = upgt_debug; +#endif + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK, + MTX_DEF); + callout_init(&sc->sc_led_ch, 0); + callout_init(&sc->sc_watchdog_ch, 0); + + /* Allocate TX and RX xfers. */ + error = upgt_alloc_tx(sc); + if (error) + goto fail1; + error = upgt_alloc_rx(sc); + if (error) + goto fail2; + + error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, + upgt_config, UPGT_N_XFERS, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "could not allocate USB transfers, " + "err=%s\n", usbd_errstr(error)); + goto fail3; + } + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(dev, "can not if_alloc()\n"); + goto fail4; + } + + /* Initialize the device. */ + error = upgt_device_reset(sc); + if (error) + goto fail5; + /* Verify the firmware. */ + error = upgt_fw_verify(sc); + if (error) + goto fail5; + /* Calculate device memory space. */ + if (sc->sc_memaddr_frame_start == 0 || sc->sc_memaddr_frame_end == 0) { + device_printf(dev, + "could not find memory space addresses on FW\n"); + error = EIO; + goto fail5; + } + sc->sc_memaddr_frame_end -= UPGT_MEMSIZE_RX + 1; + sc->sc_memaddr_rx_start = sc->sc_memaddr_frame_end + 1; + + DPRINTF(sc, UPGT_DEBUG_FW, "memory address frame start=0x%08x\n", + sc->sc_memaddr_frame_start); + DPRINTF(sc, UPGT_DEBUG_FW, "memory address frame end=0x%08x\n", + sc->sc_memaddr_frame_end); + DPRINTF(sc, UPGT_DEBUG_FW, "memory address rx start=0x%08x\n", + sc->sc_memaddr_rx_start); + + upgt_mem_init(sc); + + /* Load the firmware. */ + error = upgt_fw_load(sc); + if (error) + goto fail5; + + /* Read the whole EEPROM content and parse it. */ + error = upgt_eeprom_read(sc); + if (error) + goto fail5; + error = upgt_eeprom_parse(sc); + if (error) + goto fail5; + + /* all works related with the device have done here. */ + upgt_abort_xfers(sc); + + /* Setup the 802.11 device. */ + ifp->if_softc = sc; + if_initname(ifp, "upgt", device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_init = upgt_init; + ifp->if_ioctl = upgt_ioctl; + ifp->if_start = upgt_start; + IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); + IFQ_SET_READY(&ifp->if_snd); + + ic = ifp->if_l2com; + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + ic->ic_opmode = IEEE80211_M_STA; + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA /* station mode */ + | IEEE80211_C_MONITOR /* monitor mode */ + | IEEE80211_C_SHPREAMBLE /* short preamble supported */ + | IEEE80211_C_SHSLOT /* short slot time supported */ + | IEEE80211_C_BGSCAN /* capable of bg scanning */ + | IEEE80211_C_WPA /* 802.11i */ + ; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + ieee80211_init_channels(ic, NULL, &bands); + + ieee80211_ifattach(ic, sc->sc_myaddr); + ic->ic_raw_xmit = upgt_raw_xmit; + ic->ic_scan_start = upgt_scan_start; + ic->ic_scan_end = upgt_scan_end; + ic->ic_set_channel = upgt_set_channel; + + ic->ic_vap_create = upgt_vap_create; + ic->ic_vap_delete = upgt_vap_delete; + ic->ic_update_mcast = upgt_update_mcast; + + ieee80211_radiotap_attach(ic, + &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), + UPGT_TX_RADIOTAP_PRESENT, + &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), + UPGT_RX_RADIOTAP_PRESENT); + + upgt_sysctl_node(sc); + + if (bootverbose) + ieee80211_announce(ic); + + return (0); + +fail5: if_free(ifp); +fail4: usbd_transfer_unsetup(sc->sc_xfer, UPGT_N_XFERS); +fail3: upgt_free_rx(sc); +fail2: upgt_free_tx(sc); +fail1: mtx_destroy(&sc->sc_mtx); + + return (error); +} + +static void +upgt_txeof(struct usb_xfer *xfer, struct upgt_data *data) +{ + struct upgt_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + + UPGT_ASSERT_LOCKED(sc); + + /* + * Do any tx complete callback. Note this must be done before releasing + * the node reference. + */ + if (data->m) { + m = data->m; + if (m->m_flags & M_TXCB) { + /* XXX status? */ + ieee80211_process_callback(data->ni, m, 0); + } + m_freem(m); + data->m = NULL; + } + if (data->ni) { + ieee80211_free_node(data->ni); + data->ni = NULL; + } + ifp->if_opackets++; +} + +static void +upgt_get_stats(struct upgt_softc *sc) +{ + struct upgt_data *data_cmd; + struct upgt_lmac_mem *mem; + struct upgt_lmac_stats *stats; + + data_cmd = upgt_getbuf(sc); + if (data_cmd == NULL) { + device_printf(sc->sc_dev, "%s: out of buffer.\n", __func__); + return; + } + + /* + * Transmit the URB containing the CMD data. + */ + memset(data_cmd->buf, 0, MCLBYTES); + + mem = (struct upgt_lmac_mem *)data_cmd->buf; + mem->addr = htole32(sc->sc_memaddr_frame_start + + UPGT_MEMSIZE_FRAME_HEAD); + + stats = (struct upgt_lmac_stats *)(mem + 1); + + stats->header1.flags = 0; + stats->header1.type = UPGT_H1_TYPE_CTRL; + stats->header1.len = htole16( + sizeof(struct upgt_lmac_stats) - sizeof(struct upgt_lmac_header)); + + stats->header2.reqid = htole32(sc->sc_memaddr_frame_start); + stats->header2.type = htole16(UPGT_H2_TYPE_STATS); + stats->header2.flags = 0; + + data_cmd->buflen = sizeof(*mem) + sizeof(*stats); + + mem->chksum = upgt_chksum_le((uint32_t *)stats, + data_cmd->buflen - sizeof(*mem)); + + upgt_bulk_tx(sc, data_cmd); +} + +static int +upgt_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct upgt_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *) data; + int error = 0, startall = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + if ((ifp->if_flags ^ sc->sc_if_flags) & + (IFF_ALLMULTI | IFF_PROMISC)) + upgt_set_multi(sc); + } else { + upgt_init(sc); + startall = 1; + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + upgt_stop(sc); + } + sc->sc_if_flags = ifp->if_flags; + if (startall) + ieee80211_start_all(ic); + break; + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + case SIOCGIFADDR: + error = ether_ioctl(ifp, cmd, data); + break; + default: + error = EINVAL; + break; + } + return error; +} + +static void +upgt_stop_locked(struct upgt_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + + UPGT_ASSERT_LOCKED(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + upgt_set_macfilter(sc, IEEE80211_S_INIT); + upgt_abort_xfers_locked(sc); +} + +static void +upgt_stop(struct upgt_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + + UPGT_LOCK(sc); + upgt_stop_locked(sc); + UPGT_UNLOCK(sc); + + /* device down */ + sc->sc_tx_timer = 0; + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + sc->sc_flags &= ~UPGT_FLAG_INITDONE; +} + +static void +upgt_set_led(struct upgt_softc *sc, int action) +{ + struct upgt_data *data_cmd; + struct upgt_lmac_mem *mem; + struct upgt_lmac_led *led; + + data_cmd = upgt_getbuf(sc); + if (data_cmd == NULL) { + device_printf(sc->sc_dev, "%s: out of buffers.\n", __func__); + return; + } + + /* + * Transmit the URB containing the CMD data. + */ + memset(data_cmd->buf, 0, MCLBYTES); + + mem = (struct upgt_lmac_mem *)data_cmd->buf; + mem->addr = htole32(sc->sc_memaddr_frame_start + + UPGT_MEMSIZE_FRAME_HEAD); + + led = (struct upgt_lmac_led *)(mem + 1); + + led->header1.flags = UPGT_H1_FLAGS_TX_NO_CALLBACK; + led->header1.type = UPGT_H1_TYPE_CTRL; + led->header1.len = htole16( + sizeof(struct upgt_lmac_led) - + sizeof(struct upgt_lmac_header)); + + led->header2.reqid = htole32(sc->sc_memaddr_frame_start); + led->header2.type = htole16(UPGT_H2_TYPE_LED); + led->header2.flags = 0; + + switch (action) { + case UPGT_LED_OFF: + led->mode = htole16(UPGT_LED_MODE_SET); + led->action_fix = 0; + led->action_tmp = htole16(UPGT_LED_ACTION_OFF); + led->action_tmp_dur = 0; + break; + case UPGT_LED_ON: + led->mode = htole16(UPGT_LED_MODE_SET); + led->action_fix = 0; + led->action_tmp = htole16(UPGT_LED_ACTION_ON); + led->action_tmp_dur = 0; + break; + case UPGT_LED_BLINK: + if (sc->sc_state != IEEE80211_S_RUN) { + STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data_cmd, next); + return; + } + if (sc->sc_led_blink) { + /* previous blink was not finished */ + STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data_cmd, next); + return; + } + led->mode = htole16(UPGT_LED_MODE_SET); + led->action_fix = htole16(UPGT_LED_ACTION_OFF); + led->action_tmp = htole16(UPGT_LED_ACTION_ON); + led->action_tmp_dur = htole16(UPGT_LED_ACTION_TMP_DUR); + /* lock blink */ + sc->sc_led_blink = 1; + callout_reset(&sc->sc_led_ch, hz, upgt_set_led_blink, sc); + break; + default: + STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data_cmd, next); + return; + } + + data_cmd->buflen = sizeof(*mem) + sizeof(*led); + + mem->chksum = upgt_chksum_le((uint32_t *)led, + data_cmd->buflen - sizeof(*mem)); + + upgt_bulk_tx(sc, data_cmd); +} + +static void +upgt_set_led_blink(void *arg) +{ + struct upgt_softc *sc = arg; + + /* blink finished, we are ready for a next one */ + sc->sc_led_blink = 0; +} + +static void +upgt_init(void *priv) +{ + struct upgt_softc *sc = priv; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + UPGT_LOCK(sc); + upgt_init_locked(sc); + UPGT_UNLOCK(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ieee80211_start_all(ic); /* start all vap's */ +} + +static void +upgt_init_locked(struct upgt_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + + UPGT_ASSERT_LOCKED(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + upgt_stop_locked(sc); + + usbd_transfer_start(sc->sc_xfer[UPGT_BULK_RX]); + + (void)upgt_set_macfilter(sc, IEEE80211_S_SCAN); + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + sc->sc_flags |= UPGT_FLAG_INITDONE; + + callout_reset(&sc->sc_watchdog_ch, hz, upgt_watchdog, sc); +} + +static int +upgt_set_macfilter(struct upgt_softc *sc, uint8_t state) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct ieee80211_node *ni; + struct upgt_data *data_cmd; + struct upgt_lmac_mem *mem; + struct upgt_lmac_filter *filter; + uint8_t broadcast[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + UPGT_ASSERT_LOCKED(sc); + + data_cmd = upgt_getbuf(sc); + if (data_cmd == NULL) { + device_printf(sc->sc_dev, "out of TX buffers.\n"); + return (ENOBUFS); + } + + /* + * Transmit the URB containing the CMD data. + */ + memset(data_cmd->buf, 0, MCLBYTES); + + mem = (struct upgt_lmac_mem *)data_cmd->buf; + mem->addr = htole32(sc->sc_memaddr_frame_start + + UPGT_MEMSIZE_FRAME_HEAD); + + filter = (struct upgt_lmac_filter *)(mem + 1); + + filter->header1.flags = UPGT_H1_FLAGS_TX_NO_CALLBACK; + filter->header1.type = UPGT_H1_TYPE_CTRL; + filter->header1.len = htole16( + sizeof(struct upgt_lmac_filter) - + sizeof(struct upgt_lmac_header)); + + filter->header2.reqid = htole32(sc->sc_memaddr_frame_start); + filter->header2.type = htole16(UPGT_H2_TYPE_MACFILTER); + filter->header2.flags = 0; + + switch (state) { + case IEEE80211_S_INIT: + DPRINTF(sc, UPGT_DEBUG_STATE, "%s: set MAC filter to INIT\n", + __func__); + filter->type = htole16(UPGT_FILTER_TYPE_RESET); + break; + case IEEE80211_S_SCAN: + DPRINTF(sc, UPGT_DEBUG_STATE, + "set MAC filter to SCAN (bssid %s)\n", + ether_sprintf(broadcast)); + filter->type = htole16(UPGT_FILTER_TYPE_NONE); + IEEE80211_ADDR_COPY(filter->dst, sc->sc_myaddr); + IEEE80211_ADDR_COPY(filter->src, broadcast); + filter->unknown1 = htole16(UPGT_FILTER_UNKNOWN1); + filter->rxaddr = htole32(sc->sc_memaddr_rx_start); + filter->unknown2 = htole16(UPGT_FILTER_UNKNOWN2); + filter->rxhw = htole32(sc->sc_eeprom_hwrx); + filter->unknown3 = htole16(UPGT_FILTER_UNKNOWN3); + break; + case IEEE80211_S_RUN: + ni = ieee80211_ref_node(vap->iv_bss); + /* XXX monitor mode isn't tested yet. */ + if (vap->iv_opmode == IEEE80211_M_MONITOR) { + filter->type = htole16(UPGT_FILTER_TYPE_MONITOR); + IEEE80211_ADDR_COPY(filter->dst, sc->sc_myaddr); + IEEE80211_ADDR_COPY(filter->src, ni->ni_bssid); + filter->unknown1 = htole16(UPGT_FILTER_MONITOR_UNKNOWN1); + filter->rxaddr = htole32(sc->sc_memaddr_rx_start); + filter->unknown2 = htole16(UPGT_FILTER_MONITOR_UNKNOWN2); + filter->rxhw = htole32(sc->sc_eeprom_hwrx); + filter->unknown3 = htole16(UPGT_FILTER_MONITOR_UNKNOWN3); + } else { + DPRINTF(sc, UPGT_DEBUG_STATE, + "set MAC filter to RUN (bssid %s)\n", + ether_sprintf(ni->ni_bssid)); + filter->type = htole16(UPGT_FILTER_TYPE_STA); + IEEE80211_ADDR_COPY(filter->dst, sc->sc_myaddr); + IEEE80211_ADDR_COPY(filter->src, ni->ni_bssid); + filter->unknown1 = htole16(UPGT_FILTER_UNKNOWN1); + filter->rxaddr = htole32(sc->sc_memaddr_rx_start); + filter->unknown2 = htole16(UPGT_FILTER_UNKNOWN2); + filter->rxhw = htole32(sc->sc_eeprom_hwrx); + filter->unknown3 = htole16(UPGT_FILTER_UNKNOWN3); + } + ieee80211_free_node(ni); + break; + default: + device_printf(sc->sc_dev, + "MAC filter does not know that state\n"); + break; + } + + data_cmd->buflen = sizeof(*mem) + sizeof(*filter); + + mem->chksum = upgt_chksum_le((uint32_t *)filter, + data_cmd->buflen - sizeof(*mem)); + + upgt_bulk_tx(sc, data_cmd); + + return (0); +} + +static void +upgt_setup_rates(struct ieee80211vap *vap, struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct upgt_softc *sc = ifp->if_softc; + const struct ieee80211_txparam *tp; + + /* + * 0x01 = OFMD6 0x10 = DS1 + * 0x04 = OFDM9 0x11 = DS2 + * 0x06 = OFDM12 0x12 = DS5 + * 0x07 = OFDM18 0x13 = DS11 + * 0x08 = OFDM24 + * 0x09 = OFDM36 + * 0x0a = OFDM48 + * 0x0b = OFDM54 + */ + const uint8_t rateset_auto_11b[] = + { 0x13, 0x13, 0x12, 0x11, 0x11, 0x10, 0x10, 0x10 }; + const uint8_t rateset_auto_11g[] = + { 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x04, 0x01 }; + const uint8_t rateset_fix_11bg[] = + { 0x10, 0x11, 0x12, 0x13, 0x01, 0x04, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b }; + + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + + /* XXX */ + if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) { + /* + * Automatic rate control is done by the device. + * We just pass the rateset from which the device + * will pickup a rate. + */ + if (ic->ic_curmode == IEEE80211_MODE_11B) + memcpy(sc->sc_cur_rateset, rateset_auto_11b, + sizeof(sc->sc_cur_rateset)); + if (ic->ic_curmode == IEEE80211_MODE_11G || + ic->ic_curmode == IEEE80211_MODE_AUTO) + memcpy(sc->sc_cur_rateset, rateset_auto_11g, + sizeof(sc->sc_cur_rateset)); + } else { + /* set a fixed rate */ + memset(sc->sc_cur_rateset, rateset_fix_11bg[tp->ucastrate], + sizeof(sc->sc_cur_rateset)); + } +} + +static void +upgt_set_multi(void *arg) +{ + struct upgt_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + + if (!(ifp->if_flags & IFF_UP)) + return; + + /* + * XXX don't know how to set a device. Lack of docs. Just try to set + * IFF_ALLMULTI flag here. + */ + ifp->if_flags |= IFF_ALLMULTI; +} + +static void +upgt_start(struct ifnet *ifp) +{ + struct upgt_softc *sc = ifp->if_softc; + struct upgt_data *data_tx; + struct ieee80211_node *ni; + struct mbuf *m; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + UPGT_LOCK(sc); + for (;;) { + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + + data_tx = upgt_gettxbuf(sc); + if (data_tx == NULL) { + IFQ_DRV_PREPEND(&ifp->if_snd, m); + break; + } + + ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; + m->m_pkthdr.rcvif = NULL; + + if (upgt_tx_start(sc, m, ni, data_tx) != 0) { + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, data_tx, next); + UPGT_STAT_INC(sc, st_tx_inactive); + ieee80211_free_node(ni); + ifp->if_oerrors++; + continue; + } + sc->sc_tx_timer = 5; + } + UPGT_UNLOCK(sc); +} + +static int +upgt_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct upgt_softc *sc = ifp->if_softc; + struct upgt_data *data_tx = NULL; + + /* prevent management frames from being sent if we're not ready */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + m_freem(m); + ieee80211_free_node(ni); + return ENETDOWN; + } + + UPGT_LOCK(sc); + data_tx = upgt_gettxbuf(sc); + if (data_tx == NULL) { + ieee80211_free_node(ni); + m_freem(m); + UPGT_UNLOCK(sc); + return (ENOBUFS); + } + + if (upgt_tx_start(sc, m, ni, data_tx) != 0) { + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, data_tx, next); + UPGT_STAT_INC(sc, st_tx_inactive); + ieee80211_free_node(ni); + ifp->if_oerrors++; + UPGT_UNLOCK(sc); + return (EIO); + } + UPGT_UNLOCK(sc); + + sc->sc_tx_timer = 5; + return (0); +} + +static void +upgt_watchdog(void *arg) +{ + struct upgt_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + + if (sc->sc_tx_timer > 0) { + if (--sc->sc_tx_timer == 0) { + device_printf(sc->sc_dev, "watchdog timeout\n"); + /* upgt_init(ifp); XXX needs a process context ? */ + ifp->if_oerrors++; + return; + } + callout_reset(&sc->sc_watchdog_ch, hz, upgt_watchdog, sc); + } +} + +static uint32_t +upgt_mem_alloc(struct upgt_softc *sc) +{ + int i; + + for (i = 0; i < sc->sc_memory.pages; i++) { + if (sc->sc_memory.page[i].used == 0) { + sc->sc_memory.page[i].used = 1; + return (sc->sc_memory.page[i].addr); + } + } + + return (0); +} + +static void +upgt_scan_start(struct ieee80211com *ic) +{ + /* do nothing. */ +} + +static void +upgt_scan_end(struct ieee80211com *ic) +{ + /* do nothing. */ +} + +static void +upgt_set_channel(struct ieee80211com *ic) +{ + struct upgt_softc *sc = ic->ic_ifp->if_softc; + + UPGT_LOCK(sc); + upgt_set_chan(sc, ic->ic_curchan); + UPGT_UNLOCK(sc); +} + +static void +upgt_set_chan(struct upgt_softc *sc, struct ieee80211_channel *c) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct upgt_data *data_cmd; + struct upgt_lmac_mem *mem; + struct upgt_lmac_channel *chan; + int channel; + + UPGT_ASSERT_LOCKED(sc); + + channel = ieee80211_chan2ieee(ic, c); + if (channel == 0 || channel == IEEE80211_CHAN_ANY) { + /* XXX should NEVER happen */ + device_printf(sc->sc_dev, + "%s: invalid channel %x\n", __func__, channel); + return; + } + + DPRINTF(sc, UPGT_DEBUG_STATE, "%s: channel %d\n", __func__, channel); + + data_cmd = upgt_getbuf(sc); + if (data_cmd == NULL) { + device_printf(sc->sc_dev, "%s: out of buffers.\n", __func__); + return; + } + /* + * Transmit the URB containing the CMD data. + */ + memset(data_cmd->buf, 0, MCLBYTES); + + mem = (struct upgt_lmac_mem *)data_cmd->buf; + mem->addr = htole32(sc->sc_memaddr_frame_start + + UPGT_MEMSIZE_FRAME_HEAD); + + chan = (struct upgt_lmac_channel *)(mem + 1); + + chan->header1.flags = UPGT_H1_FLAGS_TX_NO_CALLBACK; + chan->header1.type = UPGT_H1_TYPE_CTRL; + chan->header1.len = htole16( + sizeof(struct upgt_lmac_channel) - sizeof(struct upgt_lmac_header)); + + chan->header2.reqid = htole32(sc->sc_memaddr_frame_start); + chan->header2.type = htole16(UPGT_H2_TYPE_CHANNEL); + chan->header2.flags = 0; + + chan->unknown1 = htole16(UPGT_CHANNEL_UNKNOWN1); + chan->unknown2 = htole16(UPGT_CHANNEL_UNKNOWN2); + chan->freq6 = sc->sc_eeprom_freq6[channel]; + chan->settings = sc->sc_eeprom_freq6_settings; + chan->unknown3 = UPGT_CHANNEL_UNKNOWN3; + + memcpy(chan->freq3_1, &sc->sc_eeprom_freq3[channel].data, + sizeof(chan->freq3_1)); + memcpy(chan->freq4, &sc->sc_eeprom_freq4[channel], + sizeof(sc->sc_eeprom_freq4[channel])); + memcpy(chan->freq3_2, &sc->sc_eeprom_freq3[channel].data, + sizeof(chan->freq3_2)); + + data_cmd->buflen = sizeof(*mem) + sizeof(*chan); + + mem->chksum = upgt_chksum_le((uint32_t *)chan, + data_cmd->buflen - sizeof(*mem)); + + upgt_bulk_tx(sc, data_cmd); +} + +static struct ieee80211vap * +upgt_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, + enum ieee80211_opmode opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct upgt_vap *uvp; + struct ieee80211vap *vap; + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return NULL; + uvp = (struct upgt_vap *) malloc(sizeof(struct upgt_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (uvp == NULL) + return NULL; + vap = &uvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + uvp->newstate = vap->iv_newstate; + vap->iv_newstate = upgt_newstate; + + /* setup device rates */ + upgt_setup_rates(vap, ic); + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, + ieee80211_media_status); + ic->ic_opmode = opmode; + return vap; +} + +static int +upgt_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct upgt_vap *uvp = UPGT_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + struct upgt_softc *sc = ic->ic_ifp->if_softc; + + /* do it in a process context */ + sc->sc_state = nstate; + + IEEE80211_UNLOCK(ic); + UPGT_LOCK(sc); + callout_stop(&sc->sc_led_ch); + callout_stop(&sc->sc_watchdog_ch); + + switch (nstate) { + case IEEE80211_S_INIT: + /* do not accept any frames if the device is down */ + (void)upgt_set_macfilter(sc, sc->sc_state); + upgt_set_led(sc, UPGT_LED_OFF); + break; + case IEEE80211_S_SCAN: + upgt_set_chan(sc, ic->ic_curchan); + break; + case IEEE80211_S_AUTH: + upgt_set_chan(sc, ic->ic_curchan); + break; + case IEEE80211_S_ASSOC: + break; + case IEEE80211_S_RUN: + upgt_set_macfilter(sc, sc->sc_state); + upgt_set_led(sc, UPGT_LED_ON); + break; + default: + break; + } + UPGT_UNLOCK(sc); + IEEE80211_LOCK(ic); + return (uvp->newstate(vap, nstate, arg)); +} + +static void +upgt_vap_delete(struct ieee80211vap *vap) +{ + struct upgt_vap *uvp = UPGT_VAP(vap); + + ieee80211_vap_detach(vap); + free(uvp, M_80211_VAP); +} + +static void +upgt_update_mcast(struct ifnet *ifp) +{ + struct upgt_softc *sc = ifp->if_softc; + + upgt_set_multi(sc); +} + +static int +upgt_eeprom_parse(struct upgt_softc *sc) +{ + struct upgt_eeprom_header *eeprom_header; + struct upgt_eeprom_option *eeprom_option; + uint16_t option_len; + uint16_t option_type; + uint16_t preamble_len; + int option_end = 0; + + /* calculate eeprom options start offset */ + eeprom_header = (struct upgt_eeprom_header *)sc->sc_eeprom; + preamble_len = le16toh(eeprom_header->preamble_len); + eeprom_option = (struct upgt_eeprom_option *)(sc->sc_eeprom + + (sizeof(struct upgt_eeprom_header) + preamble_len)); + + while (!option_end) { + /* the eeprom option length is stored in words */ + option_len = + (le16toh(eeprom_option->len) - 1) * sizeof(uint16_t); + option_type = + le16toh(eeprom_option->type); + + switch (option_type) { + case UPGT_EEPROM_TYPE_NAME: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM name len=%d\n", option_len); + break; + case UPGT_EEPROM_TYPE_SERIAL: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM serial len=%d\n", option_len); + break; + case UPGT_EEPROM_TYPE_MAC: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM mac len=%d\n", option_len); + + IEEE80211_ADDR_COPY(sc->sc_myaddr, eeprom_option->data); + break; + case UPGT_EEPROM_TYPE_HWRX: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM hwrx len=%d\n", option_len); + + upgt_eeprom_parse_hwrx(sc, eeprom_option->data); + break; + case UPGT_EEPROM_TYPE_CHIP: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM chip len=%d\n", option_len); + break; + case UPGT_EEPROM_TYPE_FREQ3: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM freq3 len=%d\n", option_len); + + upgt_eeprom_parse_freq3(sc, eeprom_option->data, + option_len); + break; + case UPGT_EEPROM_TYPE_FREQ4: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM freq4 len=%d\n", option_len); + + upgt_eeprom_parse_freq4(sc, eeprom_option->data, + option_len); + break; + case UPGT_EEPROM_TYPE_FREQ5: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM freq5 len=%d\n", option_len); + break; + case UPGT_EEPROM_TYPE_FREQ6: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM freq6 len=%d\n", option_len); + + upgt_eeprom_parse_freq6(sc, eeprom_option->data, + option_len); + break; + case UPGT_EEPROM_TYPE_END: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM end len=%d\n", option_len); + option_end = 1; + break; + case UPGT_EEPROM_TYPE_OFF: + DPRINTF(sc, UPGT_DEBUG_FW, + "%s: EEPROM off without end option\n", __func__); + return (EIO); + default: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM unknown type 0x%04x len=%d\n", + option_type, option_len); + break; + } + + /* jump to next EEPROM option */ + eeprom_option = (struct upgt_eeprom_option *) + (eeprom_option->data + option_len); + } + + return (0); +} + +static void +upgt_eeprom_parse_freq3(struct upgt_softc *sc, uint8_t *data, int len) +{ + struct upgt_eeprom_freq3_header *freq3_header; + struct upgt_lmac_freq3 *freq3; + int i, elements, flags; + unsigned channel; + + freq3_header = (struct upgt_eeprom_freq3_header *)data; + freq3 = (struct upgt_lmac_freq3 *)(freq3_header + 1); + + flags = freq3_header->flags; + elements = freq3_header->elements; + + DPRINTF(sc, UPGT_DEBUG_FW, "flags=0x%02x elements=%d\n", + flags, elements); + + for (i = 0; i < elements; i++) { + channel = ieee80211_mhz2ieee(le16toh(freq3[i].freq), 0); + if (!(channel >= 0 && channel < IEEE80211_CHAN_MAX)) + continue; + + sc->sc_eeprom_freq3[channel] = freq3[i]; + + DPRINTF(sc, UPGT_DEBUG_FW, "frequence=%d, channel=%d\n", + le16toh(sc->sc_eeprom_freq3[channel].freq), channel); + } +} + +void +upgt_eeprom_parse_freq4(struct upgt_softc *sc, uint8_t *data, int len) +{ + struct upgt_eeprom_freq4_header *freq4_header; + struct upgt_eeprom_freq4_1 *freq4_1; + struct upgt_eeprom_freq4_2 *freq4_2; + int i, j, elements, settings, flags; + unsigned channel; + + freq4_header = (struct upgt_eeprom_freq4_header *)data; + freq4_1 = (struct upgt_eeprom_freq4_1 *)(freq4_header + 1); + flags = freq4_header->flags; + elements = freq4_header->elements; + settings = freq4_header->settings; + + /* we need this value later */ + sc->sc_eeprom_freq6_settings = freq4_header->settings; + + DPRINTF(sc, UPGT_DEBUG_FW, "flags=0x%02x elements=%d settings=%d\n", + flags, elements, settings); + + for (i = 0; i < elements; i++) { + channel = ieee80211_mhz2ieee(le16toh(freq4_1[i].freq), 0); + if (!(channel >= 0 && channel < IEEE80211_CHAN_MAX)) + continue; + + freq4_2 = (struct upgt_eeprom_freq4_2 *)freq4_1[i].data; + for (j = 0; j < settings; j++) { + sc->sc_eeprom_freq4[channel][j].cmd = freq4_2[j]; + sc->sc_eeprom_freq4[channel][j].pad = 0; + } + + DPRINTF(sc, UPGT_DEBUG_FW, "frequence=%d, channel=%d\n", + le16toh(freq4_1[i].freq), channel); + } +} + +void +upgt_eeprom_parse_freq6(struct upgt_softc *sc, uint8_t *data, int len) +{ + struct upgt_lmac_freq6 *freq6; + int i, elements; + unsigned channel; + + freq6 = (struct upgt_lmac_freq6 *)data; + elements = len / sizeof(struct upgt_lmac_freq6); + + DPRINTF(sc, UPGT_DEBUG_FW, "elements=%d\n", elements); + + for (i = 0; i < elements; i++) { + channel = ieee80211_mhz2ieee(le16toh(freq6[i].freq), 0); + if (!(channel >= 0 && channel < IEEE80211_CHAN_MAX)) + continue; + + sc->sc_eeprom_freq6[channel] = freq6[i]; + + DPRINTF(sc, UPGT_DEBUG_FW, "frequence=%d, channel=%d\n", + le16toh(sc->sc_eeprom_freq6[channel].freq), channel); + } +} + +static void +upgt_eeprom_parse_hwrx(struct upgt_softc *sc, uint8_t *data) +{ + struct upgt_eeprom_option_hwrx *option_hwrx; + + option_hwrx = (struct upgt_eeprom_option_hwrx *)data; + + sc->sc_eeprom_hwrx = option_hwrx->rxfilter - UPGT_EEPROM_RX_CONST; + + DPRINTF(sc, UPGT_DEBUG_FW, "hwrx option value=0x%04x\n", + sc->sc_eeprom_hwrx); +} + +static int +upgt_eeprom_read(struct upgt_softc *sc) +{ + struct upgt_data *data_cmd; + struct upgt_lmac_mem *mem; + struct upgt_lmac_eeprom *eeprom; + int block, error, offset; + + UPGT_LOCK(sc); + usb_pause_mtx(&sc->sc_mtx, 100); + + offset = 0; + block = UPGT_EEPROM_BLOCK_SIZE; + while (offset < UPGT_EEPROM_SIZE) { + DPRINTF(sc, UPGT_DEBUG_FW, + "request EEPROM block (offset=%d, len=%d)\n", offset, block); + + data_cmd = upgt_getbuf(sc); + if (data_cmd == NULL) { + UPGT_UNLOCK(sc); + return (ENOBUFS); + } + + /* + * Transmit the URB containing the CMD data. + */ + memset(data_cmd->buf, 0, MCLBYTES); + + mem = (struct upgt_lmac_mem *)data_cmd->buf; + mem->addr = htole32(sc->sc_memaddr_frame_start + + UPGT_MEMSIZE_FRAME_HEAD); + + eeprom = (struct upgt_lmac_eeprom *)(mem + 1); + eeprom->header1.flags = 0; + eeprom->header1.type = UPGT_H1_TYPE_CTRL; + eeprom->header1.len = htole16(( + sizeof(struct upgt_lmac_eeprom) - + sizeof(struct upgt_lmac_header)) + block); + + eeprom->header2.reqid = htole32(sc->sc_memaddr_frame_start); + eeprom->header2.type = htole16(UPGT_H2_TYPE_EEPROM); + eeprom->header2.flags = 0; + + eeprom->offset = htole16(offset); + eeprom->len = htole16(block); + + data_cmd->buflen = sizeof(*mem) + sizeof(*eeprom) + block; + + mem->chksum = upgt_chksum_le((uint32_t *)eeprom, + data_cmd->buflen - sizeof(*mem)); + upgt_bulk_tx(sc, data_cmd); + + error = mtx_sleep(sc, &sc->sc_mtx, 0, "eeprom_request", hz); + if (error != 0) { + device_printf(sc->sc_dev, + "timeout while waiting for EEPROM data\n"); + UPGT_UNLOCK(sc); + return (EIO); + } + + offset += block; + if (UPGT_EEPROM_SIZE - offset < block) + block = UPGT_EEPROM_SIZE - offset; + } + + UPGT_UNLOCK(sc); + return (0); +} + +/* + * When a rx data came in the function returns a mbuf and a rssi values. + */ +static struct mbuf * +upgt_rxeof(struct usb_xfer *xfer, struct upgt_data *data, int *rssi) +{ + struct mbuf *m = NULL; + struct upgt_softc *sc = usbd_xfer_softc(xfer); + struct upgt_lmac_header *header; + struct upgt_lmac_eeprom *eeprom; + uint8_t h1_type; + uint16_t h2_type; + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + UPGT_ASSERT_LOCKED(sc); + + if (actlen < 1) + return (NULL); + + /* Check only at the very beginning. */ + if (!(sc->sc_flags & UPGT_FLAG_FWLOADED) && + (memcmp(data->buf, "OK", 2) == 0)) { + sc->sc_flags |= UPGT_FLAG_FWLOADED; + wakeup_one(sc); + return (NULL); + } + + if (actlen < UPGT_RX_MINSZ) + return (NULL); + + /* + * Check what type of frame came in. + */ + header = (struct upgt_lmac_header *)(data->buf + 4); + + h1_type = header->header1.type; + h2_type = le16toh(header->header2.type); + + if (h1_type == UPGT_H1_TYPE_CTRL && h2_type == UPGT_H2_TYPE_EEPROM) { + eeprom = (struct upgt_lmac_eeprom *)(data->buf + 4); + uint16_t eeprom_offset = le16toh(eeprom->offset); + uint16_t eeprom_len = le16toh(eeprom->len); + + DPRINTF(sc, UPGT_DEBUG_FW, + "received EEPROM block (offset=%d, len=%d)\n", + eeprom_offset, eeprom_len); + + memcpy(sc->sc_eeprom + eeprom_offset, + data->buf + sizeof(struct upgt_lmac_eeprom) + 4, + eeprom_len); + + /* EEPROM data has arrived in time, wakeup. */ + wakeup(sc); + } else if (h1_type == UPGT_H1_TYPE_CTRL && + h2_type == UPGT_H2_TYPE_TX_DONE) { + DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: received 802.11 TX done\n", + __func__); + upgt_tx_done(sc, data->buf + 4); + } else if (h1_type == UPGT_H1_TYPE_RX_DATA || + h1_type == UPGT_H1_TYPE_RX_DATA_MGMT) { + DPRINTF(sc, UPGT_DEBUG_RECV, "%s: received 802.11 RX data\n", + __func__); + m = upgt_rx(sc, data->buf + 4, le16toh(header->header1.len), + rssi); + } else if (h1_type == UPGT_H1_TYPE_CTRL && + h2_type == UPGT_H2_TYPE_STATS) { + DPRINTF(sc, UPGT_DEBUG_STAT, "%s: received statistic data\n", + __func__); + /* TODO: what could we do with the statistic data? */ + } else { + /* ignore unknown frame types */ + DPRINTF(sc, UPGT_DEBUG_INTR, + "received unknown frame type 0x%02x\n", + header->header1.type); + } + return (m); +} + +/* + * The firmware awaits a checksum for each frame we send to it. + * The algorithm used therefor is uncommon but somehow similar to CRC32. + */ +static uint32_t +upgt_chksum_le(const uint32_t *buf, size_t size) +{ + int i; + uint32_t crc = 0; + + for (i = 0; i < size; i += sizeof(uint32_t)) { + crc = htole32(crc ^ *buf++); + crc = htole32((crc >> 5) ^ (crc << 3)); + } + + return (crc); +} + +static struct mbuf * +upgt_rx(struct upgt_softc *sc, uint8_t *data, int pkglen, int *rssi) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct upgt_lmac_rx_desc *rxdesc; + struct mbuf *m; + + /* + * don't pass packets to the ieee80211 framework if the driver isn't + * RUNNING. + */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + return (NULL); + + /* access RX packet descriptor */ + rxdesc = (struct upgt_lmac_rx_desc *)data; + + /* create mbuf which is suitable for strict alignment archs */ + KASSERT((pkglen + ETHER_ALIGN) < MCLBYTES, + ("A current mbuf storage is small (%d)", pkglen + ETHER_ALIGN)); + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (m == NULL) { + device_printf(sc->sc_dev, "could not create RX mbuf\n"); + return (NULL); + } + m_adj(m, ETHER_ALIGN); + memcpy(mtod(m, char *), rxdesc->data, pkglen); + /* trim FCS */ + m->m_len = m->m_pkthdr.len = pkglen - IEEE80211_CRC_LEN; + m->m_pkthdr.rcvif = ifp; + + if (ieee80211_radiotap_active(ic)) { + struct upgt_rx_radiotap_header *tap = &sc->sc_rxtap; + + tap->wr_flags = 0; + tap->wr_rate = upgt_rx_rate(sc, rxdesc->rate); + tap->wr_antsignal = rxdesc->rssi; + } + ifp->if_ipackets++; + + DPRINTF(sc, UPGT_DEBUG_RX_PROC, "%s: RX done\n", __func__); + *rssi = rxdesc->rssi; + return (m); +} + +static uint8_t +upgt_rx_rate(struct upgt_softc *sc, const int rate) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + static const uint8_t cck_upgt2rate[4] = { 2, 4, 11, 22 }; + static const uint8_t ofdm_upgt2rate[12] = + { 2, 4, 11, 22, 12, 18, 24, 36, 48, 72, 96, 108 }; + + if (ic->ic_curmode == IEEE80211_MODE_11B && + !(rate < 0 || rate > 3)) + return cck_upgt2rate[rate & 0xf]; + + if (ic->ic_curmode == IEEE80211_MODE_11G && + !(rate < 0 || rate > 11)) + return ofdm_upgt2rate[rate & 0xf]; + + return (0); +} + +static void +upgt_tx_done(struct upgt_softc *sc, uint8_t *data) +{ + struct ifnet *ifp = sc->sc_ifp; + struct upgt_lmac_tx_done_desc *desc; + int i, freed = 0; + + UPGT_ASSERT_LOCKED(sc); + + desc = (struct upgt_lmac_tx_done_desc *)data; + + for (i = 0; i < UPGT_TX_MAXCOUNT; i++) { + struct upgt_data *data_tx = &sc->sc_tx_data[i]; + + if (data_tx->addr == le32toh(desc->header2.reqid)) { + upgt_mem_free(sc, data_tx->addr); + data_tx->ni = NULL; + data_tx->addr = 0; + data_tx->m = NULL; + data_tx->use = 0; + + DPRINTF(sc, UPGT_DEBUG_TX_PROC, + "TX done: memaddr=0x%08x, status=0x%04x, rssi=%d, ", + le32toh(desc->header2.reqid), + le16toh(desc->status), le16toh(desc->rssi)); + DPRINTF(sc, UPGT_DEBUG_TX_PROC, "seq=%d\n", + le16toh(desc->seq)); + + freed++; + } + } + + if (freed != 0) { + sc->sc_tx_timer = 0; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + UPGT_UNLOCK(sc); + upgt_start(ifp); + UPGT_LOCK(sc); + } +} + +static void +upgt_mem_free(struct upgt_softc *sc, uint32_t addr) +{ + int i; + + for (i = 0; i < sc->sc_memory.pages; i++) { + if (sc->sc_memory.page[i].addr == addr) { + sc->sc_memory.page[i].used = 0; + return; + } + } + + device_printf(sc->sc_dev, + "could not free memory address 0x%08x\n", addr); +} + +static int +upgt_fw_load(struct upgt_softc *sc) +{ + const struct firmware *fw; + struct upgt_data *data_cmd; + struct upgt_fw_x2_header *x2; + char start_fwload_cmd[] = { 0x3c, 0x0d }; + int error = 0, offset, bsize, n; + uint32_t crc32; + + fw = firmware_get(upgt_fwname); + if (fw == NULL) { + device_printf(sc->sc_dev, "could not read microcode %s\n", + upgt_fwname); + return (EIO); + } + + UPGT_LOCK(sc); + + /* send firmware start load command */ + data_cmd = upgt_getbuf(sc); + if (data_cmd == NULL) { + error = ENOBUFS; + goto fail; + } + data_cmd->buflen = sizeof(start_fwload_cmd); + memcpy(data_cmd->buf, start_fwload_cmd, data_cmd->buflen); + upgt_bulk_tx(sc, data_cmd); + + /* send X2 header */ + data_cmd = upgt_getbuf(sc); + if (data_cmd == NULL) { + error = ENOBUFS; + goto fail; + } + data_cmd->buflen = sizeof(struct upgt_fw_x2_header); + x2 = (struct upgt_fw_x2_header *)data_cmd->buf; + memcpy(x2->signature, UPGT_X2_SIGNATURE, UPGT_X2_SIGNATURE_SIZE); + x2->startaddr = htole32(UPGT_MEMADDR_FIRMWARE_START); + x2->len = htole32(fw->datasize); + x2->crc = upgt_crc32_le((uint8_t *)data_cmd->buf + + UPGT_X2_SIGNATURE_SIZE, + sizeof(struct upgt_fw_x2_header) - UPGT_X2_SIGNATURE_SIZE - + sizeof(uint32_t)); + upgt_bulk_tx(sc, data_cmd); + + /* download firmware */ + for (offset = 0; offset < fw->datasize; offset += bsize) { + if (fw->datasize - offset > UPGT_FW_BLOCK_SIZE) + bsize = UPGT_FW_BLOCK_SIZE; + else + bsize = fw->datasize - offset; + + data_cmd = upgt_getbuf(sc); + if (data_cmd == NULL) { + error = ENOBUFS; + goto fail; + } + n = upgt_fw_copy((const uint8_t *)fw->data + offset, + data_cmd->buf, bsize); + data_cmd->buflen = bsize; + upgt_bulk_tx(sc, data_cmd); + + DPRINTF(sc, UPGT_DEBUG_FW, "FW offset=%d, read=%d, sent=%d\n", + offset, n, bsize); + bsize = n; + } + DPRINTF(sc, UPGT_DEBUG_FW, "%s: firmware downloaded\n", __func__); + + /* load firmware */ + data_cmd = upgt_getbuf(sc); + if (data_cmd == NULL) { + error = ENOBUFS; + goto fail; + } + crc32 = upgt_crc32_le(fw->data, fw->datasize); + *((uint32_t *)(data_cmd->buf) ) = crc32; + *((uint8_t *)(data_cmd->buf) + 4) = 'g'; + *((uint8_t *)(data_cmd->buf) + 5) = '\r'; + data_cmd->buflen = 6; + upgt_bulk_tx(sc, data_cmd); + + /* waiting 'OK' response. */ + usbd_transfer_start(sc->sc_xfer[UPGT_BULK_RX]); + error = mtx_sleep(sc, &sc->sc_mtx, 0, "upgtfw", 2 * hz); + if (error != 0) { + device_printf(sc->sc_dev, "firmware load failed\n"); + error = EIO; + } + + DPRINTF(sc, UPGT_DEBUG_FW, "%s: firmware loaded\n", __func__); +fail: + UPGT_UNLOCK(sc); + firmware_put(fw, FIRMWARE_UNLOAD); + return (error); +} + +static uint32_t +upgt_crc32_le(const void *buf, size_t size) +{ + uint32_t crc; + + crc = ether_crc32_le(buf, size); + + /* apply final XOR value as common for CRC-32 */ + crc = htole32(crc ^ 0xffffffffU); + + return (crc); +} + +/* + * While copying the version 2 firmware, we need to replace two characters: + * + * 0x7e -> 0x7d 0x5e + * 0x7d -> 0x7d 0x5d + */ +static int +upgt_fw_copy(const uint8_t *src, char *dst, int size) +{ + int i, j; + + for (i = 0, j = 0; i < size && j < size; i++) { + switch (src[i]) { + case 0x7e: + dst[j] = 0x7d; + j++; + dst[j] = 0x5e; + j++; + break; + case 0x7d: + dst[j] = 0x7d; + j++; + dst[j] = 0x5d; + j++; + break; + default: + dst[j] = src[i]; + j++; + break; + } + } + + return (i); +} + +static int +upgt_mem_init(struct upgt_softc *sc) +{ + int i; + + for (i = 0; i < UPGT_MEMORY_MAX_PAGES; i++) { + sc->sc_memory.page[i].used = 0; + + if (i == 0) { + /* + * The first memory page is always reserved for + * command data. + */ + sc->sc_memory.page[i].addr = + sc->sc_memaddr_frame_start + MCLBYTES; + } else { + sc->sc_memory.page[i].addr = + sc->sc_memory.page[i - 1].addr + MCLBYTES; + } + + if (sc->sc_memory.page[i].addr + MCLBYTES >= + sc->sc_memaddr_frame_end) + break; + + DPRINTF(sc, UPGT_DEBUG_FW, "memory address page %d=0x%08x\n", + i, sc->sc_memory.page[i].addr); + } + + sc->sc_memory.pages = i; + + DPRINTF(sc, UPGT_DEBUG_FW, "memory pages=%d\n", sc->sc_memory.pages); + return (0); +} + +static int +upgt_fw_verify(struct upgt_softc *sc) +{ + const struct firmware *fw; + const struct upgt_fw_bra_option *bra_opt; + const struct upgt_fw_bra_descr *descr; + const uint8_t *p; + const uint32_t *uc; + uint32_t bra_option_type, bra_option_len; + int offset, bra_end = 0, error = 0; + + fw = firmware_get(upgt_fwname); + if (fw == NULL) { + device_printf(sc->sc_dev, "could not read microcode %s\n", + upgt_fwname); + return EIO; + } + + /* + * Seek to beginning of Boot Record Area (BRA). + */ + for (offset = 0; offset < fw->datasize; offset += sizeof(*uc)) { + uc = (const uint32_t *)((const uint8_t *)fw->data + offset); + if (*uc == 0) + break; + } + for (; offset < fw->datasize; offset += sizeof(*uc)) { + uc = (const uint32_t *)((const uint8_t *)fw->data + offset); + if (*uc != 0) + break; + } + if (offset == fw->datasize) { + device_printf(sc->sc_dev, + "firmware Boot Record Area not found\n"); + error = EIO; + goto fail; + } + + DPRINTF(sc, UPGT_DEBUG_FW, + "firmware Boot Record Area found at offset %d\n", offset); + + /* + * Parse Boot Record Area (BRA) options. + */ + while (offset < fw->datasize && bra_end == 0) { + /* get current BRA option */ + p = (const uint8_t *)fw->data + offset; + bra_opt = (const struct upgt_fw_bra_option *)p; + bra_option_type = le32toh(bra_opt->type); + bra_option_len = le32toh(bra_opt->len) * sizeof(*uc); + + switch (bra_option_type) { + case UPGT_BRA_TYPE_FW: + DPRINTF(sc, UPGT_DEBUG_FW, "UPGT_BRA_TYPE_FW len=%d\n", + bra_option_len); + + if (bra_option_len != UPGT_BRA_FWTYPE_SIZE) { + device_printf(sc->sc_dev, + "wrong UPGT_BRA_TYPE_FW len\n"); + error = EIO; + goto fail; + } + if (memcmp(UPGT_BRA_FWTYPE_LM86, bra_opt->data, + bra_option_len) == 0) { + sc->sc_fw_type = UPGT_FWTYPE_LM86; + break; + } + if (memcmp(UPGT_BRA_FWTYPE_LM87, bra_opt->data, + bra_option_len) == 0) { + sc->sc_fw_type = UPGT_FWTYPE_LM87; + break; + } + device_printf(sc->sc_dev, + "unsupported firmware type\n"); + error = EIO; + goto fail; + case UPGT_BRA_TYPE_VERSION: + DPRINTF(sc, UPGT_DEBUG_FW, + "UPGT_BRA_TYPE_VERSION len=%d\n", bra_option_len); + break; + case UPGT_BRA_TYPE_DEPIF: + DPRINTF(sc, UPGT_DEBUG_FW, + "UPGT_BRA_TYPE_DEPIF len=%d\n", bra_option_len); + break; + case UPGT_BRA_TYPE_EXPIF: + DPRINTF(sc, UPGT_DEBUG_FW, + "UPGT_BRA_TYPE_EXPIF len=%d\n", bra_option_len); + break; + case UPGT_BRA_TYPE_DESCR: + DPRINTF(sc, UPGT_DEBUG_FW, + "UPGT_BRA_TYPE_DESCR len=%d\n", bra_option_len); + + descr = (const struct upgt_fw_bra_descr *)bra_opt->data; + + sc->sc_memaddr_frame_start = + le32toh(descr->memaddr_space_start); + sc->sc_memaddr_frame_end = + le32toh(descr->memaddr_space_end); + + DPRINTF(sc, UPGT_DEBUG_FW, + "memory address space start=0x%08x\n", + sc->sc_memaddr_frame_start); + DPRINTF(sc, UPGT_DEBUG_FW, + "memory address space end=0x%08x\n", + sc->sc_memaddr_frame_end); + break; + case UPGT_BRA_TYPE_END: + DPRINTF(sc, UPGT_DEBUG_FW, "UPGT_BRA_TYPE_END len=%d\n", + bra_option_len); + bra_end = 1; + break; + default: + DPRINTF(sc, UPGT_DEBUG_FW, "unknown BRA option len=%d\n", + bra_option_len); + error = EIO; + goto fail; + } + + /* jump to next BRA option */ + offset += sizeof(struct upgt_fw_bra_option) + bra_option_len; + } + + DPRINTF(sc, UPGT_DEBUG_FW, "%s: firmware verified", __func__); +fail: + firmware_put(fw, FIRMWARE_UNLOAD); + return (error); +} + +static void +upgt_bulk_tx(struct upgt_softc *sc, struct upgt_data *data) +{ + + UPGT_ASSERT_LOCKED(sc); + + STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next); + UPGT_STAT_INC(sc, st_tx_pending); + usbd_transfer_start(sc->sc_xfer[UPGT_BULK_TX]); +} + +static int +upgt_device_reset(struct upgt_softc *sc) +{ + struct upgt_data *data; + char init_cmd[] = { 0x7e, 0x7e, 0x7e, 0x7e }; + + UPGT_LOCK(sc); + + data = upgt_getbuf(sc); + if (data == NULL) { + UPGT_UNLOCK(sc); + return (ENOBUFS); + } + memcpy(data->buf, init_cmd, sizeof(init_cmd)); + data->buflen = sizeof(init_cmd); + upgt_bulk_tx(sc, data); + usb_pause_mtx(&sc->sc_mtx, 100); + + UPGT_UNLOCK(sc); + DPRINTF(sc, UPGT_DEBUG_FW, "%s: device initialized\n", __func__); + return (0); +} + +static int +upgt_alloc_tx(struct upgt_softc *sc) +{ + int i; + + STAILQ_INIT(&sc->sc_tx_active); + STAILQ_INIT(&sc->sc_tx_inactive); + STAILQ_INIT(&sc->sc_tx_pending); + + for (i = 0; i < UPGT_TX_MAXCOUNT; i++) { + struct upgt_data *data = &sc->sc_tx_data[i]; + + data->buf = malloc(MCLBYTES, M_USBDEV, M_NOWAIT | M_ZERO); + if (data->buf == NULL) { + device_printf(sc->sc_dev, + "could not allocate TX buffer\n"); + return (ENOMEM); + } + STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next); + UPGT_STAT_INC(sc, st_tx_inactive); + } + + return (0); +} + +static int +upgt_alloc_rx(struct upgt_softc *sc) +{ + int i; + + STAILQ_INIT(&sc->sc_rx_active); + STAILQ_INIT(&sc->sc_rx_inactive); + + for (i = 0; i < UPGT_RX_MAXCOUNT; i++) { + struct upgt_data *data = &sc->sc_rx_data[i]; + + data->buf = malloc(MCLBYTES, M_USBDEV, M_NOWAIT | M_ZERO); + if (data->buf == NULL) { + device_printf(sc->sc_dev, + "could not allocate RX buffer\n"); + return (ENOMEM); + } + STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); + } + + return (0); +} + +static int +upgt_detach(device_t dev) +{ + struct upgt_softc *sc = device_get_softc(dev); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + if (!device_is_attached(dev)) + return 0; + + upgt_stop(sc); + + callout_drain(&sc->sc_led_ch); + callout_drain(&sc->sc_watchdog_ch); + + usbd_transfer_unsetup(sc->sc_xfer, UPGT_N_XFERS); + ieee80211_ifdetach(ic); + upgt_free_rx(sc); + upgt_free_tx(sc); + + if_free(ifp); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +upgt_free_rx(struct upgt_softc *sc) +{ + int i; + + for (i = 0; i < UPGT_RX_MAXCOUNT; i++) { + struct upgt_data *data = &sc->sc_rx_data[i]; + + free(data->buf, M_USBDEV); + data->ni = NULL; + } +} + +static void +upgt_free_tx(struct upgt_softc *sc) +{ + int i; + + for (i = 0; i < UPGT_TX_MAXCOUNT; i++) { + struct upgt_data *data = &sc->sc_tx_data[i]; + + free(data->buf, M_USBDEV); + data->ni = NULL; + } +} + +static void +upgt_abort_xfers_locked(struct upgt_softc *sc) +{ + int i; + + UPGT_ASSERT_LOCKED(sc); + /* abort any pending transfers */ + for (i = 0; i < UPGT_N_XFERS; i++) + usbd_transfer_stop(sc->sc_xfer[i]); +} + +static void +upgt_abort_xfers(struct upgt_softc *sc) +{ + + UPGT_LOCK(sc); + upgt_abort_xfers_locked(sc); + UPGT_UNLOCK(sc); +} + +#define UPGT_SYSCTL_STAT_ADD32(c, h, n, p, d) \ + SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d) + +static void +upgt_sysctl_node(struct upgt_softc *sc) +{ + struct sysctl_ctx_list *ctx; + struct sysctl_oid_list *child; + struct sysctl_oid *tree; + struct upgt_stat *stats; + + stats = &sc->sc_stat; + ctx = device_get_sysctl_ctx(sc->sc_dev); + child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)); + + tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", CTLFLAG_RD, + NULL, "UPGT statistics"); + child = SYSCTL_CHILDREN(tree); + UPGT_SYSCTL_STAT_ADD32(ctx, child, "tx_active", + &stats->st_tx_active, "Active numbers in TX queue"); + UPGT_SYSCTL_STAT_ADD32(ctx, child, "tx_inactive", + &stats->st_tx_inactive, "Inactive numbers in TX queue"); + UPGT_SYSCTL_STAT_ADD32(ctx, child, "tx_pending", + &stats->st_tx_pending, "Pending numbers in TX queue"); +} + +#undef UPGT_SYSCTL_STAT_ADD32 + +static struct upgt_data * +_upgt_getbuf(struct upgt_softc *sc) +{ + struct upgt_data *bf; + + bf = STAILQ_FIRST(&sc->sc_tx_inactive); + if (bf != NULL) { + STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next); + UPGT_STAT_DEC(sc, st_tx_inactive); + } else + bf = NULL; + if (bf == NULL) + DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: %s\n", __func__, + "out of xmit buffers"); + return (bf); +} + +static struct upgt_data * +upgt_getbuf(struct upgt_softc *sc) +{ + struct upgt_data *bf; + + UPGT_ASSERT_LOCKED(sc); + + bf = _upgt_getbuf(sc); + if (bf == NULL) { + struct ifnet *ifp = sc->sc_ifp; + + DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: stop queue\n", __func__); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + } + + return (bf); +} + +static struct upgt_data * +upgt_gettxbuf(struct upgt_softc *sc) +{ + struct upgt_data *bf; + + UPGT_ASSERT_LOCKED(sc); + + bf = upgt_getbuf(sc); + if (bf == NULL) + return (NULL); + + bf->addr = upgt_mem_alloc(sc); + if (bf->addr == 0) { + struct ifnet *ifp = sc->sc_ifp; + + DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: no free prism memory!\n", + __func__); + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); + UPGT_STAT_INC(sc, st_tx_inactive); + if (!(ifp->if_drv_flags & IFF_DRV_OACTIVE)) + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + return (NULL); + } + return (bf); +} + +static int +upgt_tx_start(struct upgt_softc *sc, struct mbuf *m, struct ieee80211_node *ni, + struct upgt_data *data) +{ + struct ieee80211vap *vap = ni->ni_vap; + int error = 0, len; + struct ieee80211_frame *wh; + struct ieee80211_key *k; + struct ifnet *ifp = sc->sc_ifp; + struct upgt_lmac_mem *mem; + struct upgt_lmac_tx_desc *txdesc; + + UPGT_ASSERT_LOCKED(sc); + + upgt_set_led(sc, UPGT_LED_BLINK); + + /* + * Software crypto. + */ + wh = mtod(m, struct ieee80211_frame *); + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m); + if (k == NULL) { + device_printf(sc->sc_dev, + "ieee80211_crypto_encap returns NULL.\n"); + error = EIO; + goto done; + } + + /* in case packet header moved, reset pointer */ + wh = mtod(m, struct ieee80211_frame *); + } + + /* Transmit the URB containing the TX data. */ + memset(data->buf, 0, MCLBYTES); + mem = (struct upgt_lmac_mem *)data->buf; + mem->addr = htole32(data->addr); + txdesc = (struct upgt_lmac_tx_desc *)(mem + 1); + + if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == + IEEE80211_FC0_TYPE_MGT) { + /* mgmt frames */ + txdesc->header1.flags = UPGT_H1_FLAGS_TX_MGMT; + /* always send mgmt frames at lowest rate (DS1) */ + memset(txdesc->rates, 0x10, sizeof(txdesc->rates)); + } else { + /* data frames */ + txdesc->header1.flags = UPGT_H1_FLAGS_TX_DATA; + memcpy(txdesc->rates, sc->sc_cur_rateset, sizeof(txdesc->rates)); + } + txdesc->header1.type = UPGT_H1_TYPE_TX_DATA; + txdesc->header1.len = htole16(m->m_pkthdr.len); + txdesc->header2.reqid = htole32(data->addr); + txdesc->header2.type = htole16(UPGT_H2_TYPE_TX_ACK_YES); + txdesc->header2.flags = htole16(UPGT_H2_FLAGS_TX_ACK_YES); + txdesc->type = htole32(UPGT_TX_DESC_TYPE_DATA); + txdesc->pad3[0] = UPGT_TX_DESC_PAD3_SIZE; + + if (ieee80211_radiotap_active_vap(vap)) { + struct upgt_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = 0; /* XXX where to get from? */ + + ieee80211_radiotap_tx(vap, m); + } + + /* copy frame below our TX descriptor header */ + m_copydata(m, 0, m->m_pkthdr.len, + data->buf + (sizeof(*mem) + sizeof(*txdesc))); + /* calculate frame size */ + len = sizeof(*mem) + sizeof(*txdesc) + m->m_pkthdr.len; + /* we need to align the frame to a 4 byte boundary */ + len = (len + 3) & ~3; + /* calculate frame checksum */ + mem->chksum = upgt_chksum_le((uint32_t *)txdesc, len - sizeof(*mem)); + data->ni = ni; + data->m = m; + data->buflen = len; + + DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: TX start data sending (%d bytes)\n", + __func__, len); + KASSERT(len <= MCLBYTES, ("mbuf is small for saving data")); + + upgt_bulk_tx(sc, data); +done: + /* + * If we don't regulary read the device statistics, the RX queue + * will stall. It's strange, but it works, so we keep reading + * the statistics here. *shrug* + */ + if (!(ifp->if_opackets % UPGT_TX_STAT_INTERVAL)) + upgt_get_stats(sc); + + return (error); +} + +static void +upgt_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct upgt_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211_frame *wh; + struct ieee80211_node *ni; + struct mbuf *m = NULL; + struct upgt_data *data; + int8_t nf; + int rssi = -1; + + UPGT_ASSERT_LOCKED(sc); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + data = STAILQ_FIRST(&sc->sc_rx_active); + if (data == NULL) + goto setup; + STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); + m = upgt_rxeof(xfer, data, &rssi); + STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); + /* FALLTHROUGH */ + case USB_ST_SETUP: +setup: + data = STAILQ_FIRST(&sc->sc_rx_inactive); + if (data == NULL) + return; + STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next); + STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next); + usbd_xfer_set_frame_data(xfer, 0, data->buf, + usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + + /* + * To avoid LOR we should unlock our private mutex here to call + * ieee80211_input() because here is at the end of a USB + * callback and safe to unlock. + */ + UPGT_UNLOCK(sc); + if (m != NULL) { + wh = mtod(m, struct ieee80211_frame *); + ni = ieee80211_find_rxnode(ic, + (struct ieee80211_frame_min *)wh); + nf = -95; /* XXX */ + if (ni != NULL) { + (void) ieee80211_input(ni, m, rssi, nf); + /* node is no longer needed */ + ieee80211_free_node(ni); + } else + (void) ieee80211_input_all(ic, m, rssi, nf); + m = NULL; + } + if ((ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0 && + !IFQ_IS_EMPTY(&ifp->if_snd)) + upgt_start(ifp); + UPGT_LOCK(sc); + break; + default: + /* needs it to the inactive queue due to a error. */ + data = STAILQ_FIRST(&sc->sc_rx_active); + if (data != NULL) { + STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); + STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); + } + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + ifp->if_ierrors++; + goto setup; + } + break; + } +} + +static void +upgt_bulk_tx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct upgt_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct upgt_data *data; + + UPGT_ASSERT_LOCKED(sc); + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + data = STAILQ_FIRST(&sc->sc_tx_active); + if (data == NULL) + goto setup; + STAILQ_REMOVE_HEAD(&sc->sc_tx_active, next); + UPGT_STAT_DEC(sc, st_tx_active); + upgt_txeof(xfer, data); + STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next); + UPGT_STAT_INC(sc, st_tx_inactive); + /* FALLTHROUGH */ + case USB_ST_SETUP: +setup: + data = STAILQ_FIRST(&sc->sc_tx_pending); + if (data == NULL) { + DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: empty pending queue\n", + __func__); + return; + } + STAILQ_REMOVE_HEAD(&sc->sc_tx_pending, next); + UPGT_STAT_DEC(sc, st_tx_pending); + STAILQ_INSERT_TAIL(&sc->sc_tx_active, data, next); + UPGT_STAT_INC(sc, st_tx_active); + + usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen); + usbd_transfer_submit(xfer); + UPGT_UNLOCK(sc); + upgt_start(ifp); + UPGT_LOCK(sc); + break; + default: + data = STAILQ_FIRST(&sc->sc_tx_active); + if (data == NULL) + goto setup; + if (data->ni != NULL) { + ieee80211_free_node(data->ni); + data->ni = NULL; + ifp->if_oerrors++; + } + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto setup; + } + break; + } +} + +static device_method_t upgt_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, upgt_match), + DEVMETHOD(device_attach, upgt_attach), + DEVMETHOD(device_detach, upgt_detach), + + { 0, 0 } +}; + +static driver_t upgt_driver = { + "upgt", + upgt_methods, + sizeof(struct upgt_softc) +}; + +static devclass_t upgt_devclass; + +DRIVER_MODULE(if_upgt, uhub, upgt_driver, upgt_devclass, NULL, 0); +MODULE_VERSION(if_upgt, 1); +MODULE_DEPEND(if_upgt, usb, 1, 1, 1); +MODULE_DEPEND(if_upgt, wlan, 1, 1, 1); +MODULE_DEPEND(if_upgt, upgtfw_fw, 1, 1, 1); diff --git a/sys/bus/u4b/wlan/if_upgtvar.h b/sys/bus/u4b/wlan/if_upgtvar.h new file mode 100644 index 0000000000..68d5d2be72 --- /dev/null +++ b/sys/bus/u4b/wlan/if_upgtvar.h @@ -0,0 +1,482 @@ +/* $OpenBSD: if_upgtvar.h,v 1.14 2008/02/02 13:48:44 mglocker Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2007 Marcus Glocker + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct upgt_softc; + +/* + * General values. + */ +enum { + UPGT_BULK_RX, + UPGT_BULK_TX, + UPGT_N_XFERS = 2, +}; + +#define UPGT_CONFIG_INDEX 0 +#define UPGT_IFACE_INDEX 0 +#define UPGT_USB_TIMEOUT 1000 +#define UPGT_FIRMWARE_TIMEOUT 10 + +#define UPGT_MEMADDR_FIRMWARE_START 0x00020000 /* 512 bytes large */ +#define UPGT_MEMSIZE_FRAME_HEAD 0x0070 +#define UPGT_MEMSIZE_RX 0x3500 + +#define UPGT_RX_MAXCOUNT 6 +#define UPGT_TX_MAXCOUNT 128 +#define UPGT_TX_STAT_INTERVAL 5 +#define UPGT_RX_MINSZ (sizeof(struct upgt_lmac_header) + 4) + +/* device flags */ +#define UPGT_DEVICE_ATTACHED (1 << 0) + +/* leds */ +#define UPGT_LED_OFF 0 +#define UPGT_LED_ON 1 +#define UPGT_LED_BLINK 2 + +/* + * Firmware. + */ +#define UPGT_FW_BLOCK_SIZE 256 + +#define UPGT_BRA_FWTYPE_SIZE 4 +#define UPGT_BRA_FWTYPE_LM86 "LM86" +#define UPGT_BRA_FWTYPE_LM87 "LM87" +enum upgt_fw_type { + UPGT_FWTYPE_LM86, + UPGT_FWTYPE_LM87 +}; + +#define UPGT_BRA_TYPE_FW 0x80000001 +#define UPGT_BRA_TYPE_VERSION 0x80000002 +#define UPGT_BRA_TYPE_DEPIF 0x80000003 +#define UPGT_BRA_TYPE_EXPIF 0x80000004 +#define UPGT_BRA_TYPE_DESCR 0x80000101 +#define UPGT_BRA_TYPE_END 0xff0000ff +struct upgt_fw_bra_option { + uint32_t type; + uint32_t len; + uint8_t data[]; +} __packed; + +struct upgt_fw_bra_descr { + uint32_t unknown1; + uint32_t memaddr_space_start; + uint32_t memaddr_space_end; + uint32_t unknown2; + uint32_t unknown3; + uint8_t rates[20]; +} __packed; + +#define UPGT_X2_SIGNATURE_SIZE 4 +#define UPGT_X2_SIGNATURE "x2 " +struct upgt_fw_x2_header { + uint8_t signature[4]; + uint32_t startaddr; + uint32_t len; + uint32_t crc; +} __packed; + +/* + * EEPROM. + */ +#define UPGT_EEPROM_SIZE 8192 +#define UPGT_EEPROM_BLOCK_SIZE 1020 + +struct upgt_eeprom_header { + /* 14 bytes */ + uint32_t magic; + uint16_t pad1; + uint16_t preamble_len; + uint32_t pad2; + /* data */ +} __packed; + +#define UPGT_EEPROM_TYPE_END 0x0000 +#define UPGT_EEPROM_TYPE_NAME 0x0001 +#define UPGT_EEPROM_TYPE_SERIAL 0x0003 +#define UPGT_EEPROM_TYPE_MAC 0x0101 +#define UPGT_EEPROM_TYPE_HWRX 0x1001 +#define UPGT_EEPROM_TYPE_CHIP 0x1002 +#define UPGT_EEPROM_TYPE_FREQ3 0x1903 +#define UPGT_EEPROM_TYPE_FREQ4 0x1904 +#define UPGT_EEPROM_TYPE_FREQ5 0x1905 +#define UPGT_EEPROM_TYPE_FREQ6 0x1906 +#define UPGT_EEPROM_TYPE_OFF 0xffff +struct upgt_eeprom_option { + uint16_t len; + uint16_t type; + uint8_t data[]; + /* data */ +} __packed; + +#define UPGT_EEPROM_RX_CONST 0x88 +struct upgt_eeprom_option_hwrx { + uint32_t pad1; + uint8_t rxfilter; + uint8_t pad2[15]; +} __packed; + +struct upgt_eeprom_freq3_header { + uint8_t flags; + uint8_t elements; +} __packed; + +struct upgt_eeprom_freq4_header { + uint8_t flags; + uint8_t elements; + uint8_t settings; + uint8_t type; +} __packed; + +struct upgt_eeprom_freq4_1 { + uint16_t freq; + uint8_t data[50]; +} __packed; + +struct upgt_eeprom_freq4_2 { + uint16_t head; + uint8_t subtails[4]; + uint8_t tail; +} __packed; + +/* + * LMAC protocol. + */ +struct upgt_lmac_mem { + uint32_t addr; + uint32_t chksum; +} __packed; + +#define UPGT_H1_FLAGS_TX_MGMT 0x00 /* for TX: mgmt frame */ +#define UPGT_H1_FLAGS_TX_NO_CALLBACK 0x01 /* for TX: no USB callback */ +#define UPGT_H1_FLAGS_TX_DATA 0x10 /* for TX: data frame */ +#define UPGT_H1_TYPE_RX_DATA 0x00 /* 802.11 RX data frame */ +#define UPGT_H1_TYPE_RX_DATA_MGMT 0x04 /* 802.11 RX mgmt frame */ +#define UPGT_H1_TYPE_TX_DATA 0x40 /* 802.11 TX data frame */ +#define UPGT_H1_TYPE_CTRL 0x80 /* control frame */ +struct upgt_lmac_h1 { + /* 4 bytes */ + uint8_t flags; + uint8_t type; + uint16_t len; +} __packed; + +#define UPGT_H2_TYPE_TX_ACK_NO 0x0000 +#define UPGT_H2_TYPE_TX_ACK_YES 0x0001 +#define UPGT_H2_TYPE_MACFILTER 0x0000 +#define UPGT_H2_TYPE_CHANNEL 0x0001 +#define UPGT_H2_TYPE_TX_DONE 0x0008 +#define UPGT_H2_TYPE_STATS 0x000a +#define UPGT_H2_TYPE_EEPROM 0x000c +#define UPGT_H2_TYPE_LED 0x000d +#define UPGT_H2_FLAGS_TX_ACK_NO 0x0101 +#define UPGT_H2_FLAGS_TX_ACK_YES 0x0707 +struct upgt_lmac_h2 { + /* 8 bytes */ + uint32_t reqid; + uint16_t type; + uint16_t flags; +} __packed; + +struct upgt_lmac_header { + /* 12 bytes */ + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; +} __packed; + +struct upgt_lmac_eeprom { + /* 16 bytes */ + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + uint16_t offset; + uint16_t len; + /* data */ +} __packed; + +#define UPGT_FILTER_TYPE_NONE 0x0000 +#define UPGT_FILTER_TYPE_STA 0x0001 +#define UPGT_FILTER_TYPE_IBSS 0x0002 +#define UPGT_FILTER_TYPE_HOSTAP 0x0004 +#define UPGT_FILTER_TYPE_MONITOR 0x0010 +#define UPGT_FILTER_TYPE_RESET 0x0020 +#define UPGT_FILTER_UNKNOWN1 0x0002 +#define UPGT_FILTER_UNKNOWN2 0x0ca8 +#define UPGT_FILTER_UNKNOWN3 0xffff +#define UPGT_FILTER_MONITOR_UNKNOWN1 0x0000 +#define UPGT_FILTER_MONITOR_UNKNOWN2 0x0000 +#define UPGT_FILTER_MONITOR_UNKNOWN3 0x0000 +struct upgt_lmac_filter { + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + /* 32 bytes */ + uint16_t type; + uint8_t dst[IEEE80211_ADDR_LEN]; + uint8_t src[IEEE80211_ADDR_LEN]; + uint16_t unknown1; + uint32_t rxaddr; + uint16_t unknown2; + uint32_t rxhw; + uint16_t unknown3; + uint32_t unknown4; +} __packed; + +/* frequence 3 data */ +struct upgt_lmac_freq3 { + uint16_t freq; + uint8_t data[6]; +} __packed; + +/* frequence 4 data */ +struct upgt_lmac_freq4 { + struct upgt_eeprom_freq4_2 cmd; + uint8_t pad; +}; + +/* frequence 6 data */ +struct upgt_lmac_freq6 { + uint16_t freq; + uint8_t data[8]; +} __packed; + +#define UPGT_CHANNEL_UNKNOWN1 0x0001 +#define UPGT_CHANNEL_UNKNOWN2 0x0000 +#define UPGT_CHANNEL_UNKNOWN3 0x48 +struct upgt_lmac_channel { + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + /* 112 bytes */ + uint16_t unknown1; + uint16_t unknown2; + uint8_t pad1[20]; + struct upgt_lmac_freq6 freq6; + uint8_t settings; + uint8_t unknown3; + uint8_t freq3_1[4]; + struct upgt_lmac_freq4 freq4[8]; + uint8_t freq3_2[4]; + uint32_t pad2; +} __packed; + +#define UPGT_LED_MODE_SET 0x0003 +#define UPGT_LED_ACTION_OFF 0x0002 +#define UPGT_LED_ACTION_ON 0x0003 +#define UPGT_LED_ACTION_TMP_DUR 100 /* ms */ +struct upgt_lmac_led { + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + uint16_t mode; + uint16_t action_fix; + uint16_t action_tmp; + uint16_t action_tmp_dur; +} __packed; + +struct upgt_lmac_stats { + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + uint8_t data[76]; +} __packed; + +struct upgt_lmac_rx_desc { + struct upgt_lmac_h1 header1; + /* 16 bytes */ + uint16_t freq; + uint8_t unknown1; + uint8_t rate; + uint8_t rssi; + uint8_t pad; + uint16_t unknown2; + uint32_t timestamp; + uint32_t unknown3; + uint8_t data[]; +} __packed; + +#define UPGT_TX_DESC_KEY_EXISTS 0x01 +struct upgt_lmac_tx_desc_wep { + uint8_t key_exists; + uint8_t key_len; + uint8_t key_val[16]; +} __packed; + +#define UPGT_TX_DESC_TYPE_BEACON 0x00000000 +#define UPGT_TX_DESC_TYPE_PROBE 0x00000001 +#define UPGT_TX_DESC_TYPE_MGMT 0x00000002 +#define UPGT_TX_DESC_TYPE_DATA 0x00000004 +#define UPGT_TX_DESC_PAD3_SIZE 2 +struct upgt_lmac_tx_desc { + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + uint8_t rates[8]; + uint16_t pad1; + struct upgt_lmac_tx_desc_wep wep_key; + uint32_t type; + uint32_t pad2; + uint32_t unknown1; + uint32_t unknown2; + uint8_t pad3[2]; + /* 802.11 frame data */ +} __packed; + +#define UPGT_TX_DONE_DESC_STATUS_OK 0x0001 +struct upgt_lmac_tx_done_desc { + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + uint16_t status; + uint16_t rssi; + uint16_t seq; + uint16_t unknown; +} __packed; + +/* + * USB xfers. + */ +struct upgt_data { + uint8_t *buf; + uint32_t buflen; + struct ieee80211_node *ni; + struct mbuf *m; + uint32_t addr; + uint8_t use; + STAILQ_ENTRY(upgt_data) next; +}; +typedef STAILQ_HEAD(, upgt_data) upgt_datahead; + +/* + * Prism memory. + */ +struct upgt_memory_page { + uint8_t used; + uint32_t addr; +} __packed; + +#define UPGT_MEMORY_MAX_PAGES 8 +struct upgt_memory { + uint8_t pages; + struct upgt_memory_page page[UPGT_MEMORY_MAX_PAGES]; +} __packed; + +/* + * BPF + */ +struct upgt_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint8_t wr_rate; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + int8_t wr_antsignal; +} __packed; + +#define UPGT_RX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL)) + +struct upgt_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint8_t wt_rate; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; +} __packed; + +#define UPGT_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL)) + +struct upgt_stat { + uint32_t st_tx_active; + uint32_t st_tx_inactive; + uint32_t st_tx_pending; +}; + +#define UPGT_STAT_INC(sc, var) (sc)->sc_stat.var++ +#define UPGT_STAT_DEC(sc, var) (sc)->sc_stat.var-- + +struct upgt_vap { + struct ieee80211vap vap; + int (*newstate)(struct ieee80211vap *, + enum ieee80211_state, int); +}; +#define UPGT_VAP(vap) ((struct upgt_vap *)(vap)) + +struct upgt_softc { + device_t sc_dev; + struct ifnet *sc_ifp; + struct usb_device *sc_udev; + struct mtx sc_mtx; + struct upgt_stat sc_stat; + int sc_flags; +#define UPGT_FLAG_FWLOADED (1 << 0) +#define UPGT_FLAG_INITDONE (1 << 1) + int sc_if_flags; + int sc_debug; + + uint8_t sc_myaddr[IEEE80211_ADDR_LEN]; + + enum ieee80211_state sc_state; + int sc_arg; + int sc_led_blink; + struct callout sc_led_ch; + uint8_t sc_cur_rateset[8]; + + /* watchdog */ + int sc_tx_timer; + struct callout sc_watchdog_ch; + + /* Firmware. */ + int sc_fw_type; + /* memory addresses on device */ + uint32_t sc_memaddr_frame_start; + uint32_t sc_memaddr_frame_end; + uint32_t sc_memaddr_rx_start; + struct upgt_memory sc_memory; + + /* data which we found in the EEPROM */ + uint8_t sc_eeprom[UPGT_EEPROM_SIZE]; + uint16_t sc_eeprom_hwrx; + struct upgt_lmac_freq3 sc_eeprom_freq3[IEEE80211_CHAN_MAX]; + struct upgt_lmac_freq4 sc_eeprom_freq4[IEEE80211_CHAN_MAX][8]; + struct upgt_lmac_freq6 sc_eeprom_freq6[IEEE80211_CHAN_MAX]; + uint8_t sc_eeprom_freq6_settings; + + /* RX/TX */ + struct usb_xfer *sc_xfer[UPGT_N_XFERS]; + int sc_rx_no; + int sc_tx_no; + struct upgt_data sc_rx_data[UPGT_RX_MAXCOUNT]; + upgt_datahead sc_rx_active; + upgt_datahead sc_rx_inactive; + struct upgt_data sc_tx_data[UPGT_TX_MAXCOUNT]; + upgt_datahead sc_tx_active; + upgt_datahead sc_tx_inactive; + upgt_datahead sc_tx_pending; + + /* BPF */ + struct upgt_rx_radiotap_header sc_rxtap; + int sc_rxtap_len; + struct upgt_tx_radiotap_header sc_txtap; + int sc_txtap_len; +}; + +#define UPGT_LOCK(sc) mtx_lock(&(sc)->sc_mtx) +#define UPGT_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) +#define UPGT_ASSERT_LOCKED(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED) diff --git a/sys/bus/u4b/wlan/if_ural.c b/sys/bus/u4b/wlan/if_ural.c new file mode 100644 index 0000000000..945789f803 --- /dev/null +++ b/sys/bus/u4b/wlan/if_ural.c @@ -0,0 +1,2270 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005, 2006 + * Damien Bergamini + * + * Copyright (c) 2006, 2008 + * Hans Petter Selasky + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/*- + * Ralink Technology RT2500USB chipset driver + * http://www.ralinktech.com/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef INET +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR ural_debug +#include + +#include +#include + +#ifdef USB_DEBUG +static int ural_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ural, CTLFLAG_RW, 0, "USB ural"); +SYSCTL_INT(_hw_usb_ural, OID_AUTO, debug, CTLFLAG_RW, &ural_debug, 0, + "Debug level"); +#endif + +#define URAL_RSSI(rssi) \ + ((rssi) > (RAL_NOISE_FLOOR + RAL_RSSI_CORR) ? \ + ((rssi) - (RAL_NOISE_FLOOR + RAL_RSSI_CORR)) : 0) + +/* various supported device vendors/products */ +static const STRUCT_USB_HOST_ID ural_devs[] = { +#define URAL_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } + URAL_DEV(ASUS, WL167G), + URAL_DEV(ASUS, RT2570), + URAL_DEV(BELKIN, F5D7050), + URAL_DEV(BELKIN, F5D7051), + URAL_DEV(CISCOLINKSYS, HU200TS), + URAL_DEV(CISCOLINKSYS, WUSB54G), + URAL_DEV(CISCOLINKSYS, WUSB54GP), + URAL_DEV(CONCEPTRONIC2, C54RU), + URAL_DEV(DLINK, DWLG122), + URAL_DEV(GIGABYTE, GN54G), + URAL_DEV(GIGABYTE, GNWBKG), + URAL_DEV(GUILLEMOT, HWGUSB254), + URAL_DEV(MELCO, KG54), + URAL_DEV(MELCO, KG54AI), + URAL_DEV(MELCO, KG54YB), + URAL_DEV(MELCO, NINWIFI), + URAL_DEV(MSI, RT2570), + URAL_DEV(MSI, RT2570_2), + URAL_DEV(MSI, RT2570_3), + URAL_DEV(NOVATECH, NV902), + URAL_DEV(RALINK, RT2570), + URAL_DEV(RALINK, RT2570_2), + URAL_DEV(RALINK, RT2570_3), + URAL_DEV(SIEMENS2, WL54G), + URAL_DEV(SMC, 2862WG), + URAL_DEV(SPHAIRON, UB801R), + URAL_DEV(SURECOM, RT2570), + URAL_DEV(VTECH, RT2570), + URAL_DEV(ZINWELL, RT2570), +#undef URAL_DEV +}; + +static usb_callback_t ural_bulk_read_callback; +static usb_callback_t ural_bulk_write_callback; + +static usb_error_t ural_do_request(struct ural_softc *sc, + struct usb_device_request *req, void *data); +static struct ieee80211vap *ural_vap_create(struct ieee80211com *, + const char [IFNAMSIZ], int, enum ieee80211_opmode, + int, const uint8_t [IEEE80211_ADDR_LEN], + const uint8_t [IEEE80211_ADDR_LEN]); +static void ural_vap_delete(struct ieee80211vap *); +static void ural_tx_free(struct ural_tx_data *, int); +static void ural_setup_tx_list(struct ural_softc *); +static void ural_unsetup_tx_list(struct ural_softc *); +static int ural_newstate(struct ieee80211vap *, + enum ieee80211_state, int); +static void ural_setup_tx_desc(struct ural_softc *, + struct ural_tx_desc *, uint32_t, int, int); +static int ural_tx_bcn(struct ural_softc *, struct mbuf *, + struct ieee80211_node *); +static int ural_tx_mgt(struct ural_softc *, struct mbuf *, + struct ieee80211_node *); +static int ural_tx_data(struct ural_softc *, struct mbuf *, + struct ieee80211_node *); +static void ural_start(struct ifnet *); +static int ural_ioctl(struct ifnet *, u_long, caddr_t); +static void ural_set_testmode(struct ural_softc *); +static void ural_eeprom_read(struct ural_softc *, uint16_t, void *, + int); +static uint16_t ural_read(struct ural_softc *, uint16_t); +static void ural_read_multi(struct ural_softc *, uint16_t, void *, + int); +static void ural_write(struct ural_softc *, uint16_t, uint16_t); +static void ural_write_multi(struct ural_softc *, uint16_t, void *, + int) __unused; +static void ural_bbp_write(struct ural_softc *, uint8_t, uint8_t); +static uint8_t ural_bbp_read(struct ural_softc *, uint8_t); +static void ural_rf_write(struct ural_softc *, uint8_t, uint32_t); +static void ural_scan_start(struct ieee80211com *); +static void ural_scan_end(struct ieee80211com *); +static void ural_set_channel(struct ieee80211com *); +static void ural_set_chan(struct ural_softc *, + struct ieee80211_channel *); +static void ural_disable_rf_tune(struct ural_softc *); +static void ural_enable_tsf_sync(struct ural_softc *); +static void ural_enable_tsf(struct ural_softc *); +static void ural_update_slot(struct ifnet *); +static void ural_set_txpreamble(struct ural_softc *); +static void ural_set_basicrates(struct ural_softc *, + const struct ieee80211_channel *); +static void ural_set_bssid(struct ural_softc *, const uint8_t *); +static void ural_set_macaddr(struct ural_softc *, uint8_t *); +static void ural_update_promisc(struct ifnet *); +static void ural_setpromisc(struct ural_softc *); +static const char *ural_get_rf(int); +static void ural_read_eeprom(struct ural_softc *); +static int ural_bbp_init(struct ural_softc *); +static void ural_set_txantenna(struct ural_softc *, int); +static void ural_set_rxantenna(struct ural_softc *, int); +static void ural_init_locked(struct ural_softc *); +static void ural_init(void *); +static void ural_stop(struct ural_softc *); +static int ural_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static void ural_ratectl_start(struct ural_softc *, + struct ieee80211_node *); +static void ural_ratectl_timeout(void *); +static void ural_ratectl_task(void *, int); +static int ural_pause(struct ural_softc *sc, int timeout); + +/* + * Default values for MAC registers; values taken from the reference driver. + */ +static const struct { + uint16_t reg; + uint16_t val; +} ural_def_mac[] = { + { RAL_TXRX_CSR5, 0x8c8d }, + { RAL_TXRX_CSR6, 0x8b8a }, + { RAL_TXRX_CSR7, 0x8687 }, + { RAL_TXRX_CSR8, 0x0085 }, + { RAL_MAC_CSR13, 0x1111 }, + { RAL_MAC_CSR14, 0x1e11 }, + { RAL_TXRX_CSR21, 0xe78f }, + { RAL_MAC_CSR9, 0xff1d }, + { RAL_MAC_CSR11, 0x0002 }, + { RAL_MAC_CSR22, 0x0053 }, + { RAL_MAC_CSR15, 0x0000 }, + { RAL_MAC_CSR8, RAL_FRAME_SIZE }, + { RAL_TXRX_CSR19, 0x0000 }, + { RAL_TXRX_CSR18, 0x005a }, + { RAL_PHY_CSR2, 0x0000 }, + { RAL_TXRX_CSR0, 0x1ec0 }, + { RAL_PHY_CSR4, 0x000f } +}; + +/* + * Default values for BBP registers; values taken from the reference driver. + */ +static const struct { + uint8_t reg; + uint8_t val; +} ural_def_bbp[] = { + { 3, 0x02 }, + { 4, 0x19 }, + { 14, 0x1c }, + { 15, 0x30 }, + { 16, 0xac }, + { 17, 0x48 }, + { 18, 0x18 }, + { 19, 0xff }, + { 20, 0x1e }, + { 21, 0x08 }, + { 22, 0x08 }, + { 23, 0x08 }, + { 24, 0x80 }, + { 25, 0x50 }, + { 26, 0x08 }, + { 27, 0x23 }, + { 30, 0x10 }, + { 31, 0x2b }, + { 32, 0xb9 }, + { 34, 0x12 }, + { 35, 0x50 }, + { 39, 0xc4 }, + { 40, 0x02 }, + { 41, 0x60 }, + { 53, 0x10 }, + { 54, 0x18 }, + { 56, 0x08 }, + { 57, 0x10 }, + { 58, 0x08 }, + { 61, 0x60 }, + { 62, 0x10 }, + { 75, 0xff } +}; + +/* + * Default values for RF register R2 indexed by channel numbers. + */ +static const uint32_t ural_rf2522_r2[] = { + 0x307f6, 0x307fb, 0x30800, 0x30805, 0x3080a, 0x3080f, 0x30814, + 0x30819, 0x3081e, 0x30823, 0x30828, 0x3082d, 0x30832, 0x3083e +}; + +static const uint32_t ural_rf2523_r2[] = { + 0x00327, 0x00328, 0x00329, 0x0032a, 0x0032b, 0x0032c, 0x0032d, + 0x0032e, 0x0032f, 0x00340, 0x00341, 0x00342, 0x00343, 0x00346 +}; + +static const uint32_t ural_rf2524_r2[] = { + 0x00327, 0x00328, 0x00329, 0x0032a, 0x0032b, 0x0032c, 0x0032d, + 0x0032e, 0x0032f, 0x00340, 0x00341, 0x00342, 0x00343, 0x00346 +}; + +static const uint32_t ural_rf2525_r2[] = { + 0x20327, 0x20328, 0x20329, 0x2032a, 0x2032b, 0x2032c, 0x2032d, + 0x2032e, 0x2032f, 0x20340, 0x20341, 0x20342, 0x20343, 0x20346 +}; + +static const uint32_t ural_rf2525_hi_r2[] = { + 0x2032f, 0x20340, 0x20341, 0x20342, 0x20343, 0x20344, 0x20345, + 0x20346, 0x20347, 0x20348, 0x20349, 0x2034a, 0x2034b, 0x2034e +}; + +static const uint32_t ural_rf2525e_r2[] = { + 0x2044d, 0x2044e, 0x2044f, 0x20460, 0x20461, 0x20462, 0x20463, + 0x20464, 0x20465, 0x20466, 0x20467, 0x20468, 0x20469, 0x2046b +}; + +static const uint32_t ural_rf2526_hi_r2[] = { + 0x0022a, 0x0022b, 0x0022b, 0x0022c, 0x0022c, 0x0022d, 0x0022d, + 0x0022e, 0x0022e, 0x0022f, 0x0022d, 0x00240, 0x00240, 0x00241 +}; + +static const uint32_t ural_rf2526_r2[] = { + 0x00226, 0x00227, 0x00227, 0x00228, 0x00228, 0x00229, 0x00229, + 0x0022a, 0x0022a, 0x0022b, 0x0022b, 0x0022c, 0x0022c, 0x0022d +}; + +/* + * For dual-band RF, RF registers R1 and R4 also depend on channel number; + * values taken from the reference driver. + */ +static const struct { + uint8_t chan; + uint32_t r1; + uint32_t r2; + uint32_t r4; +} ural_rf5222[] = { + { 1, 0x08808, 0x0044d, 0x00282 }, + { 2, 0x08808, 0x0044e, 0x00282 }, + { 3, 0x08808, 0x0044f, 0x00282 }, + { 4, 0x08808, 0x00460, 0x00282 }, + { 5, 0x08808, 0x00461, 0x00282 }, + { 6, 0x08808, 0x00462, 0x00282 }, + { 7, 0x08808, 0x00463, 0x00282 }, + { 8, 0x08808, 0x00464, 0x00282 }, + { 9, 0x08808, 0x00465, 0x00282 }, + { 10, 0x08808, 0x00466, 0x00282 }, + { 11, 0x08808, 0x00467, 0x00282 }, + { 12, 0x08808, 0x00468, 0x00282 }, + { 13, 0x08808, 0x00469, 0x00282 }, + { 14, 0x08808, 0x0046b, 0x00286 }, + + { 36, 0x08804, 0x06225, 0x00287 }, + { 40, 0x08804, 0x06226, 0x00287 }, + { 44, 0x08804, 0x06227, 0x00287 }, + { 48, 0x08804, 0x06228, 0x00287 }, + { 52, 0x08804, 0x06229, 0x00287 }, + { 56, 0x08804, 0x0622a, 0x00287 }, + { 60, 0x08804, 0x0622b, 0x00287 }, + { 64, 0x08804, 0x0622c, 0x00287 }, + + { 100, 0x08804, 0x02200, 0x00283 }, + { 104, 0x08804, 0x02201, 0x00283 }, + { 108, 0x08804, 0x02202, 0x00283 }, + { 112, 0x08804, 0x02203, 0x00283 }, + { 116, 0x08804, 0x02204, 0x00283 }, + { 120, 0x08804, 0x02205, 0x00283 }, + { 124, 0x08804, 0x02206, 0x00283 }, + { 128, 0x08804, 0x02207, 0x00283 }, + { 132, 0x08804, 0x02208, 0x00283 }, + { 136, 0x08804, 0x02209, 0x00283 }, + { 140, 0x08804, 0x0220a, 0x00283 }, + + { 149, 0x08808, 0x02429, 0x00281 }, + { 153, 0x08808, 0x0242b, 0x00281 }, + { 157, 0x08808, 0x0242d, 0x00281 }, + { 161, 0x08808, 0x0242f, 0x00281 } +}; + +static const struct usb_config ural_config[URAL_N_TRANSFER] = { + [URAL_BULK_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = (RAL_FRAME_SIZE + RAL_TX_DESC_SIZE + 4), + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = ural_bulk_write_callback, + .timeout = 5000, /* ms */ + }, + [URAL_BULK_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = (RAL_FRAME_SIZE + RAL_RX_DESC_SIZE), + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = ural_bulk_read_callback, + }, +}; + +static device_probe_t ural_match; +static device_attach_t ural_attach; +static device_detach_t ural_detach; + +static device_method_t ural_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ural_match), + DEVMETHOD(device_attach, ural_attach), + DEVMETHOD(device_detach, ural_detach), + + { 0, 0 } +}; + +static driver_t ural_driver = { + .name = "ural", + .methods = ural_methods, + .size = sizeof(struct ural_softc), +}; + +static devclass_t ural_devclass; + +DRIVER_MODULE(ural, uhub, ural_driver, ural_devclass, NULL, 0); +MODULE_DEPEND(ural, usb, 1, 1, 1); +MODULE_DEPEND(ural, wlan, 1, 1, 1); +MODULE_VERSION(ural, 1); + +static int +ural_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != 0) + return (ENXIO); + if (uaa->info.bIfaceIndex != RAL_IFACE_INDEX) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(ural_devs, sizeof(ural_devs), uaa)); +} + +static int +ural_attach(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + struct ural_softc *sc = device_get_softc(self); + struct ifnet *ifp; + struct ieee80211com *ic; + uint8_t iface_index, bands; + int error; + + device_set_usb_desc(self); + sc->sc_udev = uaa->device; + sc->sc_dev = self; + + mtx_init(&sc->sc_mtx, device_get_nameunit(self), + MTX_NETWORK_LOCK, MTX_DEF); + + iface_index = RAL_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, + &iface_index, sc->sc_xfer, ural_config, + URAL_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + device_printf(self, "could not allocate USB transfers, " + "err=%s\n", usbd_errstr(error)); + goto detach; + } + + RAL_LOCK(sc); + /* retrieve RT2570 rev. no */ + sc->asic_rev = ural_read(sc, RAL_MAC_CSR0); + + /* retrieve MAC address and various other things from EEPROM */ + ural_read_eeprom(sc); + RAL_UNLOCK(sc); + + device_printf(self, "MAC/BBP RT2570 (rev 0x%02x), RF %s\n", + sc->asic_rev, ural_get_rf(sc->rf_rev)); + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(sc->sc_dev, "can not if_alloc()\n"); + goto detach; + } + ic = ifp->if_l2com; + + ifp->if_softc = sc; + if_initname(ifp, "ural", device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_init = ural_init; + ifp->if_ioctl = ural_ioctl; + ifp->if_start = ural_start; + IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); + ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; + IFQ_SET_READY(&ifp->if_snd); + + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA /* station mode supported */ + | IEEE80211_C_IBSS /* IBSS mode supported */ + | IEEE80211_C_MONITOR /* monitor mode supported */ + | IEEE80211_C_HOSTAP /* HostAp mode supported */ + | IEEE80211_C_TXPMGT /* tx power management */ + | IEEE80211_C_SHPREAMBLE /* short preamble supported */ + | IEEE80211_C_SHSLOT /* short slot time supported */ + | IEEE80211_C_BGSCAN /* bg scanning supported */ + | IEEE80211_C_WPA /* 802.11i */ + ; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + if (sc->rf_rev == RAL_RF_5222) + setbit(&bands, IEEE80211_MODE_11A); + ieee80211_init_channels(ic, NULL, &bands); + + ieee80211_ifattach(ic, sc->sc_bssid); + ic->ic_update_promisc = ural_update_promisc; + ic->ic_raw_xmit = ural_raw_xmit; + ic->ic_scan_start = ural_scan_start; + ic->ic_scan_end = ural_scan_end; + ic->ic_set_channel = ural_set_channel; + + ic->ic_vap_create = ural_vap_create; + ic->ic_vap_delete = ural_vap_delete; + + ieee80211_radiotap_attach(ic, + &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), + RAL_TX_RADIOTAP_PRESENT, + &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), + RAL_RX_RADIOTAP_PRESENT); + + if (bootverbose) + ieee80211_announce(ic); + + return (0); + +detach: + ural_detach(self); + return (ENXIO); /* failure */ +} + +static int +ural_detach(device_t self) +{ + struct ural_softc *sc = device_get_softc(self); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic; + + /* stop all USB transfers */ + usbd_transfer_unsetup(sc->sc_xfer, URAL_N_TRANSFER); + + /* free TX list, if any */ + RAL_LOCK(sc); + ural_unsetup_tx_list(sc); + RAL_UNLOCK(sc); + + if (ifp) { + ic = ifp->if_l2com; + ieee80211_ifdetach(ic); + if_free(ifp); + } + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static usb_error_t +ural_do_request(struct ural_softc *sc, + struct usb_device_request *req, void *data) +{ + usb_error_t err; + int ntries = 10; + + while (ntries--) { + err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, + req, data, 0, NULL, 250 /* ms */); + if (err == 0) + break; + + DPRINTFN(1, "Control request failed, %s (retrying)\n", + usbd_errstr(err)); + if (ural_pause(sc, hz / 100)) + break; + } + return (err); +} + +static struct ieee80211vap * +ural_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, + enum ieee80211_opmode opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct ural_softc *sc = ic->ic_ifp->if_softc; + struct ural_vap *uvp; + struct ieee80211vap *vap; + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return NULL; + uvp = (struct ural_vap *) malloc(sizeof(struct ural_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (uvp == NULL) + return NULL; + vap = &uvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + uvp->newstate = vap->iv_newstate; + vap->iv_newstate = ural_newstate; + + usb_callout_init_mtx(&uvp->ratectl_ch, &sc->sc_mtx, 0); + TASK_INIT(&uvp->ratectl_task, 0, ural_ratectl_task, uvp); + ieee80211_ratectl_init(vap); + ieee80211_ratectl_setinterval(vap, 1000 /* 1 sec */); + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status); + ic->ic_opmode = opmode; + return vap; +} + +static void +ural_vap_delete(struct ieee80211vap *vap) +{ + struct ural_vap *uvp = URAL_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + + usb_callout_drain(&uvp->ratectl_ch); + ieee80211_draintask(ic, &uvp->ratectl_task); + ieee80211_ratectl_deinit(vap); + ieee80211_vap_detach(vap); + free(uvp, M_80211_VAP); +} + +static void +ural_tx_free(struct ural_tx_data *data, int txerr) +{ + struct ural_softc *sc = data->sc; + + if (data->m != NULL) { + if (data->m->m_flags & M_TXCB) + ieee80211_process_callback(data->ni, data->m, + txerr ? ETIMEDOUT : 0); + m_freem(data->m); + data->m = NULL; + + ieee80211_free_node(data->ni); + data->ni = NULL; + } + STAILQ_INSERT_TAIL(&sc->tx_free, data, next); + sc->tx_nfree++; +} + +static void +ural_setup_tx_list(struct ural_softc *sc) +{ + struct ural_tx_data *data; + int i; + + sc->tx_nfree = 0; + STAILQ_INIT(&sc->tx_q); + STAILQ_INIT(&sc->tx_free); + + for (i = 0; i < RAL_TX_LIST_COUNT; i++) { + data = &sc->tx_data[i]; + + data->sc = sc; + STAILQ_INSERT_TAIL(&sc->tx_free, data, next); + sc->tx_nfree++; + } +} + +static void +ural_unsetup_tx_list(struct ural_softc *sc) +{ + struct ural_tx_data *data; + int i; + + /* make sure any subsequent use of the queues will fail */ + sc->tx_nfree = 0; + STAILQ_INIT(&sc->tx_q); + STAILQ_INIT(&sc->tx_free); + + /* free up all node references and mbufs */ + for (i = 0; i < RAL_TX_LIST_COUNT; i++) { + data = &sc->tx_data[i]; + + if (data->m != NULL) { + m_freem(data->m); + data->m = NULL; + } + if (data->ni != NULL) { + ieee80211_free_node(data->ni); + data->ni = NULL; + } + } +} + +static int +ural_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct ural_vap *uvp = URAL_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + struct ural_softc *sc = ic->ic_ifp->if_softc; + const struct ieee80211_txparam *tp; + struct ieee80211_node *ni; + struct mbuf *m; + + DPRINTF("%s -> %s\n", + ieee80211_state_name[vap->iv_state], + ieee80211_state_name[nstate]); + + IEEE80211_UNLOCK(ic); + RAL_LOCK(sc); + usb_callout_stop(&uvp->ratectl_ch); + + switch (nstate) { + case IEEE80211_S_INIT: + if (vap->iv_state == IEEE80211_S_RUN) { + /* abort TSF synchronization */ + ural_write(sc, RAL_TXRX_CSR19, 0); + + /* force tx led to stop blinking */ + ural_write(sc, RAL_MAC_CSR20, 0); + } + break; + + case IEEE80211_S_RUN: + ni = ieee80211_ref_node(vap->iv_bss); + + if (vap->iv_opmode != IEEE80211_M_MONITOR) { + ural_update_slot(ic->ic_ifp); + ural_set_txpreamble(sc); + ural_set_basicrates(sc, ic->ic_bsschan); + IEEE80211_ADDR_COPY(sc->sc_bssid, ni->ni_bssid); + ural_set_bssid(sc, sc->sc_bssid); + } + + if (vap->iv_opmode == IEEE80211_M_HOSTAP || + vap->iv_opmode == IEEE80211_M_IBSS) { + m = ieee80211_beacon_alloc(ni, &uvp->bo); + if (m == NULL) { + device_printf(sc->sc_dev, + "could not allocate beacon\n"); + RAL_UNLOCK(sc); + IEEE80211_LOCK(ic); + ieee80211_free_node(ni); + return (-1); + } + ieee80211_ref_node(ni); + if (ural_tx_bcn(sc, m, ni) != 0) { + device_printf(sc->sc_dev, + "could not send beacon\n"); + RAL_UNLOCK(sc); + IEEE80211_LOCK(ic); + ieee80211_free_node(ni); + return (-1); + } + } + + /* make tx led blink on tx (controlled by ASIC) */ + ural_write(sc, RAL_MAC_CSR20, 1); + + if (vap->iv_opmode != IEEE80211_M_MONITOR) + ural_enable_tsf_sync(sc); + else + ural_enable_tsf(sc); + + /* enable automatic rate adaptation */ + /* XXX should use ic_bsschan but not valid until after newstate call below */ + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) + ural_ratectl_start(sc, ni); + ieee80211_free_node(ni); + break; + + default: + break; + } + RAL_UNLOCK(sc); + IEEE80211_LOCK(ic); + return (uvp->newstate(vap, nstate, arg)); +} + + +static void +ural_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ural_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211vap *vap; + struct ural_tx_data *data; + struct mbuf *m; + struct usb_page_cache *pc; + int len; + + usbd_xfer_status(xfer, &len, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete, %d bytes\n", len); + + /* free resources */ + data = usbd_xfer_get_priv(xfer); + ural_tx_free(data, 0); + usbd_xfer_set_priv(xfer, NULL); + + ifp->if_opackets++; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + data = STAILQ_FIRST(&sc->tx_q); + if (data) { + STAILQ_REMOVE_HEAD(&sc->tx_q, next); + m = data->m; + + if (m->m_pkthdr.len > (RAL_FRAME_SIZE + RAL_TX_DESC_SIZE)) { + DPRINTFN(0, "data overflow, %u bytes\n", + m->m_pkthdr.len); + m->m_pkthdr.len = (RAL_FRAME_SIZE + RAL_TX_DESC_SIZE); + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &data->desc, RAL_TX_DESC_SIZE); + usbd_m_copy_in(pc, RAL_TX_DESC_SIZE, m, 0, + m->m_pkthdr.len); + + vap = data->ni->ni_vap; + if (ieee80211_radiotap_active_vap(vap)) { + struct ural_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = data->rate; + tap->wt_antenna = sc->tx_ant; + + ieee80211_radiotap_tx(vap, m); + } + + /* xfer length needs to be a multiple of two! */ + len = (RAL_TX_DESC_SIZE + m->m_pkthdr.len + 1) & ~1; + if ((len % 64) == 0) + len += 2; + + DPRINTFN(11, "sending frame len=%u xferlen=%u\n", + m->m_pkthdr.len, len); + + usbd_xfer_set_frame_len(xfer, 0, len); + usbd_xfer_set_priv(xfer, data); + + usbd_transfer_submit(xfer); + } + RAL_UNLOCK(sc); + ural_start(ifp); + RAL_LOCK(sc); + break; + + default: /* Error */ + DPRINTFN(11, "transfer error, %s\n", + usbd_errstr(error)); + + ifp->if_oerrors++; + data = usbd_xfer_get_priv(xfer); + if (data != NULL) { + ural_tx_free(data, error); + usbd_xfer_set_priv(xfer, NULL); + } + + if (error == USB_ERR_STALLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + if (error == USB_ERR_TIMEOUT) + device_printf(sc->sc_dev, "device timeout\n"); + break; + } +} + +static void +ural_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ural_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211_node *ni; + struct mbuf *m = NULL; + struct usb_page_cache *pc; + uint32_t flags; + int8_t rssi = 0, nf = 0; + int len; + + usbd_xfer_status(xfer, &len, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTFN(15, "rx done, actlen=%d\n", len); + + if (len < RAL_RX_DESC_SIZE + IEEE80211_MIN_LEN) { + DPRINTF("%s: xfer too short %d\n", + device_get_nameunit(sc->sc_dev), len); + ifp->if_ierrors++; + goto tr_setup; + } + + len -= RAL_RX_DESC_SIZE; + /* rx descriptor is located at the end */ + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, len, &sc->sc_rx_desc, RAL_RX_DESC_SIZE); + + rssi = URAL_RSSI(sc->sc_rx_desc.rssi); + nf = RAL_NOISE_FLOOR; + flags = le32toh(sc->sc_rx_desc.flags); + if (flags & (RAL_RX_PHY_ERROR | RAL_RX_CRC_ERROR)) { + /* + * This should not happen since we did not + * request to receive those frames when we + * filled RAL_TXRX_CSR2: + */ + DPRINTFN(5, "PHY or CRC error\n"); + ifp->if_ierrors++; + goto tr_setup; + } + + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (m == NULL) { + DPRINTF("could not allocate mbuf\n"); + ifp->if_ierrors++; + goto tr_setup; + } + usbd_copy_out(pc, 0, mtod(m, uint8_t *), len); + + /* finalize mbuf */ + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = (flags >> 16) & 0xfff; + + if (ieee80211_radiotap_active(ic)) { + struct ural_rx_radiotap_header *tap = &sc->sc_rxtap; + + /* XXX set once */ + tap->wr_flags = 0; + tap->wr_rate = ieee80211_plcp2rate(sc->sc_rx_desc.rate, + (flags & RAL_RX_OFDM) ? + IEEE80211_T_OFDM : IEEE80211_T_CCK); + tap->wr_antenna = sc->rx_ant; + tap->wr_antsignal = nf + rssi; + tap->wr_antnoise = nf; + } + /* Strip trailing 802.11 MAC FCS. */ + m_adj(m, -IEEE80211_CRC_LEN); + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! That is why we do the + * "ieee80211_input" here, and not some lines up! + */ + RAL_UNLOCK(sc); + if (m) { + ni = ieee80211_find_rxnode(ic, + mtod(m, struct ieee80211_frame_min *)); + if (ni != NULL) { + (void) ieee80211_input(ni, m, rssi, nf); + ieee80211_free_node(ni); + } else + (void) ieee80211_input_all(ic, m, rssi, nf); + } + if ((ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0 && + !IFQ_IS_EMPTY(&ifp->if_snd)) + ural_start(ifp); + RAL_LOCK(sc); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static uint8_t +ural_plcp_signal(int rate) +{ + switch (rate) { + /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ + case 12: return 0xb; + case 18: return 0xf; + case 24: return 0xa; + case 36: return 0xe; + case 48: return 0x9; + case 72: return 0xd; + case 96: return 0x8; + case 108: return 0xc; + + /* CCK rates (NB: not IEEE std, device-specific) */ + case 2: return 0x0; + case 4: return 0x1; + case 11: return 0x2; + case 22: return 0x3; + } + return 0xff; /* XXX unsupported/unknown rate */ +} + +static void +ural_setup_tx_desc(struct ural_softc *sc, struct ural_tx_desc *desc, + uint32_t flags, int len, int rate) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint16_t plcp_length; + int remainder; + + desc->flags = htole32(flags); + desc->flags |= htole32(RAL_TX_NEWSEQ); + desc->flags |= htole32(len << 16); + + desc->wme = htole16(RAL_AIFSN(2) | RAL_LOGCWMIN(3) | RAL_LOGCWMAX(5)); + desc->wme |= htole16(RAL_IVOFFSET(sizeof (struct ieee80211_frame))); + + /* setup PLCP fields */ + desc->plcp_signal = ural_plcp_signal(rate); + desc->plcp_service = 4; + + len += IEEE80211_CRC_LEN; + if (ieee80211_rate2phytype(ic->ic_rt, rate) == IEEE80211_T_OFDM) { + desc->flags |= htole32(RAL_TX_OFDM); + + plcp_length = len & 0xfff; + desc->plcp_length_hi = plcp_length >> 6; + desc->plcp_length_lo = plcp_length & 0x3f; + } else { + plcp_length = (16 * len + rate - 1) / rate; + if (rate == 22) { + remainder = (16 * len) % 22; + if (remainder != 0 && remainder < 7) + desc->plcp_service |= RAL_PLCP_LENGEXT; + } + desc->plcp_length_hi = plcp_length >> 8; + desc->plcp_length_lo = plcp_length & 0xff; + + if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) + desc->plcp_signal |= 0x08; + } + + desc->iv = 0; + desc->eiv = 0; +} + +#define RAL_TX_TIMEOUT 5000 + +static int +ural_tx_bcn(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = sc->sc_ifp; + const struct ieee80211_txparam *tp; + struct ural_tx_data *data; + + if (sc->tx_nfree == 0) { + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + m_freem(m0); + ieee80211_free_node(ni); + return EIO; + } + data = STAILQ_FIRST(&sc->tx_free); + STAILQ_REMOVE_HEAD(&sc->tx_free, next); + sc->tx_nfree--; + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_bsschan)]; + + data->m = m0; + data->ni = ni; + data->rate = tp->mgmtrate; + + ural_setup_tx_desc(sc, &data->desc, + RAL_TX_IFS_NEWBACKOFF | RAL_TX_TIMESTAMP, m0->m_pkthdr.len, + tp->mgmtrate); + + DPRINTFN(10, "sending beacon frame len=%u rate=%u\n", + m0->m_pkthdr.len, tp->mgmtrate); + + STAILQ_INSERT_TAIL(&sc->tx_q, data, next); + usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]); + + return (0); +} + +static int +ural_tx_mgt(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_txparam *tp; + struct ural_tx_data *data; + struct ieee80211_frame *wh; + struct ieee80211_key *k; + uint32_t flags; + uint16_t dur; + + RAL_LOCK_ASSERT(sc, MA_OWNED); + + data = STAILQ_FIRST(&sc->tx_free); + STAILQ_REMOVE_HEAD(&sc->tx_free, next); + sc->tx_nfree--; + + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + + wh = mtod(m0, struct ieee80211_frame *); + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + m_freem(m0); + return ENOBUFS; + } + wh = mtod(m0, struct ieee80211_frame *); + } + + data->m = m0; + data->ni = ni; + data->rate = tp->mgmtrate; + + flags = 0; + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + flags |= RAL_TX_ACK; + + dur = ieee80211_ack_duration(ic->ic_rt, tp->mgmtrate, + ic->ic_flags & IEEE80211_F_SHPREAMBLE); + *(uint16_t *)wh->i_dur = htole16(dur); + + /* tell hardware to add timestamp for probe responses */ + if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == + IEEE80211_FC0_TYPE_MGT && + (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == + IEEE80211_FC0_SUBTYPE_PROBE_RESP) + flags |= RAL_TX_TIMESTAMP; + } + + ural_setup_tx_desc(sc, &data->desc, flags, m0->m_pkthdr.len, tp->mgmtrate); + + DPRINTFN(10, "sending mgt frame len=%u rate=%u\n", + m0->m_pkthdr.len, tp->mgmtrate); + + STAILQ_INSERT_TAIL(&sc->tx_q, data, next); + usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]); + + return 0; +} + +static int +ural_sendprot(struct ural_softc *sc, + const struct mbuf *m, struct ieee80211_node *ni, int prot, int rate) +{ + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_frame *wh; + struct ural_tx_data *data; + struct mbuf *mprot; + int protrate, ackrate, pktlen, flags, isshort; + uint16_t dur; + + KASSERT(prot == IEEE80211_PROT_RTSCTS || prot == IEEE80211_PROT_CTSONLY, + ("protection %d", prot)); + + wh = mtod(m, const struct ieee80211_frame *); + pktlen = m->m_pkthdr.len + IEEE80211_CRC_LEN; + + protrate = ieee80211_ctl_rate(ic->ic_rt, rate); + ackrate = ieee80211_ack_rate(ic->ic_rt, rate); + + isshort = (ic->ic_flags & IEEE80211_F_SHPREAMBLE) != 0; + dur = ieee80211_compute_duration(ic->ic_rt, pktlen, rate, isshort) + + ieee80211_ack_duration(ic->ic_rt, rate, isshort); + flags = RAL_TX_RETRY(7); + if (prot == IEEE80211_PROT_RTSCTS) { + /* NB: CTS is the same size as an ACK */ + dur += ieee80211_ack_duration(ic->ic_rt, rate, isshort); + flags |= RAL_TX_ACK; + mprot = ieee80211_alloc_rts(ic, wh->i_addr1, wh->i_addr2, dur); + } else { + mprot = ieee80211_alloc_cts(ic, ni->ni_vap->iv_myaddr, dur); + } + if (mprot == NULL) { + /* XXX stat + msg */ + return ENOBUFS; + } + data = STAILQ_FIRST(&sc->tx_free); + STAILQ_REMOVE_HEAD(&sc->tx_free, next); + sc->tx_nfree--; + + data->m = mprot; + data->ni = ieee80211_ref_node(ni); + data->rate = protrate; + ural_setup_tx_desc(sc, &data->desc, flags, mprot->m_pkthdr.len, protrate); + + STAILQ_INSERT_TAIL(&sc->tx_q, data, next); + usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]); + + return 0; +} + +static int +ural_tx_raw(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ural_tx_data *data; + uint32_t flags; + int error; + int rate; + + RAL_LOCK_ASSERT(sc, MA_OWNED); + KASSERT(params != NULL, ("no raw xmit params")); + + rate = params->ibp_rate0; + if (!ieee80211_isratevalid(ic->ic_rt, rate)) { + m_freem(m0); + return EINVAL; + } + flags = 0; + if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0) + flags |= RAL_TX_ACK; + if (params->ibp_flags & (IEEE80211_BPF_RTS|IEEE80211_BPF_CTS)) { + error = ural_sendprot(sc, m0, ni, + params->ibp_flags & IEEE80211_BPF_RTS ? + IEEE80211_PROT_RTSCTS : IEEE80211_PROT_CTSONLY, + rate); + if (error || sc->tx_nfree == 0) { + m_freem(m0); + return ENOBUFS; + } + flags |= RAL_TX_IFS_SIFS; + } + + data = STAILQ_FIRST(&sc->tx_free); + STAILQ_REMOVE_HEAD(&sc->tx_free, next); + sc->tx_nfree--; + + data->m = m0; + data->ni = ni; + data->rate = rate; + + /* XXX need to setup descriptor ourself */ + ural_setup_tx_desc(sc, &data->desc, flags, m0->m_pkthdr.len, rate); + + DPRINTFN(10, "sending raw frame len=%u rate=%u\n", + m0->m_pkthdr.len, rate); + + STAILQ_INSERT_TAIL(&sc->tx_q, data, next); + usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]); + + return 0; +} + +static int +ural_tx_data(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + struct ural_tx_data *data; + struct ieee80211_frame *wh; + const struct ieee80211_txparam *tp; + struct ieee80211_key *k; + uint32_t flags = 0; + uint16_t dur; + int error, rate; + + RAL_LOCK_ASSERT(sc, MA_OWNED); + + wh = mtod(m0, struct ieee80211_frame *); + + tp = &vap->iv_txparms[ieee80211_chan2mode(ni->ni_chan)]; + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) + rate = tp->mcastrate; + else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) + rate = tp->ucastrate; + else + rate = ni->ni_txrate; + + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + m_freem(m0); + return ENOBUFS; + } + /* packet header may have moved, reset our local pointer */ + wh = mtod(m0, struct ieee80211_frame *); + } + + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + int prot = IEEE80211_PROT_NONE; + if (m0->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) + prot = IEEE80211_PROT_RTSCTS; + else if ((ic->ic_flags & IEEE80211_F_USEPROT) && + ieee80211_rate2phytype(ic->ic_rt, rate) == IEEE80211_T_OFDM) + prot = ic->ic_protmode; + if (prot != IEEE80211_PROT_NONE) { + error = ural_sendprot(sc, m0, ni, prot, rate); + if (error || sc->tx_nfree == 0) { + m_freem(m0); + return ENOBUFS; + } + flags |= RAL_TX_IFS_SIFS; + } + } + + data = STAILQ_FIRST(&sc->tx_free); + STAILQ_REMOVE_HEAD(&sc->tx_free, next); + sc->tx_nfree--; + + data->m = m0; + data->ni = ni; + data->rate = rate; + + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + flags |= RAL_TX_ACK; + flags |= RAL_TX_RETRY(7); + + dur = ieee80211_ack_duration(ic->ic_rt, rate, + ic->ic_flags & IEEE80211_F_SHPREAMBLE); + *(uint16_t *)wh->i_dur = htole16(dur); + } + + ural_setup_tx_desc(sc, &data->desc, flags, m0->m_pkthdr.len, rate); + + DPRINTFN(10, "sending data frame len=%u rate=%u\n", + m0->m_pkthdr.len, rate); + + STAILQ_INSERT_TAIL(&sc->tx_q, data, next); + usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]); + + return 0; +} + +static void +ural_start(struct ifnet *ifp) +{ + struct ural_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + struct mbuf *m; + + RAL_LOCK(sc); + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + RAL_UNLOCK(sc); + return; + } + for (;;) { + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + if (sc->tx_nfree < RAL_TX_MINFREE) { + IFQ_DRV_PREPEND(&ifp->if_snd, m); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + break; + } + ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; + if (ural_tx_data(sc, m, ni) != 0) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + break; + } + } + RAL_UNLOCK(sc); +} + +static int +ural_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct ural_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *) data; + int error = 0, startall = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + RAL_LOCK(sc); + if (ifp->if_flags & IFF_UP) { + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + ural_init_locked(sc); + startall = 1; + } else + ural_setpromisc(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ural_stop(sc); + } + RAL_UNLOCK(sc); + if (startall) + ieee80211_start_all(ic); + break; + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + default: + error = ether_ioctl(ifp, cmd, data); + break; + } + return error; +} + +static void +ural_set_testmode(struct ural_softc *sc) +{ + struct usb_device_request req; + usb_error_t error; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RAL_VENDOR_REQUEST; + USETW(req.wValue, 4); + USETW(req.wIndex, 1); + USETW(req.wLength, 0); + + error = ural_do_request(sc, &req, NULL); + if (error != 0) { + device_printf(sc->sc_dev, "could not set test mode: %s\n", + usbd_errstr(error)); + } +} + +static void +ural_eeprom_read(struct ural_softc *sc, uint16_t addr, void *buf, int len) +{ + struct usb_device_request req; + usb_error_t error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RAL_READ_EEPROM; + USETW(req.wValue, 0); + USETW(req.wIndex, addr); + USETW(req.wLength, len); + + error = ural_do_request(sc, &req, buf); + if (error != 0) { + device_printf(sc->sc_dev, "could not read EEPROM: %s\n", + usbd_errstr(error)); + } +} + +static uint16_t +ural_read(struct ural_softc *sc, uint16_t reg) +{ + struct usb_device_request req; + usb_error_t error; + uint16_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RAL_READ_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, sizeof (uint16_t)); + + error = ural_do_request(sc, &req, &val); + if (error != 0) { + device_printf(sc->sc_dev, "could not read MAC register: %s\n", + usbd_errstr(error)); + return 0; + } + + return le16toh(val); +} + +static void +ural_read_multi(struct ural_softc *sc, uint16_t reg, void *buf, int len) +{ + struct usb_device_request req; + usb_error_t error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RAL_READ_MULTI_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, len); + + error = ural_do_request(sc, &req, buf); + if (error != 0) { + device_printf(sc->sc_dev, "could not read MAC register: %s\n", + usbd_errstr(error)); + } +} + +static void +ural_write(struct ural_softc *sc, uint16_t reg, uint16_t val) +{ + struct usb_device_request req; + usb_error_t error; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RAL_WRITE_MAC; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + error = ural_do_request(sc, &req, NULL); + if (error != 0) { + device_printf(sc->sc_dev, "could not write MAC register: %s\n", + usbd_errstr(error)); + } +} + +static void +ural_write_multi(struct ural_softc *sc, uint16_t reg, void *buf, int len) +{ + struct usb_device_request req; + usb_error_t error; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RAL_WRITE_MULTI_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, len); + + error = ural_do_request(sc, &req, buf); + if (error != 0) { + device_printf(sc->sc_dev, "could not write MAC register: %s\n", + usbd_errstr(error)); + } +} + +static void +ural_bbp_write(struct ural_softc *sc, uint8_t reg, uint8_t val) +{ + uint16_t tmp; + int ntries; + + for (ntries = 0; ntries < 100; ntries++) { + if (!(ural_read(sc, RAL_PHY_CSR8) & RAL_BBP_BUSY)) + break; + if (ural_pause(sc, hz / 100)) + break; + } + if (ntries == 100) { + device_printf(sc->sc_dev, "could not write to BBP\n"); + return; + } + + tmp = reg << 8 | val; + ural_write(sc, RAL_PHY_CSR7, tmp); +} + +static uint8_t +ural_bbp_read(struct ural_softc *sc, uint8_t reg) +{ + uint16_t val; + int ntries; + + val = RAL_BBP_WRITE | reg << 8; + ural_write(sc, RAL_PHY_CSR7, val); + + for (ntries = 0; ntries < 100; ntries++) { + if (!(ural_read(sc, RAL_PHY_CSR8) & RAL_BBP_BUSY)) + break; + if (ural_pause(sc, hz / 100)) + break; + } + if (ntries == 100) { + device_printf(sc->sc_dev, "could not read BBP\n"); + return 0; + } + + return ural_read(sc, RAL_PHY_CSR7) & 0xff; +} + +static void +ural_rf_write(struct ural_softc *sc, uint8_t reg, uint32_t val) +{ + uint32_t tmp; + int ntries; + + for (ntries = 0; ntries < 100; ntries++) { + if (!(ural_read(sc, RAL_PHY_CSR10) & RAL_RF_LOBUSY)) + break; + if (ural_pause(sc, hz / 100)) + break; + } + if (ntries == 100) { + device_printf(sc->sc_dev, "could not write to RF\n"); + return; + } + + tmp = RAL_RF_BUSY | RAL_RF_20BIT | (val & 0xfffff) << 2 | (reg & 0x3); + ural_write(sc, RAL_PHY_CSR9, tmp & 0xffff); + ural_write(sc, RAL_PHY_CSR10, tmp >> 16); + + /* remember last written value in sc */ + sc->rf_regs[reg] = val; + + DPRINTFN(15, "RF R[%u] <- 0x%05x\n", reg & 0x3, val & 0xfffff); +} + +static void +ural_scan_start(struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct ural_softc *sc = ifp->if_softc; + + RAL_LOCK(sc); + ural_write(sc, RAL_TXRX_CSR19, 0); + ural_set_bssid(sc, ifp->if_broadcastaddr); + RAL_UNLOCK(sc); +} + +static void +ural_scan_end(struct ieee80211com *ic) +{ + struct ural_softc *sc = ic->ic_ifp->if_softc; + + RAL_LOCK(sc); + ural_enable_tsf_sync(sc); + ural_set_bssid(sc, sc->sc_bssid); + RAL_UNLOCK(sc); + +} + +static void +ural_set_channel(struct ieee80211com *ic) +{ + struct ural_softc *sc = ic->ic_ifp->if_softc; + + RAL_LOCK(sc); + ural_set_chan(sc, ic->ic_curchan); + RAL_UNLOCK(sc); +} + +static void +ural_set_chan(struct ural_softc *sc, struct ieee80211_channel *c) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint8_t power, tmp; + int i, chan; + + chan = ieee80211_chan2ieee(ic, c); + if (chan == 0 || chan == IEEE80211_CHAN_ANY) + return; + + if (IEEE80211_IS_CHAN_2GHZ(c)) + power = min(sc->txpow[chan - 1], 31); + else + power = 31; + + /* adjust txpower using ifconfig settings */ + power -= (100 - ic->ic_txpowlimit) / 8; + + DPRINTFN(2, "setting channel to %u, txpower to %u\n", chan, power); + + switch (sc->rf_rev) { + case RAL_RF_2522: + ural_rf_write(sc, RAL_RF1, 0x00814); + ural_rf_write(sc, RAL_RF2, ural_rf2522_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x00040); + break; + + case RAL_RF_2523: + ural_rf_write(sc, RAL_RF1, 0x08804); + ural_rf_write(sc, RAL_RF2, ural_rf2523_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x38044); + ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); + break; + + case RAL_RF_2524: + ural_rf_write(sc, RAL_RF1, 0x0c808); + ural_rf_write(sc, RAL_RF2, ural_rf2524_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x00040); + ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); + break; + + case RAL_RF_2525: + ural_rf_write(sc, RAL_RF1, 0x08808); + ural_rf_write(sc, RAL_RF2, ural_rf2525_hi_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); + ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); + + ural_rf_write(sc, RAL_RF1, 0x08808); + ural_rf_write(sc, RAL_RF2, ural_rf2525_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); + ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); + break; + + case RAL_RF_2525E: + ural_rf_write(sc, RAL_RF1, 0x08808); + ural_rf_write(sc, RAL_RF2, ural_rf2525e_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); + ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00286 : 0x00282); + break; + + case RAL_RF_2526: + ural_rf_write(sc, RAL_RF2, ural_rf2526_hi_r2[chan - 1]); + ural_rf_write(sc, RAL_RF4, (chan & 1) ? 0x00386 : 0x00381); + ural_rf_write(sc, RAL_RF1, 0x08804); + + ural_rf_write(sc, RAL_RF2, ural_rf2526_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); + ural_rf_write(sc, RAL_RF4, (chan & 1) ? 0x00386 : 0x00381); + break; + + /* dual-band RF */ + case RAL_RF_5222: + for (i = 0; ural_rf5222[i].chan != chan; i++); + + ural_rf_write(sc, RAL_RF1, ural_rf5222[i].r1); + ural_rf_write(sc, RAL_RF2, ural_rf5222[i].r2); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x00040); + ural_rf_write(sc, RAL_RF4, ural_rf5222[i].r4); + break; + } + + if (ic->ic_opmode != IEEE80211_M_MONITOR && + (ic->ic_flags & IEEE80211_F_SCAN) == 0) { + /* set Japan filter bit for channel 14 */ + tmp = ural_bbp_read(sc, 70); + + tmp &= ~RAL_JAPAN_FILTER; + if (chan == 14) + tmp |= RAL_JAPAN_FILTER; + + ural_bbp_write(sc, 70, tmp); + + /* clear CRC errors */ + ural_read(sc, RAL_STA_CSR0); + + ural_pause(sc, hz / 100); + ural_disable_rf_tune(sc); + } + + /* XXX doesn't belong here */ + /* update basic rate set */ + ural_set_basicrates(sc, c); + + /* give the hardware some time to do the switchover */ + ural_pause(sc, hz / 100); +} + +/* + * Disable RF auto-tuning. + */ +static void +ural_disable_rf_tune(struct ural_softc *sc) +{ + uint32_t tmp; + + if (sc->rf_rev != RAL_RF_2523) { + tmp = sc->rf_regs[RAL_RF1] & ~RAL_RF1_AUTOTUNE; + ural_rf_write(sc, RAL_RF1, tmp); + } + + tmp = sc->rf_regs[RAL_RF3] & ~RAL_RF3_AUTOTUNE; + ural_rf_write(sc, RAL_RF3, tmp); + + DPRINTFN(2, "disabling RF autotune\n"); +} + +/* + * Refer to IEEE Std 802.11-1999 pp. 123 for more information on TSF + * synchronization. + */ +static void +ural_enable_tsf_sync(struct ural_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + uint16_t logcwmin, preload, tmp; + + /* first, disable TSF synchronization */ + ural_write(sc, RAL_TXRX_CSR19, 0); + + tmp = (16 * vap->iv_bss->ni_intval) << 4; + ural_write(sc, RAL_TXRX_CSR18, tmp); + + logcwmin = (ic->ic_opmode == IEEE80211_M_IBSS) ? 2 : 0; + preload = (ic->ic_opmode == IEEE80211_M_IBSS) ? 320 : 6; + tmp = logcwmin << 12 | preload; + ural_write(sc, RAL_TXRX_CSR20, tmp); + + /* finally, enable TSF synchronization */ + tmp = RAL_ENABLE_TSF | RAL_ENABLE_TBCN; + if (ic->ic_opmode == IEEE80211_M_STA) + tmp |= RAL_ENABLE_TSF_SYNC(1); + else + tmp |= RAL_ENABLE_TSF_SYNC(2) | RAL_ENABLE_BEACON_GENERATOR; + ural_write(sc, RAL_TXRX_CSR19, tmp); + + DPRINTF("enabling TSF synchronization\n"); +} + +static void +ural_enable_tsf(struct ural_softc *sc) +{ + /* first, disable TSF synchronization */ + ural_write(sc, RAL_TXRX_CSR19, 0); + ural_write(sc, RAL_TXRX_CSR19, RAL_ENABLE_TSF | RAL_ENABLE_TSF_SYNC(2)); +} + +#define RAL_RXTX_TURNAROUND 5 /* us */ +static void +ural_update_slot(struct ifnet *ifp) +{ + struct ural_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + uint16_t slottime, sifs, eifs; + + slottime = (ic->ic_flags & IEEE80211_F_SHSLOT) ? 9 : 20; + + /* + * These settings may sound a bit inconsistent but this is what the + * reference driver does. + */ + if (ic->ic_curmode == IEEE80211_MODE_11B) { + sifs = 16 - RAL_RXTX_TURNAROUND; + eifs = 364; + } else { + sifs = 10 - RAL_RXTX_TURNAROUND; + eifs = 64; + } + + ural_write(sc, RAL_MAC_CSR10, slottime); + ural_write(sc, RAL_MAC_CSR11, sifs); + ural_write(sc, RAL_MAC_CSR12, eifs); +} + +static void +ural_set_txpreamble(struct ural_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint16_t tmp; + + tmp = ural_read(sc, RAL_TXRX_CSR10); + + tmp &= ~RAL_SHORT_PREAMBLE; + if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) + tmp |= RAL_SHORT_PREAMBLE; + + ural_write(sc, RAL_TXRX_CSR10, tmp); +} + +static void +ural_set_basicrates(struct ural_softc *sc, const struct ieee80211_channel *c) +{ + /* XXX wrong, take from rate set */ + /* update basic rate set */ + if (IEEE80211_IS_CHAN_5GHZ(c)) { + /* 11a basic rates: 6, 12, 24Mbps */ + ural_write(sc, RAL_TXRX_CSR11, 0x150); + } else if (IEEE80211_IS_CHAN_ANYG(c)) { + /* 11g basic rates: 1, 2, 5.5, 11, 6, 12, 24Mbps */ + ural_write(sc, RAL_TXRX_CSR11, 0x15f); + } else { + /* 11b basic rates: 1, 2Mbps */ + ural_write(sc, RAL_TXRX_CSR11, 0x3); + } +} + +static void +ural_set_bssid(struct ural_softc *sc, const uint8_t *bssid) +{ + uint16_t tmp; + + tmp = bssid[0] | bssid[1] << 8; + ural_write(sc, RAL_MAC_CSR5, tmp); + + tmp = bssid[2] | bssid[3] << 8; + ural_write(sc, RAL_MAC_CSR6, tmp); + + tmp = bssid[4] | bssid[5] << 8; + ural_write(sc, RAL_MAC_CSR7, tmp); + + DPRINTF("setting BSSID to %6D\n", bssid, ":"); +} + +static void +ural_set_macaddr(struct ural_softc *sc, uint8_t *addr) +{ + uint16_t tmp; + + tmp = addr[0] | addr[1] << 8; + ural_write(sc, RAL_MAC_CSR2, tmp); + + tmp = addr[2] | addr[3] << 8; + ural_write(sc, RAL_MAC_CSR3, tmp); + + tmp = addr[4] | addr[5] << 8; + ural_write(sc, RAL_MAC_CSR4, tmp); + + DPRINTF("setting MAC address to %6D\n", addr, ":"); +} + +static void +ural_setpromisc(struct ural_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + uint32_t tmp; + + tmp = ural_read(sc, RAL_TXRX_CSR2); + + tmp &= ~RAL_DROP_NOT_TO_ME; + if (!(ifp->if_flags & IFF_PROMISC)) + tmp |= RAL_DROP_NOT_TO_ME; + + ural_write(sc, RAL_TXRX_CSR2, tmp); + + DPRINTF("%s promiscuous mode\n", (ifp->if_flags & IFF_PROMISC) ? + "entering" : "leaving"); +} + +static void +ural_update_promisc(struct ifnet *ifp) +{ + struct ural_softc *sc = ifp->if_softc; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + RAL_LOCK(sc); + ural_setpromisc(sc); + RAL_UNLOCK(sc); +} + +static const char * +ural_get_rf(int rev) +{ + switch (rev) { + case RAL_RF_2522: return "RT2522"; + case RAL_RF_2523: return "RT2523"; + case RAL_RF_2524: return "RT2524"; + case RAL_RF_2525: return "RT2525"; + case RAL_RF_2525E: return "RT2525e"; + case RAL_RF_2526: return "RT2526"; + case RAL_RF_5222: return "RT5222"; + default: return "unknown"; + } +} + +static void +ural_read_eeprom(struct ural_softc *sc) +{ + uint16_t val; + + ural_eeprom_read(sc, RAL_EEPROM_CONFIG0, &val, 2); + val = le16toh(val); + sc->rf_rev = (val >> 11) & 0x7; + sc->hw_radio = (val >> 10) & 0x1; + sc->led_mode = (val >> 6) & 0x7; + sc->rx_ant = (val >> 4) & 0x3; + sc->tx_ant = (val >> 2) & 0x3; + sc->nb_ant = val & 0x3; + + /* read MAC address */ + ural_eeprom_read(sc, RAL_EEPROM_ADDRESS, sc->sc_bssid, 6); + + /* read default values for BBP registers */ + ural_eeprom_read(sc, RAL_EEPROM_BBP_BASE, sc->bbp_prom, 2 * 16); + + /* read Tx power for all b/g channels */ + ural_eeprom_read(sc, RAL_EEPROM_TXPOWER, sc->txpow, 14); +} + +static int +ural_bbp_init(struct ural_softc *sc) +{ +#define N(a) (sizeof (a) / sizeof ((a)[0])) + int i, ntries; + + /* wait for BBP to be ready */ + for (ntries = 0; ntries < 100; ntries++) { + if (ural_bbp_read(sc, RAL_BBP_VERSION) != 0) + break; + if (ural_pause(sc, hz / 100)) + break; + } + if (ntries == 100) { + device_printf(sc->sc_dev, "timeout waiting for BBP\n"); + return EIO; + } + + /* initialize BBP registers to default values */ + for (i = 0; i < N(ural_def_bbp); i++) + ural_bbp_write(sc, ural_def_bbp[i].reg, ural_def_bbp[i].val); + +#if 0 + /* initialize BBP registers to values stored in EEPROM */ + for (i = 0; i < 16; i++) { + if (sc->bbp_prom[i].reg == 0xff) + continue; + ural_bbp_write(sc, sc->bbp_prom[i].reg, sc->bbp_prom[i].val); + } +#endif + + return 0; +#undef N +} + +static void +ural_set_txantenna(struct ural_softc *sc, int antenna) +{ + uint16_t tmp; + uint8_t tx; + + tx = ural_bbp_read(sc, RAL_BBP_TX) & ~RAL_BBP_ANTMASK; + if (antenna == 1) + tx |= RAL_BBP_ANTA; + else if (antenna == 2) + tx |= RAL_BBP_ANTB; + else + tx |= RAL_BBP_DIVERSITY; + + /* need to force I/Q flip for RF 2525e, 2526 and 5222 */ + if (sc->rf_rev == RAL_RF_2525E || sc->rf_rev == RAL_RF_2526 || + sc->rf_rev == RAL_RF_5222) + tx |= RAL_BBP_FLIPIQ; + + ural_bbp_write(sc, RAL_BBP_TX, tx); + + /* update values in PHY_CSR5 and PHY_CSR6 */ + tmp = ural_read(sc, RAL_PHY_CSR5) & ~0x7; + ural_write(sc, RAL_PHY_CSR5, tmp | (tx & 0x7)); + + tmp = ural_read(sc, RAL_PHY_CSR6) & ~0x7; + ural_write(sc, RAL_PHY_CSR6, tmp | (tx & 0x7)); +} + +static void +ural_set_rxantenna(struct ural_softc *sc, int antenna) +{ + uint8_t rx; + + rx = ural_bbp_read(sc, RAL_BBP_RX) & ~RAL_BBP_ANTMASK; + if (antenna == 1) + rx |= RAL_BBP_ANTA; + else if (antenna == 2) + rx |= RAL_BBP_ANTB; + else + rx |= RAL_BBP_DIVERSITY; + + /* need to force no I/Q flip for RF 2525e and 2526 */ + if (sc->rf_rev == RAL_RF_2525E || sc->rf_rev == RAL_RF_2526) + rx &= ~RAL_BBP_FLIPIQ; + + ural_bbp_write(sc, RAL_BBP_RX, rx); +} + +static void +ural_init_locked(struct ural_softc *sc) +{ +#define N(a) (sizeof (a) / sizeof ((a)[0])) + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint16_t tmp; + int i, ntries; + + RAL_LOCK_ASSERT(sc, MA_OWNED); + + ural_set_testmode(sc); + ural_write(sc, 0x308, 0x00f0); /* XXX magic */ + + ural_stop(sc); + + /* initialize MAC registers to default values */ + for (i = 0; i < N(ural_def_mac); i++) + ural_write(sc, ural_def_mac[i].reg, ural_def_mac[i].val); + + /* wait for BBP and RF to wake up (this can take a long time!) */ + for (ntries = 0; ntries < 100; ntries++) { + tmp = ural_read(sc, RAL_MAC_CSR17); + if ((tmp & (RAL_BBP_AWAKE | RAL_RF_AWAKE)) == + (RAL_BBP_AWAKE | RAL_RF_AWAKE)) + break; + if (ural_pause(sc, hz / 100)) + break; + } + if (ntries == 100) { + device_printf(sc->sc_dev, + "timeout waiting for BBP/RF to wakeup\n"); + goto fail; + } + + /* we're ready! */ + ural_write(sc, RAL_MAC_CSR1, RAL_HOST_READY); + + /* set basic rate set (will be updated later) */ + ural_write(sc, RAL_TXRX_CSR11, 0x15f); + + if (ural_bbp_init(sc) != 0) + goto fail; + + ural_set_chan(sc, ic->ic_curchan); + + /* clear statistic registers (STA_CSR0 to STA_CSR10) */ + ural_read_multi(sc, RAL_STA_CSR0, sc->sta, sizeof sc->sta); + + ural_set_txantenna(sc, sc->tx_ant); + ural_set_rxantenna(sc, sc->rx_ant); + + ural_set_macaddr(sc, IF_LLADDR(ifp)); + + /* + * Allocate Tx and Rx xfer queues. + */ + ural_setup_tx_list(sc); + + /* kick Rx */ + tmp = RAL_DROP_PHY | RAL_DROP_CRC; + if (ic->ic_opmode != IEEE80211_M_MONITOR) { + tmp |= RAL_DROP_CTL | RAL_DROP_BAD_VERSION; + if (ic->ic_opmode != IEEE80211_M_HOSTAP) + tmp |= RAL_DROP_TODS; + if (!(ifp->if_flags & IFF_PROMISC)) + tmp |= RAL_DROP_NOT_TO_ME; + } + ural_write(sc, RAL_TXRX_CSR2, tmp); + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + usbd_xfer_set_stall(sc->sc_xfer[URAL_BULK_WR]); + usbd_transfer_start(sc->sc_xfer[URAL_BULK_RD]); + return; + +fail: ural_stop(sc); +#undef N +} + +static void +ural_init(void *priv) +{ + struct ural_softc *sc = priv; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + RAL_LOCK(sc); + ural_init_locked(sc); + RAL_UNLOCK(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ieee80211_start_all(ic); /* start all vap's */ +} + +static void +ural_stop(struct ural_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + + RAL_LOCK_ASSERT(sc, MA_OWNED); + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + /* + * Drain all the transfers, if not already drained: + */ + RAL_UNLOCK(sc); + usbd_transfer_drain(sc->sc_xfer[URAL_BULK_WR]); + usbd_transfer_drain(sc->sc_xfer[URAL_BULK_RD]); + RAL_LOCK(sc); + + ural_unsetup_tx_list(sc); + + /* disable Rx */ + ural_write(sc, RAL_TXRX_CSR2, RAL_DISABLE_RX); + /* reset ASIC and BBP (but won't reset MAC registers!) */ + ural_write(sc, RAL_MAC_CSR1, RAL_RESET_ASIC | RAL_RESET_BBP); + /* wait a little */ + ural_pause(sc, hz / 10); + ural_write(sc, RAL_MAC_CSR1, 0); + /* wait a little */ + ural_pause(sc, hz / 10); +} + +static int +ural_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct ural_softc *sc = ifp->if_softc; + + RAL_LOCK(sc); + /* prevent management frames from being sent if we're not ready */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + RAL_UNLOCK(sc); + m_freem(m); + ieee80211_free_node(ni); + return ENETDOWN; + } + if (sc->tx_nfree < RAL_TX_MINFREE) { + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + RAL_UNLOCK(sc); + m_freem(m); + ieee80211_free_node(ni); + return EIO; + } + + ifp->if_opackets++; + + if (params == NULL) { + /* + * Legacy path; interpret frame contents to decide + * precisely how to send the frame. + */ + if (ural_tx_mgt(sc, m, ni) != 0) + goto bad; + } else { + /* + * Caller supplied explicit parameters to use in + * sending the frame. + */ + if (ural_tx_raw(sc, m, ni, params) != 0) + goto bad; + } + RAL_UNLOCK(sc); + return 0; +bad: + ifp->if_oerrors++; + RAL_UNLOCK(sc); + ieee80211_free_node(ni); + return EIO; /* XXX */ +} + +static void +ural_ratectl_start(struct ural_softc *sc, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ural_vap *uvp = URAL_VAP(vap); + + /* clear statistic registers (STA_CSR0 to STA_CSR10) */ + ural_read_multi(sc, RAL_STA_CSR0, sc->sta, sizeof sc->sta); + + usb_callout_reset(&uvp->ratectl_ch, hz, ural_ratectl_timeout, uvp); +} + +static void +ural_ratectl_timeout(void *arg) +{ + struct ural_vap *uvp = arg; + struct ieee80211vap *vap = &uvp->vap; + struct ieee80211com *ic = vap->iv_ic; + + ieee80211_runtask(ic, &uvp->ratectl_task); +} + +static void +ural_ratectl_task(void *arg, int pending) +{ + struct ural_vap *uvp = arg; + struct ieee80211vap *vap = &uvp->vap; + struct ieee80211com *ic = vap->iv_ic; + struct ifnet *ifp = ic->ic_ifp; + struct ural_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + int ok, fail; + int sum, retrycnt; + + ni = ieee80211_ref_node(vap->iv_bss); + RAL_LOCK(sc); + /* read and clear statistic registers (STA_CSR0 to STA_CSR10) */ + ural_read_multi(sc, RAL_STA_CSR0, sc->sta, sizeof(sc->sta)); + + ok = sc->sta[7] + /* TX ok w/o retry */ + sc->sta[8]; /* TX ok w/ retry */ + fail = sc->sta[9]; /* TX retry-fail count */ + sum = ok+fail; + retrycnt = sc->sta[8] + fail; + + ieee80211_ratectl_tx_update(vap, ni, &sum, &ok, &retrycnt); + (void) ieee80211_ratectl_rate(ni, NULL, 0); + + ifp->if_oerrors += fail; /* count TX retry-fail as Tx errors */ + + usb_callout_reset(&uvp->ratectl_ch, hz, ural_ratectl_timeout, uvp); + RAL_UNLOCK(sc); + ieee80211_free_node(ni); +} + +static int +ural_pause(struct ural_softc *sc, int timeout) +{ + + usb_pause_mtx(&sc->sc_mtx, timeout); + return (0); +} diff --git a/sys/bus/u4b/wlan/if_uralreg.h b/sys/bus/u4b/wlan/if_uralreg.h new file mode 100644 index 0000000000..042cf5ace5 --- /dev/null +++ b/sys/bus/u4b/wlan/if_uralreg.h @@ -0,0 +1,211 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005, 2006 + * Damien Bergamini + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define RAL_NOISE_FLOOR -95 +#define RAL_RSSI_CORR 120 + +#define RAL_RX_DESC_SIZE (sizeof (struct ural_rx_desc)) +#define RAL_TX_DESC_SIZE (sizeof (struct ural_tx_desc)) +#define RAL_FRAME_SIZE 0x780 /* NOTE: using 0x980 does not work */ + +#define RAL_CONFIG_NO 1 +#define RAL_IFACE_INDEX 0 + +#define RAL_VENDOR_REQUEST 0x01 +#define RAL_WRITE_MAC 0x02 +#define RAL_READ_MAC 0x03 +#define RAL_WRITE_MULTI_MAC 0x06 +#define RAL_READ_MULTI_MAC 0x07 +#define RAL_READ_EEPROM 0x09 + +/* + * MAC registers. + */ +#define RAL_MAC_CSR0 0x0400 /* ASIC Version */ +#define RAL_MAC_CSR1 0x0402 /* System control */ +#define RAL_MAC_CSR2 0x0404 /* MAC addr0 */ +#define RAL_MAC_CSR3 0x0406 /* MAC addr1 */ +#define RAL_MAC_CSR4 0x0408 /* MAC addr2 */ +#define RAL_MAC_CSR5 0x040a /* BSSID0 */ +#define RAL_MAC_CSR6 0x040c /* BSSID1 */ +#define RAL_MAC_CSR7 0x040e /* BSSID2 */ +#define RAL_MAC_CSR8 0x0410 /* Max frame length */ +#define RAL_MAC_CSR9 0x0412 /* Timer control */ +#define RAL_MAC_CSR10 0x0414 /* Slot time */ +#define RAL_MAC_CSR11 0x0416 /* IFS */ +#define RAL_MAC_CSR12 0x0418 /* EIFS */ +#define RAL_MAC_CSR13 0x041a /* Power mode0 */ +#define RAL_MAC_CSR14 0x041c /* Power mode1 */ +#define RAL_MAC_CSR15 0x041e /* Power saving transition0 */ +#define RAL_MAC_CSR16 0x0420 /* Power saving transition1 */ +#define RAL_MAC_CSR17 0x0422 /* Power state control */ +#define RAL_MAC_CSR18 0x0424 /* Auto wake-up control */ +#define RAL_MAC_CSR19 0x0426 /* GPIO control */ +#define RAL_MAC_CSR20 0x0428 /* LED control0 */ +#define RAL_MAC_CSR22 0x042c /* XXX not documented */ + +/* + * Tx/Rx Registers. + */ +#define RAL_TXRX_CSR0 0x0440 /* Security control */ +#define RAL_TXRX_CSR2 0x0444 /* Rx control */ +#define RAL_TXRX_CSR5 0x044a /* CCK Tx BBP ID0 */ +#define RAL_TXRX_CSR6 0x044c /* CCK Tx BBP ID1 */ +#define RAL_TXRX_CSR7 0x044e /* OFDM Tx BBP ID0 */ +#define RAL_TXRX_CSR8 0x0450 /* OFDM Tx BBP ID1 */ +#define RAL_TXRX_CSR10 0x0454 /* Auto responder control */ +#define RAL_TXRX_CSR11 0x0456 /* Auto responder basic rate */ +#define RAL_TXRX_CSR18 0x0464 /* Beacon interval */ +#define RAL_TXRX_CSR19 0x0466 /* Beacon/sync control */ +#define RAL_TXRX_CSR20 0x0468 /* Beacon alignment */ +#define RAL_TXRX_CSR21 0x046a /* XXX not documented */ + +/* + * Security registers. + */ +#define RAL_SEC_CSR0 0x0480 /* Shared key 0, word 0 */ + +/* + * PHY registers. + */ +#define RAL_PHY_CSR2 0x04c4 /* Tx MAC configuration */ +#define RAL_PHY_CSR4 0x04c8 /* Interface configuration */ +#define RAL_PHY_CSR5 0x04ca /* BBP Pre-Tx CCK */ +#define RAL_PHY_CSR6 0x04cc /* BBP Pre-Tx OFDM */ +#define RAL_PHY_CSR7 0x04ce /* BBP serial control */ +#define RAL_PHY_CSR8 0x04d0 /* BBP serial status */ +#define RAL_PHY_CSR9 0x04d2 /* RF serial control0 */ +#define RAL_PHY_CSR10 0x04d4 /* RF serial control1 */ + +/* + * Statistics registers. + */ +#define RAL_STA_CSR0 0x04e0 /* FCS error */ + + +#define RAL_DISABLE_RX (1 << 0) +#define RAL_DROP_CRC (1 << 1) +#define RAL_DROP_PHY (1 << 2) +#define RAL_DROP_CTL (1 << 3) +#define RAL_DROP_NOT_TO_ME (1 << 4) +#define RAL_DROP_TODS (1 << 5) +#define RAL_DROP_BAD_VERSION (1 << 6) +#define RAL_DROP_MULTICAST (1 << 9) +#define RAL_DROP_BROADCAST (1 << 10) + +#define RAL_SHORT_PREAMBLE (1 << 2) + +#define RAL_RESET_ASIC (1 << 0) +#define RAL_RESET_BBP (1 << 1) +#define RAL_HOST_READY (1 << 2) + +#define RAL_ENABLE_TSF (1 << 0) +#define RAL_ENABLE_TSF_SYNC(x) (((x) & 0x3) << 1) +#define RAL_ENABLE_TBCN (1 << 3) +#define RAL_ENABLE_BEACON_GENERATOR (1 << 4) + +#define RAL_RF_AWAKE (3 << 7) +#define RAL_BBP_AWAKE (3 << 5) + +#define RAL_BBP_WRITE (1 << 15) +#define RAL_BBP_BUSY (1 << 0) + +#define RAL_RF1_AUTOTUNE 0x08000 +#define RAL_RF3_AUTOTUNE 0x00040 + +#define RAL_RF_2522 0x00 +#define RAL_RF_2523 0x01 +#define RAL_RF_2524 0x02 +#define RAL_RF_2525 0x03 +#define RAL_RF_2525E 0x04 +#define RAL_RF_2526 0x05 +/* dual-band RF */ +#define RAL_RF_5222 0x10 + +#define RAL_BBP_VERSION 0 +#define RAL_BBP_TX 2 +#define RAL_BBP_RX 14 + +#define RAL_BBP_ANTA 0x00 +#define RAL_BBP_DIVERSITY 0x01 +#define RAL_BBP_ANTB 0x02 +#define RAL_BBP_ANTMASK 0x03 +#define RAL_BBP_FLIPIQ 0x04 + +#define RAL_JAPAN_FILTER 0x08 + +struct ural_tx_desc { + uint32_t flags; +#define RAL_TX_RETRY(x) ((x) << 4) +#define RAL_TX_MORE_FRAG (1 << 8) +#define RAL_TX_ACK (1 << 9) +#define RAL_TX_TIMESTAMP (1 << 10) +#define RAL_TX_OFDM (1 << 11) +#define RAL_TX_NEWSEQ (1 << 12) + +#define RAL_TX_IFS_MASK 0x00006000 +#define RAL_TX_IFS_BACKOFF (0 << 13) +#define RAL_TX_IFS_SIFS (1 << 13) +#define RAL_TX_IFS_NEWBACKOFF (2 << 13) +#define RAL_TX_IFS_NONE (3 << 13) + + uint16_t wme; +#define RAL_LOGCWMAX(x) (((x) & 0xf) << 12) +#define RAL_LOGCWMIN(x) (((x) & 0xf) << 8) +#define RAL_AIFSN(x) (((x) & 0x3) << 6) +#define RAL_IVOFFSET(x) (((x) & 0x3f)) + + uint16_t reserved1; + uint8_t plcp_signal; + uint8_t plcp_service; +#define RAL_PLCP_LENGEXT 0x80 + + uint8_t plcp_length_lo; + uint8_t plcp_length_hi; + uint32_t iv; + uint32_t eiv; +} __packed; + +struct ural_rx_desc { + uint32_t flags; +#define RAL_RX_CRC_ERROR (1 << 5) +#define RAL_RX_OFDM (1 << 6) +#define RAL_RX_PHY_ERROR (1 << 7) + + uint8_t rssi; + uint8_t rate; + uint16_t reserved; + + uint32_t iv; + uint32_t eiv; +} __packed; + +#define RAL_RF_LOBUSY (1 << 15) +#define RAL_RF_BUSY (1 << 31) +#define RAL_RF_20BIT (20 << 24) + +#define RAL_RF1 0 +#define RAL_RF2 2 +#define RAL_RF3 1 +#define RAL_RF4 3 + +#define RAL_EEPROM_ADDRESS 0x0004 +#define RAL_EEPROM_TXPOWER 0x003c +#define RAL_EEPROM_CONFIG0 0x0016 +#define RAL_EEPROM_BBP_BASE 0x001c diff --git a/sys/bus/u4b/wlan/if_uralvar.h b/sys/bus/u4b/wlan/if_uralvar.h new file mode 100644 index 0000000000..46dbfbddc8 --- /dev/null +++ b/sys/bus/u4b/wlan/if_uralvar.h @@ -0,0 +1,134 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005 + * Damien Bergamini + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define RAL_TX_LIST_COUNT 8 +#define RAL_TX_MINFREE 2 + +#define URAL_SCAN_START 1 +#define URAL_SCAN_END 2 +#define URAL_SET_CHANNEL 3 + + +struct ural_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint8_t wr_rate; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + int8_t wr_antsignal; + int8_t wr_antnoise; + uint8_t wr_antenna; +}; + +#define RAL_RX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE)) + +struct ural_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint8_t wt_rate; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; + uint8_t wt_antenna; +}; + +#define RAL_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA)) + +struct ural_softc; + +struct ural_tx_data { + STAILQ_ENTRY(ural_tx_data) next; + struct ural_softc *sc; + struct ural_tx_desc desc; + struct mbuf *m; + struct ieee80211_node *ni; + int rate; +}; +typedef STAILQ_HEAD(, ural_tx_data) ural_txdhead; + +struct ural_vap { + struct ieee80211vap vap; + struct ieee80211_beacon_offsets bo; + struct usb_callout ratectl_ch; + struct task ratectl_task; + + int (*newstate)(struct ieee80211vap *, + enum ieee80211_state, int); +}; +#define URAL_VAP(vap) ((struct ural_vap *)(vap)) + +enum { + URAL_BULK_WR, + URAL_BULK_RD, + URAL_N_TRANSFER = 2, +}; + +struct ural_softc { + struct ifnet *sc_ifp; + device_t sc_dev; + struct usb_device *sc_udev; + + uint32_t asic_rev; + uint8_t rf_rev; + + struct usb_xfer *sc_xfer[URAL_N_TRANSFER]; + + struct ural_tx_data tx_data[RAL_TX_LIST_COUNT]; + ural_txdhead tx_q; + ural_txdhead tx_free; + int tx_nfree; + struct ural_rx_desc sc_rx_desc; + + struct mtx sc_mtx; + + uint16_t sta[11]; + uint32_t rf_regs[4]; + uint8_t txpow[14]; + uint8_t sc_bssid[6]; + + struct { + uint8_t val; + uint8_t reg; + } __packed bbp_prom[16]; + + int led_mode; + int hw_radio; + int rx_ant; + int tx_ant; + int nb_ant; + + struct ural_rx_radiotap_header sc_rxtap; + int sc_rxtap_len; + + struct ural_tx_radiotap_header sc_txtap; + int sc_txtap_len; +}; + +#define RAL_LOCK(sc) mtx_lock(&(sc)->sc_mtx) +#define RAL_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) +#define RAL_LOCK_ASSERT(sc, t) mtx_assert(&(sc)->sc_mtx, t) diff --git a/sys/bus/u4b/wlan/if_urtw.c b/sys/bus/u4b/wlan/if_urtw.c new file mode 100644 index 0000000000..1259b2dee8 --- /dev/null +++ b/sys/bus/u4b/wlan/if_urtw.c @@ -0,0 +1,4446 @@ +/*- + * Copyright (c) 2008 Weongyo Jeong + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +__FBSDID("$FreeBSD$"); +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef INET +#include +#include +#include +#include +#include +#endif + +#include +#include +#include + +#include +#include +#include "usbdevs.h" + +#include +#include + +static SYSCTL_NODE(_hw_usb, OID_AUTO, urtw, CTLFLAG_RW, 0, "USB Realtek 8187L"); +#ifdef URTW_DEBUG +int urtw_debug = 0; +SYSCTL_INT(_hw_usb_urtw, OID_AUTO, debug, CTLFLAG_RW, &urtw_debug, 0, + "control debugging printfs"); +TUNABLE_INT("hw.usb.urtw.debug", &urtw_debug); +enum { + URTW_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ + URTW_DEBUG_RECV = 0x00000002, /* basic recv operation */ + URTW_DEBUG_RESET = 0x00000004, /* reset processing */ + URTW_DEBUG_TX_PROC = 0x00000008, /* tx ISR proc */ + URTW_DEBUG_RX_PROC = 0x00000010, /* rx ISR proc */ + URTW_DEBUG_STATE = 0x00000020, /* 802.11 state transitions */ + URTW_DEBUG_STAT = 0x00000040, /* statistic */ + URTW_DEBUG_INIT = 0x00000080, /* initialization of dev */ + URTW_DEBUG_TXSTATUS = 0x00000100, /* tx status */ + URTW_DEBUG_ANY = 0xffffffff +}; +#define DPRINTF(sc, m, fmt, ...) do { \ + if (sc->sc_debug & (m)) \ + printf(fmt, __VA_ARGS__); \ +} while (0) +#else +#define DPRINTF(sc, m, fmt, ...) do { \ + (void) sc; \ +} while (0) +#endif +static int urtw_preamble_mode = URTW_PREAMBLE_MODE_LONG; +SYSCTL_INT(_hw_usb_urtw, OID_AUTO, preamble_mode, CTLFLAG_RW, + &urtw_preamble_mode, 0, "set the preable mode (long or short)"); +TUNABLE_INT("hw.usb.urtw.preamble_mode", &urtw_preamble_mode); + +/* recognized device vendors/products */ +#define urtw_lookup(v, p) \ + ((const struct urtw_type *)usb_lookup(urtw_devs, v, p)) +#define URTW_DEV_B(v,p) \ + { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, URTW_REV_RTL8187B) } +#define URTW_DEV_L(v,p) \ + { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, URTW_REV_RTL8187L) } +#define URTW_REV_RTL8187B 0 +#define URTW_REV_RTL8187L 1 +static const STRUCT_USB_HOST_ID urtw_devs[] = { + URTW_DEV_B(NETGEAR, WG111V3), + URTW_DEV_B(REALTEK, RTL8187B_0), + URTW_DEV_B(REALTEK, RTL8187B_1), + URTW_DEV_B(REALTEK, RTL8187B_2), + URTW_DEV_B(SITECOMEU, WL168V4), + URTW_DEV_L(ASUS, P5B_WIFI), + URTW_DEV_L(BELKIN, F5D7050E), + URTW_DEV_L(LINKSYS4, WUSB54GCV2), + URTW_DEV_L(NETGEAR, WG111V2), + URTW_DEV_L(REALTEK, RTL8187), + URTW_DEV_L(SITECOMEU, WL168V1), + URTW_DEV_L(SURECOM, EP9001G2A), + { USB_VPI(USB_VENDOR_OVISLINK, 0x8187, URTW_REV_RTL8187L) }, + { USB_VPI(USB_VENDOR_DICKSMITH, 0x9401, URTW_REV_RTL8187L) }, + { USB_VPI(USB_VENDOR_HP, 0xca02, URTW_REV_RTL8187L) }, + { USB_VPI(USB_VENDOR_LOGITEC, 0x010c, URTW_REV_RTL8187L) }, + { USB_VPI(USB_VENDOR_NETGEAR, 0x6100, URTW_REV_RTL8187L) }, + { USB_VPI(USB_VENDOR_SPHAIRON, 0x0150, URTW_REV_RTL8187L) }, + { USB_VPI(USB_VENDOR_QCOM, 0x6232, URTW_REV_RTL8187L) }, +#undef URTW_DEV_L +#undef URTW_DEV_B +}; + +#define urtw_read8_m(sc, val, data) do { \ + error = urtw_read8_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_write8_m(sc, val, data) do { \ + error = urtw_write8_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_read16_m(sc, val, data) do { \ + error = urtw_read16_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_write16_m(sc, val, data) do { \ + error = urtw_write16_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_read32_m(sc, val, data) do { \ + error = urtw_read32_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_write32_m(sc, val, data) do { \ + error = urtw_write32_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_8187_write_phy_ofdm(sc, val, data) do { \ + error = urtw_8187_write_phy_ofdm_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_8187_write_phy_cck(sc, val, data) do { \ + error = urtw_8187_write_phy_cck_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_8225_write(sc, val, data) do { \ + error = urtw_8225_write_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) + +struct urtw_pair { + uint32_t reg; + uint32_t val; +}; + +static uint8_t urtw_8225_agc[] = { + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9d, 0x9c, 0x9b, + 0x9a, 0x99, 0x98, 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, + 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, 0x87, 0x86, 0x85, + 0x84, 0x83, 0x82, 0x81, 0x80, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, + 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x2f, + 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, + 0x23, 0x22, 0x21, 0x20, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, + 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, + 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, + 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 +}; + +static uint8_t urtw_8225z2_agc[] = { + 0x5e, 0x5e, 0x5e, 0x5e, 0x5d, 0x5b, 0x59, 0x57, 0x55, 0x53, 0x51, + 0x4f, 0x4d, 0x4b, 0x49, 0x47, 0x45, 0x43, 0x41, 0x3f, 0x3d, 0x3b, + 0x39, 0x37, 0x35, 0x33, 0x31, 0x2f, 0x2d, 0x2b, 0x29, 0x27, 0x25, + 0x23, 0x21, 0x1f, 0x1d, 0x1b, 0x19, 0x17, 0x15, 0x13, 0x11, 0x0f, + 0x0d, 0x0b, 0x09, 0x07, 0x05, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x2a, 0x2a, + 0x2a, 0x2b, 0x2b, 0x2b, 0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f, 0x30, 0x30, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31 +}; + +static uint32_t urtw_8225_channel[] = { + 0x0000, /* dummy channel 0 */ + 0x085c, /* 1 */ + 0x08dc, /* 2 */ + 0x095c, /* 3 */ + 0x09dc, /* 4 */ + 0x0a5c, /* 5 */ + 0x0adc, /* 6 */ + 0x0b5c, /* 7 */ + 0x0bdc, /* 8 */ + 0x0c5c, /* 9 */ + 0x0cdc, /* 10 */ + 0x0d5c, /* 11 */ + 0x0ddc, /* 12 */ + 0x0e5c, /* 13 */ + 0x0f72, /* 14 */ +}; + +static uint8_t urtw_8225_gain[] = { + 0x23, 0x88, 0x7c, 0xa5, /* -82dbm */ + 0x23, 0x88, 0x7c, 0xb5, /* -82dbm */ + 0x23, 0x88, 0x7c, 0xc5, /* -82dbm */ + 0x33, 0x80, 0x79, 0xc5, /* -78dbm */ + 0x43, 0x78, 0x76, 0xc5, /* -74dbm */ + 0x53, 0x60, 0x73, 0xc5, /* -70dbm */ + 0x63, 0x58, 0x70, 0xc5, /* -66dbm */ +}; + +static struct urtw_pair urtw_8225_rf_part1[] = { + { 0x00, 0x0067 }, { 0x01, 0x0fe0 }, { 0x02, 0x044d }, { 0x03, 0x0441 }, + { 0x04, 0x0486 }, { 0x05, 0x0bc0 }, { 0x06, 0x0ae6 }, { 0x07, 0x082a }, + { 0x08, 0x001f }, { 0x09, 0x0334 }, { 0x0a, 0x0fd4 }, { 0x0b, 0x0391 }, + { 0x0c, 0x0050 }, { 0x0d, 0x06db }, { 0x0e, 0x0029 }, { 0x0f, 0x0914 }, +}; + +static struct urtw_pair urtw_8225_rf_part2[] = { + { 0x00, 0x01 }, { 0x01, 0x02 }, { 0x02, 0x42 }, { 0x03, 0x00 }, + { 0x04, 0x00 }, { 0x05, 0x00 }, { 0x06, 0x40 }, { 0x07, 0x00 }, + { 0x08, 0x40 }, { 0x09, 0xfe }, { 0x0a, 0x09 }, { 0x0b, 0x80 }, + { 0x0c, 0x01 }, { 0x0e, 0xd3 }, { 0x0f, 0x38 }, { 0x10, 0x84 }, + { 0x11, 0x06 }, { 0x12, 0x20 }, { 0x13, 0x20 }, { 0x14, 0x00 }, + { 0x15, 0x40 }, { 0x16, 0x00 }, { 0x17, 0x40 }, { 0x18, 0xef }, + { 0x19, 0x19 }, { 0x1a, 0x20 }, { 0x1b, 0x76 }, { 0x1c, 0x04 }, + { 0x1e, 0x95 }, { 0x1f, 0x75 }, { 0x20, 0x1f }, { 0x21, 0x27 }, + { 0x22, 0x16 }, { 0x24, 0x46 }, { 0x25, 0x20 }, { 0x26, 0x90 }, + { 0x27, 0x88 } +}; + +static struct urtw_pair urtw_8225_rf_part3[] = { + { 0x00, 0x98 }, { 0x03, 0x20 }, { 0x04, 0x7e }, { 0x05, 0x12 }, + { 0x06, 0xfc }, { 0x07, 0x78 }, { 0x08, 0x2e }, { 0x10, 0x9b }, + { 0x11, 0x88 }, { 0x12, 0x47 }, { 0x13, 0xd0 }, { 0x19, 0x00 }, + { 0x1a, 0xa0 }, { 0x1b, 0x08 }, { 0x40, 0x86 }, { 0x41, 0x8d }, + { 0x42, 0x15 }, { 0x43, 0x18 }, { 0x44, 0x1f }, { 0x45, 0x1e }, + { 0x46, 0x1a }, { 0x47, 0x15 }, { 0x48, 0x10 }, { 0x49, 0x0a }, + { 0x4a, 0x05 }, { 0x4b, 0x02 }, { 0x4c, 0x05 } +}; + +static uint16_t urtw_8225_rxgain[] = { + 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0408, 0x0409, + 0x040a, 0x040b, 0x0502, 0x0503, 0x0504, 0x0505, 0x0540, 0x0541, + 0x0542, 0x0543, 0x0544, 0x0545, 0x0580, 0x0581, 0x0582, 0x0583, + 0x0584, 0x0585, 0x0588, 0x0589, 0x058a, 0x058b, 0x0643, 0x0644, + 0x0645, 0x0680, 0x0681, 0x0682, 0x0683, 0x0684, 0x0685, 0x0688, + 0x0689, 0x068a, 0x068b, 0x068c, 0x0742, 0x0743, 0x0744, 0x0745, + 0x0780, 0x0781, 0x0782, 0x0783, 0x0784, 0x0785, 0x0788, 0x0789, + 0x078a, 0x078b, 0x078c, 0x078d, 0x0790, 0x0791, 0x0792, 0x0793, + 0x0794, 0x0795, 0x0798, 0x0799, 0x079a, 0x079b, 0x079c, 0x079d, + 0x07a0, 0x07a1, 0x07a2, 0x07a3, 0x07a4, 0x07a5, 0x07a8, 0x07a9, + 0x07aa, 0x07ab, 0x07ac, 0x07ad, 0x07b0, 0x07b1, 0x07b2, 0x07b3, + 0x07b4, 0x07b5, 0x07b8, 0x07b9, 0x07ba, 0x07bb, 0x07bb +}; + +static uint8_t urtw_8225_threshold[] = { + 0x8d, 0x8d, 0x8d, 0x8d, 0x9d, 0xad, 0xbd, +}; + +static uint8_t urtw_8225_tx_gain_cck_ofdm[] = { + 0x02, 0x06, 0x0e, 0x1e, 0x3e, 0x7e +}; + +static uint8_t urtw_8225_txpwr_cck[] = { + 0x18, 0x17, 0x15, 0x11, 0x0c, 0x08, 0x04, 0x02, + 0x1b, 0x1a, 0x17, 0x13, 0x0e, 0x09, 0x04, 0x02, + 0x1f, 0x1e, 0x1a, 0x15, 0x10, 0x0a, 0x05, 0x02, + 0x22, 0x21, 0x1d, 0x18, 0x11, 0x0b, 0x06, 0x02, + 0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03, + 0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03 +}; + +static uint8_t urtw_8225_txpwr_cck_ch14[] = { + 0x18, 0x17, 0x15, 0x0c, 0x00, 0x00, 0x00, 0x00, + 0x1b, 0x1a, 0x17, 0x0e, 0x00, 0x00, 0x00, 0x00, + 0x1f, 0x1e, 0x1a, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x21, 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x26, 0x25, 0x21, 0x13, 0x00, 0x00, 0x00, 0x00, + 0x2b, 0x2a, 0x25, 0x15, 0x00, 0x00, 0x00, 0x00 +}; + +static uint8_t urtw_8225_txpwr_ofdm[]={ + 0x80, 0x90, 0xa2, 0xb5, 0xcb, 0xe4 +}; + +static uint8_t urtw_8225v2_gain_bg[]={ + 0x23, 0x15, 0xa5, /* -82-1dbm */ + 0x23, 0x15, 0xb5, /* -82-2dbm */ + 0x23, 0x15, 0xc5, /* -82-3dbm */ + 0x33, 0x15, 0xc5, /* -78dbm */ + 0x43, 0x15, 0xc5, /* -74dbm */ + 0x53, 0x15, 0xc5, /* -70dbm */ + 0x63, 0x15, 0xc5, /* -66dbm */ +}; + +static struct urtw_pair urtw_8225v2_rf_part1[] = { + { 0x00, 0x02bf }, { 0x01, 0x0ee0 }, { 0x02, 0x044d }, { 0x03, 0x0441 }, + { 0x04, 0x08c3 }, { 0x05, 0x0c72 }, { 0x06, 0x00e6 }, { 0x07, 0x082a }, + { 0x08, 0x003f }, { 0x09, 0x0335 }, { 0x0a, 0x09d4 }, { 0x0b, 0x07bb }, + { 0x0c, 0x0850 }, { 0x0d, 0x0cdf }, { 0x0e, 0x002b }, { 0x0f, 0x0114 } +}; + +static struct urtw_pair urtw_8225v2b_rf_part0[] = { + { 0x00, 0x00b7 }, { 0x01, 0x0ee0 }, { 0x02, 0x044d }, { 0x03, 0x0441 }, + { 0x04, 0x08c3 }, { 0x05, 0x0c72 }, { 0x06, 0x00e6 }, { 0x07, 0x082a }, + { 0x08, 0x003f }, { 0x09, 0x0335 }, { 0x0a, 0x09d4 }, { 0x0b, 0x07bb }, + { 0x0c, 0x0850 }, { 0x0d, 0x0cdf }, { 0x0e, 0x002b }, { 0x0f, 0x0114 } +}; + +static struct urtw_pair urtw_8225v2b_rf_part1[] = { + {0x0f0, 0x32}, {0x0f1, 0x32}, {0x0f2, 0x00}, + {0x0f3, 0x00}, {0x0f4, 0x32}, {0x0f5, 0x43}, + {0x0f6, 0x00}, {0x0f7, 0x00}, {0x0f8, 0x46}, + {0x0f9, 0xa4}, {0x0fa, 0x00}, {0x0fb, 0x00}, + {0x0fc, 0x96}, {0x0fd, 0xa4}, {0x0fe, 0x00}, + {0x0ff, 0x00}, {0x158, 0x4b}, {0x159, 0x00}, + {0x15a, 0x4b}, {0x15b, 0x00}, {0x160, 0x4b}, + {0x161, 0x09}, {0x162, 0x4b}, {0x163, 0x09}, + {0x1ce, 0x0f}, {0x1cf, 0x00}, {0x1e0, 0xff}, + {0x1e1, 0x0f}, {0x1e2, 0x00}, {0x1f0, 0x4e}, + {0x1f1, 0x01}, {0x1f2, 0x02}, {0x1f3, 0x03}, + {0x1f4, 0x04}, {0x1f5, 0x05}, {0x1f6, 0x06}, + {0x1f7, 0x07}, {0x1f8, 0x08}, {0x24e, 0x00}, + {0x20c, 0x04}, {0x221, 0x61}, {0x222, 0x68}, + {0x223, 0x6f}, {0x224, 0x76}, {0x225, 0x7d}, + {0x226, 0x84}, {0x227, 0x8d}, {0x24d, 0x08}, + {0x250, 0x05}, {0x251, 0xf5}, {0x252, 0x04}, + {0x253, 0xa0}, {0x254, 0x1f}, {0x255, 0x23}, + {0x256, 0x45}, {0x257, 0x67}, {0x258, 0x08}, + {0x259, 0x08}, {0x25a, 0x08}, {0x25b, 0x08}, + {0x260, 0x08}, {0x261, 0x08}, {0x262, 0x08}, + {0x263, 0x08}, {0x264, 0xcf}, {0x272, 0x56}, + {0x273, 0x9a}, {0x034, 0xf0}, {0x035, 0x0f}, + {0x05b, 0x40}, {0x084, 0x88}, {0x085, 0x24}, + {0x088, 0x54}, {0x08b, 0xb8}, {0x08c, 0x07}, + {0x08d, 0x00}, {0x094, 0x1b}, {0x095, 0x12}, + {0x096, 0x00}, {0x097, 0x06}, {0x09d, 0x1a}, + {0x09f, 0x10}, {0x0b4, 0x22}, {0x0be, 0x80}, + {0x0db, 0x00}, {0x0ee, 0x00}, {0x091, 0x03}, + {0x24c, 0x00}, {0x39f, 0x00}, {0x08c, 0x01}, + {0x08d, 0x10}, {0x08e, 0x08}, {0x08f, 0x00} +}; + +static struct urtw_pair urtw_8225v2_rf_part2[] = { + { 0x00, 0x01 }, { 0x01, 0x02 }, { 0x02, 0x42 }, { 0x03, 0x00 }, + { 0x04, 0x00 }, { 0x05, 0x00 }, { 0x06, 0x40 }, { 0x07, 0x00 }, + { 0x08, 0x40 }, { 0x09, 0xfe }, { 0x0a, 0x08 }, { 0x0b, 0x80 }, + { 0x0c, 0x01 }, { 0x0d, 0x43 }, { 0x0e, 0xd3 }, { 0x0f, 0x38 }, + { 0x10, 0x84 }, { 0x11, 0x07 }, { 0x12, 0x20 }, { 0x13, 0x20 }, + { 0x14, 0x00 }, { 0x15, 0x40 }, { 0x16, 0x00 }, { 0x17, 0x40 }, + { 0x18, 0xef }, { 0x19, 0x19 }, { 0x1a, 0x20 }, { 0x1b, 0x15 }, + { 0x1c, 0x04 }, { 0x1d, 0xc5 }, { 0x1e, 0x95 }, { 0x1f, 0x75 }, + { 0x20, 0x1f }, { 0x21, 0x17 }, { 0x22, 0x16 }, { 0x23, 0x80 }, + { 0x24, 0x46 }, { 0x25, 0x00 }, { 0x26, 0x90 }, { 0x27, 0x88 } +}; + +static struct urtw_pair urtw_8225v2b_rf_part2[] = { + { 0x00, 0x10 }, { 0x01, 0x0d }, { 0x02, 0x01 }, { 0x03, 0x00 }, + { 0x04, 0x14 }, { 0x05, 0xfb }, { 0x06, 0xfb }, { 0x07, 0x60 }, + { 0x08, 0x00 }, { 0x09, 0x60 }, { 0x0a, 0x00 }, { 0x0b, 0x00 }, + { 0x0c, 0x00 }, { 0x0d, 0x5c }, { 0x0e, 0x00 }, { 0x0f, 0x00 }, + { 0x10, 0x40 }, { 0x11, 0x00 }, { 0x12, 0x40 }, { 0x13, 0x00 }, + { 0x14, 0x00 }, { 0x15, 0x00 }, { 0x16, 0xa8 }, { 0x17, 0x26 }, + { 0x18, 0x32 }, { 0x19, 0x33 }, { 0x1a, 0x07 }, { 0x1b, 0xa5 }, + { 0x1c, 0x6f }, { 0x1d, 0x55 }, { 0x1e, 0xc8 }, { 0x1f, 0xb3 }, + { 0x20, 0x0a }, { 0x21, 0xe1 }, { 0x22, 0x2C }, { 0x23, 0x8a }, + { 0x24, 0x86 }, { 0x25, 0x83 }, { 0x26, 0x34 }, { 0x27, 0x0f }, + { 0x28, 0x4f }, { 0x29, 0x24 }, { 0x2a, 0x6f }, { 0x2b, 0xc2 }, + { 0x2c, 0x6b }, { 0x2d, 0x40 }, { 0x2e, 0x80 }, { 0x2f, 0x00 }, + { 0x30, 0xc0 }, { 0x31, 0xc1 }, { 0x32, 0x58 }, { 0x33, 0xf1 }, + { 0x34, 0x00 }, { 0x35, 0xe4 }, { 0x36, 0x90 }, { 0x37, 0x3e }, + { 0x38, 0x6d }, { 0x39, 0x3c }, { 0x3a, 0xfb }, { 0x3b, 0x07 } +}; + +static struct urtw_pair urtw_8225v2_rf_part3[] = { + { 0x00, 0x98 }, { 0x03, 0x20 }, { 0x04, 0x7e }, { 0x05, 0x12 }, + { 0x06, 0xfc }, { 0x07, 0x78 }, { 0x08, 0x2e }, { 0x09, 0x11 }, + { 0x0a, 0x17 }, { 0x0b, 0x11 }, { 0x10, 0x9b }, { 0x11, 0x88 }, + { 0x12, 0x47 }, { 0x13, 0xd0 }, { 0x19, 0x00 }, { 0x1a, 0xa0 }, + { 0x1b, 0x08 }, { 0x1d, 0x00 }, { 0x40, 0x86 }, { 0x41, 0x9d }, + { 0x42, 0x15 }, { 0x43, 0x18 }, { 0x44, 0x36 }, { 0x45, 0x35 }, + { 0x46, 0x2e }, { 0x47, 0x25 }, { 0x48, 0x1c }, { 0x49, 0x12 }, + { 0x4a, 0x09 }, { 0x4b, 0x04 }, { 0x4c, 0x05 } +}; + +static uint16_t urtw_8225v2_rxgain[] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0008, 0x0009, + 0x000a, 0x000b, 0x0102, 0x0103, 0x0104, 0x0105, 0x0140, 0x0141, + 0x0142, 0x0143, 0x0144, 0x0145, 0x0180, 0x0181, 0x0182, 0x0183, + 0x0184, 0x0185, 0x0188, 0x0189, 0x018a, 0x018b, 0x0243, 0x0244, + 0x0245, 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0288, + 0x0289, 0x028a, 0x028b, 0x028c, 0x0342, 0x0343, 0x0344, 0x0345, + 0x0380, 0x0381, 0x0382, 0x0383, 0x0384, 0x0385, 0x0388, 0x0389, + 0x038a, 0x038b, 0x038c, 0x038d, 0x0390, 0x0391, 0x0392, 0x0393, + 0x0394, 0x0395, 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, + 0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a8, 0x03a9, + 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03b0, 0x03b1, 0x03b2, 0x03b3, + 0x03b4, 0x03b5, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bb +}; + +static uint16_t urtw_8225v2b_rxgain[] = { + 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0408, 0x0409, + 0x040a, 0x040b, 0x0502, 0x0503, 0x0504, 0x0505, 0x0540, 0x0541, + 0x0542, 0x0543, 0x0544, 0x0545, 0x0580, 0x0581, 0x0582, 0x0583, + 0x0584, 0x0585, 0x0588, 0x0589, 0x058a, 0x058b, 0x0643, 0x0644, + 0x0645, 0x0680, 0x0681, 0x0682, 0x0683, 0x0684, 0x0685, 0x0688, + 0x0689, 0x068a, 0x068b, 0x068c, 0x0742, 0x0743, 0x0744, 0x0745, + 0x0780, 0x0781, 0x0782, 0x0783, 0x0784, 0x0785, 0x0788, 0x0789, + 0x078a, 0x078b, 0x078c, 0x078d, 0x0790, 0x0791, 0x0792, 0x0793, + 0x0794, 0x0795, 0x0798, 0x0799, 0x079a, 0x079b, 0x079c, 0x079d, + 0x07a0, 0x07a1, 0x07a2, 0x07a3, 0x07a4, 0x07a5, 0x07a8, 0x07a9, + 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03b0, 0x03b1, 0x03b2, 0x03b3, + 0x03b4, 0x03b5, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bb +}; + +static uint8_t urtw_8225v2_tx_gain_cck_ofdm[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, +}; + +static uint8_t urtw_8225v2_txpwr_cck[] = { + 0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04 +}; + +static uint8_t urtw_8225v2_txpwr_cck_ch14[] = { + 0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00 +}; + +static uint8_t urtw_8225v2b_txpwr_cck[] = { + 0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04, + 0x30, 0x2f, 0x29, 0x21, 0x19, 0x10, 0x08, 0x03, + 0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03, + 0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03 +}; + +static uint8_t urtw_8225v2b_txpwr_cck_ch14[] = { + 0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x2f, 0x29, 0x15, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x2f, 0x29, 0x15, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x2f, 0x29, 0x15, 0x00, 0x00, 0x00, 0x00 +}; + +static struct urtw_pair urtw_ratetable[] = { + { 2, 0 }, { 4, 1 }, { 11, 2 }, { 12, 4 }, { 18, 5 }, + { 22, 3 }, { 24, 6 }, { 36, 7 }, { 48, 8 }, { 72, 9 }, + { 96, 10 }, { 108, 11 } +}; + +static const uint8_t urtw_8187b_reg_table[][3] = { + { 0xf0, 0x32, 0 }, { 0xf1, 0x32, 0 }, { 0xf2, 0x00, 0 }, + { 0xf3, 0x00, 0 }, { 0xf4, 0x32, 0 }, { 0xf5, 0x43, 0 }, + { 0xf6, 0x00, 0 }, { 0xf7, 0x00, 0 }, { 0xf8, 0x46, 0 }, + { 0xf9, 0xa4, 0 }, { 0xfa, 0x00, 0 }, { 0xfb, 0x00, 0 }, + { 0xfc, 0x96, 0 }, { 0xfd, 0xa4, 0 }, { 0xfe, 0x00, 0 }, + { 0xff, 0x00, 0 }, { 0x58, 0x4b, 1 }, { 0x59, 0x00, 1 }, + { 0x5a, 0x4b, 1 }, { 0x5b, 0x00, 1 }, { 0x60, 0x4b, 1 }, + { 0x61, 0x09, 1 }, { 0x62, 0x4b, 1 }, { 0x63, 0x09, 1 }, + { 0xce, 0x0f, 1 }, { 0xcf, 0x00, 1 }, { 0xe0, 0xff, 1 }, + { 0xe1, 0x0f, 1 }, { 0xe2, 0x00, 1 }, { 0xf0, 0x4e, 1 }, + { 0xf1, 0x01, 1 }, { 0xf2, 0x02, 1 }, { 0xf3, 0x03, 1 }, + { 0xf4, 0x04, 1 }, { 0xf5, 0x05, 1 }, { 0xf6, 0x06, 1 }, + { 0xf7, 0x07, 1 }, { 0xf8, 0x08, 1 }, { 0x4e, 0x00, 2 }, + { 0x0c, 0x04, 2 }, { 0x21, 0x61, 2 }, { 0x22, 0x68, 2 }, + { 0x23, 0x6f, 2 }, { 0x24, 0x76, 2 }, { 0x25, 0x7d, 2 }, + { 0x26, 0x84, 2 }, { 0x27, 0x8d, 2 }, { 0x4d, 0x08, 2 }, + { 0x50, 0x05, 2 }, { 0x51, 0xf5, 2 }, { 0x52, 0x04, 2 }, + { 0x53, 0xa0, 2 }, { 0x54, 0x1f, 2 }, { 0x55, 0x23, 2 }, + { 0x56, 0x45, 2 }, { 0x57, 0x67, 2 }, { 0x58, 0x08, 2 }, + { 0x59, 0x08, 2 }, { 0x5a, 0x08, 2 }, { 0x5b, 0x08, 2 }, + { 0x60, 0x08, 2 }, { 0x61, 0x08, 2 }, { 0x62, 0x08, 2 }, + { 0x63, 0x08, 2 }, { 0x64, 0xcf, 2 }, { 0x72, 0x56, 2 }, + { 0x73, 0x9a, 2 }, { 0x34, 0xf0, 0 }, { 0x35, 0x0f, 0 }, + { 0x5b, 0x40, 0 }, { 0x84, 0x88, 0 }, { 0x85, 0x24, 0 }, + { 0x88, 0x54, 0 }, { 0x8b, 0xb8, 0 }, { 0x8c, 0x07, 0 }, + { 0x8d, 0x00, 0 }, { 0x94, 0x1b, 0 }, { 0x95, 0x12, 0 }, + { 0x96, 0x00, 0 }, { 0x97, 0x06, 0 }, { 0x9d, 0x1a, 0 }, + { 0x9f, 0x10, 0 }, { 0xb4, 0x22, 0 }, { 0xbe, 0x80, 0 }, + { 0xdb, 0x00, 0 }, { 0xee, 0x00, 0 }, { 0x91, 0x03, 0 }, + { 0x4c, 0x00, 2 }, { 0x9f, 0x00, 3 }, { 0x8c, 0x01, 0 }, + { 0x8d, 0x10, 0 }, { 0x8e, 0x08, 0 }, { 0x8f, 0x00, 0 } +}; + +static usb_callback_t urtw_bulk_rx_callback; +static usb_callback_t urtw_bulk_tx_callback; +static usb_callback_t urtw_bulk_tx_status_callback; + +static const struct usb_config urtw_8187b_usbconfig[URTW_8187B_N_XFERS] = { + [URTW_8187B_BULK_RX] = { + .type = UE_BULK, + .endpoint = 0x83, + .direction = UE_DIR_IN, + .bufsize = MCLBYTES, + .flags = { + .ext_buffer = 1, + .pipe_bof = 1, + .short_xfer_ok = 1 + }, + .callback = urtw_bulk_rx_callback + }, + [URTW_8187B_BULK_TX_STATUS] = { + .type = UE_BULK, + .endpoint = 0x89, + .direction = UE_DIR_IN, + .bufsize = MCLBYTES, + .flags = { + .ext_buffer = 1, + .pipe_bof = 1, + .short_xfer_ok = 1 + }, + .callback = urtw_bulk_tx_status_callback + }, + [URTW_8187B_BULK_TX_BE] = { + .type = UE_BULK, + .endpoint = URTW_8187B_TXPIPE_BE, + .direction = UE_DIR_OUT, + .bufsize = URTW_TX_MAXSIZE, + .flags = { + .ext_buffer = 1, + .force_short_xfer = 1, + .pipe_bof = 1, + }, + .callback = urtw_bulk_tx_callback, + .timeout = URTW_DATA_TIMEOUT + }, + [URTW_8187B_BULK_TX_BK] = { + .type = UE_BULK, + .endpoint = URTW_8187B_TXPIPE_BK, + .direction = UE_DIR_OUT, + .bufsize = URTW_TX_MAXSIZE, + .flags = { + .ext_buffer = 1, + .force_short_xfer = 1, + .pipe_bof = 1, + }, + .callback = urtw_bulk_tx_callback, + .timeout = URTW_DATA_TIMEOUT + }, + [URTW_8187B_BULK_TX_VI] = { + .type = UE_BULK, + .endpoint = URTW_8187B_TXPIPE_VI, + .direction = UE_DIR_OUT, + .bufsize = URTW_TX_MAXSIZE, + .flags = { + .ext_buffer = 1, + .force_short_xfer = 1, + .pipe_bof = 1, + }, + .callback = urtw_bulk_tx_callback, + .timeout = URTW_DATA_TIMEOUT + }, + [URTW_8187B_BULK_TX_VO] = { + .type = UE_BULK, + .endpoint = URTW_8187B_TXPIPE_VO, + .direction = UE_DIR_OUT, + .bufsize = URTW_TX_MAXSIZE, + .flags = { + .ext_buffer = 1, + .force_short_xfer = 1, + .pipe_bof = 1, + }, + .callback = urtw_bulk_tx_callback, + .timeout = URTW_DATA_TIMEOUT + }, + [URTW_8187B_BULK_TX_EP12] = { + .type = UE_BULK, + .endpoint = 0xc, + .direction = UE_DIR_OUT, + .bufsize = URTW_TX_MAXSIZE, + .flags = { + .ext_buffer = 1, + .force_short_xfer = 1, + .pipe_bof = 1, + }, + .callback = urtw_bulk_tx_callback, + .timeout = URTW_DATA_TIMEOUT + } +}; + +static const struct usb_config urtw_8187l_usbconfig[URTW_8187L_N_XFERS] = { + [URTW_8187L_BULK_RX] = { + .type = UE_BULK, + .endpoint = 0x81, + .direction = UE_DIR_IN, + .bufsize = MCLBYTES, + .flags = { + .ext_buffer = 1, + .pipe_bof = 1, + .short_xfer_ok = 1 + }, + .callback = urtw_bulk_rx_callback + }, + [URTW_8187L_BULK_TX_LOW] = { + .type = UE_BULK, + .endpoint = 0x2, + .direction = UE_DIR_OUT, + .bufsize = URTW_TX_MAXSIZE, + .flags = { + .ext_buffer = 1, + .force_short_xfer = 1, + .pipe_bof = 1, + }, + .callback = urtw_bulk_tx_callback, + .timeout = URTW_DATA_TIMEOUT + }, + [URTW_8187L_BULK_TX_NORMAL] = { + .type = UE_BULK, + .endpoint = 0x3, + .direction = UE_DIR_OUT, + .bufsize = URTW_TX_MAXSIZE, + .flags = { + .ext_buffer = 1, + .force_short_xfer = 1, + .pipe_bof = 1, + }, + .callback = urtw_bulk_tx_callback, + .timeout = URTW_DATA_TIMEOUT + }, +}; + +static struct ieee80211vap *urtw_vap_create(struct ieee80211com *, + const char [IFNAMSIZ], int, enum ieee80211_opmode, + int, const uint8_t [IEEE80211_ADDR_LEN], + const uint8_t [IEEE80211_ADDR_LEN]); +static void urtw_vap_delete(struct ieee80211vap *); +static void urtw_init(void *); +static void urtw_stop(struct ifnet *, int); +static void urtw_stop_locked(struct ifnet *, int); +static int urtw_ioctl(struct ifnet *, u_long, caddr_t); +static void urtw_start(struct ifnet *); +static int urtw_alloc_rx_data_list(struct urtw_softc *); +static int urtw_alloc_tx_data_list(struct urtw_softc *); +static int urtw_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static void urtw_scan_start(struct ieee80211com *); +static void urtw_scan_end(struct ieee80211com *); +static void urtw_set_channel(struct ieee80211com *); +static void urtw_update_mcast(struct ifnet *); +static int urtw_tx_start(struct urtw_softc *, + struct ieee80211_node *, struct mbuf *, + struct urtw_data *, int); +static int urtw_newstate(struct ieee80211vap *, + enum ieee80211_state, int); +static void urtw_led_ch(void *); +static void urtw_ledtask(void *, int); +static void urtw_watchdog(void *); +static void urtw_set_multi(void *); +static int urtw_isbmode(uint16_t); +static uint16_t urtw_rate2rtl(int); +static uint16_t urtw_rtl2rate(int); +static usb_error_t urtw_set_rate(struct urtw_softc *); +static usb_error_t urtw_update_msr(struct urtw_softc *); +static usb_error_t urtw_read8_c(struct urtw_softc *, int, uint8_t *); +static usb_error_t urtw_read16_c(struct urtw_softc *, int, uint16_t *); +static usb_error_t urtw_read32_c(struct urtw_softc *, int, uint32_t *); +static usb_error_t urtw_write8_c(struct urtw_softc *, int, uint8_t); +static usb_error_t urtw_write16_c(struct urtw_softc *, int, uint16_t); +static usb_error_t urtw_write32_c(struct urtw_softc *, int, uint32_t); +static usb_error_t urtw_eprom_cs(struct urtw_softc *, int); +static usb_error_t urtw_eprom_ck(struct urtw_softc *); +static usb_error_t urtw_eprom_sendbits(struct urtw_softc *, int16_t *, + int); +static usb_error_t urtw_eprom_read32(struct urtw_softc *, uint32_t, + uint32_t *); +static usb_error_t urtw_eprom_readbit(struct urtw_softc *, int16_t *); +static usb_error_t urtw_eprom_writebit(struct urtw_softc *, int16_t); +static usb_error_t urtw_get_macaddr(struct urtw_softc *); +static usb_error_t urtw_get_txpwr(struct urtw_softc *); +static usb_error_t urtw_get_rfchip(struct urtw_softc *); +static usb_error_t urtw_led_init(struct urtw_softc *); +static usb_error_t urtw_8185_rf_pins_enable(struct urtw_softc *); +static usb_error_t urtw_8185_tx_antenna(struct urtw_softc *, uint8_t); +static usb_error_t urtw_8187_write_phy(struct urtw_softc *, uint8_t, + uint32_t); +static usb_error_t urtw_8187_write_phy_ofdm_c(struct urtw_softc *, + uint8_t, uint32_t); +static usb_error_t urtw_8187_write_phy_cck_c(struct urtw_softc *, uint8_t, + uint32_t); +static usb_error_t urtw_8225_setgain(struct urtw_softc *, int16_t); +static usb_error_t urtw_8225_usb_init(struct urtw_softc *); +static usb_error_t urtw_8225_write_c(struct urtw_softc *, uint8_t, + uint16_t); +static usb_error_t urtw_8225_write_s16(struct urtw_softc *, uint8_t, int, + uint16_t *); +static usb_error_t urtw_8225_read(struct urtw_softc *, uint8_t, + uint32_t *); +static usb_error_t urtw_8225_rf_init(struct urtw_softc *); +static usb_error_t urtw_8225_rf_set_chan(struct urtw_softc *, int); +static usb_error_t urtw_8225_rf_set_sens(struct urtw_softc *, int); +static usb_error_t urtw_8225_set_txpwrlvl(struct urtw_softc *, int); +static usb_error_t urtw_8225_rf_stop(struct urtw_softc *); +static usb_error_t urtw_8225v2_rf_init(struct urtw_softc *); +static usb_error_t urtw_8225v2_rf_set_chan(struct urtw_softc *, int); +static usb_error_t urtw_8225v2_set_txpwrlvl(struct urtw_softc *, int); +static usb_error_t urtw_8225v2_setgain(struct urtw_softc *, int16_t); +static usb_error_t urtw_8225_isv2(struct urtw_softc *, int *); +static usb_error_t urtw_8225v2b_rf_init(struct urtw_softc *); +static usb_error_t urtw_8225v2b_rf_set_chan(struct urtw_softc *, int); +static usb_error_t urtw_read8e(struct urtw_softc *, int, uint8_t *); +static usb_error_t urtw_write8e(struct urtw_softc *, int, uint8_t); +static usb_error_t urtw_8180_set_anaparam(struct urtw_softc *, uint32_t); +static usb_error_t urtw_8185_set_anaparam2(struct urtw_softc *, uint32_t); +static usb_error_t urtw_intr_enable(struct urtw_softc *); +static usb_error_t urtw_intr_disable(struct urtw_softc *); +static usb_error_t urtw_reset(struct urtw_softc *); +static usb_error_t urtw_led_on(struct urtw_softc *, int); +static usb_error_t urtw_led_ctl(struct urtw_softc *, int); +static usb_error_t urtw_led_blink(struct urtw_softc *); +static usb_error_t urtw_led_mode0(struct urtw_softc *, int); +static usb_error_t urtw_led_mode1(struct urtw_softc *, int); +static usb_error_t urtw_led_mode2(struct urtw_softc *, int); +static usb_error_t urtw_led_mode3(struct urtw_softc *, int); +static usb_error_t urtw_rx_setconf(struct urtw_softc *); +static usb_error_t urtw_rx_enable(struct urtw_softc *); +static usb_error_t urtw_tx_enable(struct urtw_softc *sc); +static void urtw_free_tx_data_list(struct urtw_softc *); +static void urtw_free_rx_data_list(struct urtw_softc *); +static void urtw_free_data_list(struct urtw_softc *, + struct urtw_data data[], int, int); +static usb_error_t urtw_adapter_start(struct urtw_softc *); +static usb_error_t urtw_adapter_start_b(struct urtw_softc *); +static usb_error_t urtw_set_mode(struct urtw_softc *, uint32_t); +static usb_error_t urtw_8187b_cmd_reset(struct urtw_softc *); +static usb_error_t urtw_do_request(struct urtw_softc *, + struct usb_device_request *, void *); +static usb_error_t urtw_8225v2b_set_txpwrlvl(struct urtw_softc *, int); +static usb_error_t urtw_led_off(struct urtw_softc *, int); +static void urtw_abort_xfers(struct urtw_softc *); +static struct urtw_data * + urtw_getbuf(struct urtw_softc *sc); +static int urtw_compute_txtime(uint16_t, uint16_t, uint8_t, + uint8_t); +static void urtw_updateslot(struct ifnet *); +static void urtw_updateslottask(void *, int); +static void urtw_sysctl_node(struct urtw_softc *); + +static int +urtw_match(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != URTW_CONFIG_INDEX) + return (ENXIO); + if (uaa->info.bIfaceIndex != URTW_IFACE_INDEX) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(urtw_devs, sizeof(urtw_devs), uaa)); +} + +static int +urtw_attach(device_t dev) +{ + const struct usb_config *setup_start; + int ret = ENXIO; + struct urtw_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ieee80211com *ic; + struct ifnet *ifp; + uint8_t bands, iface_index = URTW_IFACE_INDEX; /* XXX */ + uint16_t n_setup; + uint32_t data; + usb_error_t error; + + device_set_usb_desc(dev); + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + if (USB_GET_DRIVER_INFO(uaa) == URTW_REV_RTL8187B) + sc->sc_flags |= URTW_RTL8187B; +#ifdef URTW_DEBUG + sc->sc_debug = urtw_debug; +#endif + + mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK, + MTX_DEF); + usb_callout_init_mtx(&sc->sc_led_ch, &sc->sc_mtx, 0); + TASK_INIT(&sc->sc_led_task, 0, urtw_ledtask, sc); + TASK_INIT(&sc->sc_updateslot_task, 0, urtw_updateslottask, sc); + callout_init(&sc->sc_watchdog_ch, 0); + + if (sc->sc_flags & URTW_RTL8187B) { + setup_start = urtw_8187b_usbconfig; + n_setup = URTW_8187B_N_XFERS; + } else { + setup_start = urtw_8187l_usbconfig; + n_setup = URTW_8187L_N_XFERS; + } + + error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, + setup_start, n_setup, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "could not allocate USB transfers, " + "err=%s\n", usbd_errstr(error)); + ret = ENXIO; + goto fail0; + } + + URTW_LOCK(sc); + + urtw_read32_m(sc, URTW_RX, &data); + sc->sc_epromtype = (data & URTW_RX_9356SEL) ? URTW_EEPROM_93C56 : + URTW_EEPROM_93C46; + + error = urtw_get_rfchip(sc); + if (error != 0) + goto fail; + error = urtw_get_macaddr(sc); + if (error != 0) + goto fail; + error = urtw_get_txpwr(sc); + if (error != 0) + goto fail; + error = urtw_led_init(sc); + if (error != 0) + goto fail; + + URTW_UNLOCK(sc); + + sc->sc_rts_retry = URTW_DEFAULT_RTS_RETRY; + sc->sc_tx_retry = URTW_DEFAULT_TX_RETRY; + sc->sc_currate = 3; + sc->sc_preamble_mode = urtw_preamble_mode; + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(sc->sc_dev, "can not allocate ifnet\n"); + ret = ENOMEM; + goto fail1; + } + + ifp->if_softc = sc; + if_initname(ifp, "urtw", device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_init = urtw_init; + ifp->if_ioctl = urtw_ioctl; + ifp->if_start = urtw_start; + /* XXX URTW_TX_DATA_LIST_COUNT */ + IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); + ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; + IFQ_SET_READY(&ifp->if_snd); + + ic = ifp->if_l2com; + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */ + + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA | /* station mode */ + IEEE80211_C_MONITOR | /* monitor mode supported */ + IEEE80211_C_TXPMGT | /* tx power management */ + IEEE80211_C_SHPREAMBLE | /* short preamble supported */ + IEEE80211_C_SHSLOT | /* short slot time supported */ + IEEE80211_C_BGSCAN | /* capable of bg scanning */ + IEEE80211_C_WPA; /* 802.11i */ + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + ieee80211_init_channels(ic, NULL, &bands); + + ieee80211_ifattach(ic, sc->sc_bssid); + ic->ic_raw_xmit = urtw_raw_xmit; + ic->ic_scan_start = urtw_scan_start; + ic->ic_scan_end = urtw_scan_end; + ic->ic_set_channel = urtw_set_channel; + ic->ic_updateslot = urtw_updateslot; + ic->ic_vap_create = urtw_vap_create; + ic->ic_vap_delete = urtw_vap_delete; + ic->ic_update_mcast = urtw_update_mcast; + + ieee80211_radiotap_attach(ic, + &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), + URTW_TX_RADIOTAP_PRESENT, + &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), + URTW_RX_RADIOTAP_PRESENT); + + urtw_sysctl_node(sc); + + if (bootverbose) + ieee80211_announce(ic); + return (0); + +fail: URTW_UNLOCK(sc); +fail1: usbd_transfer_unsetup(sc->sc_xfer, (sc->sc_flags & URTW_RTL8187B) ? + URTW_8187B_N_XFERS : URTW_8187L_N_XFERS); +fail0: + return (ret); +} + +static int +urtw_detach(device_t dev) +{ + struct urtw_softc *sc = device_get_softc(dev); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + if (!device_is_attached(dev)) + return (0); + + urtw_stop(ifp, 1); + ieee80211_draintask(ic, &sc->sc_updateslot_task); + ieee80211_draintask(ic, &sc->sc_led_task); + + usb_callout_drain(&sc->sc_led_ch); + callout_drain(&sc->sc_watchdog_ch); + + usbd_transfer_unsetup(sc->sc_xfer, (sc->sc_flags & URTW_RTL8187B) ? + URTW_8187B_N_XFERS : URTW_8187L_N_XFERS); + ieee80211_ifdetach(ic); + + urtw_free_tx_data_list(sc); + urtw_free_rx_data_list(sc); + + if_free(ifp); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +urtw_free_tx_data_list(struct urtw_softc *sc) +{ + + urtw_free_data_list(sc, sc->sc_tx, URTW_TX_DATA_LIST_COUNT, 0); +} + +static void +urtw_free_rx_data_list(struct urtw_softc *sc) +{ + + urtw_free_data_list(sc, sc->sc_rx, URTW_RX_DATA_LIST_COUNT, 1); +} + +static void +urtw_free_data_list(struct urtw_softc *sc, struct urtw_data data[], int ndata, + int fillmbuf) +{ + int i; + + for (i = 0; i < ndata; i++) { + struct urtw_data *dp = &data[i]; + + if (fillmbuf == 1) { + if (dp->m != NULL) { + m_freem(dp->m); + dp->m = NULL; + dp->buf = NULL; + } + } else { + if (dp->buf != NULL) { + free(dp->buf, M_USBDEV); + dp->buf = NULL; + } + } + if (dp->ni != NULL) { + ieee80211_free_node(dp->ni); + dp->ni = NULL; + } + } +} + +static struct ieee80211vap * +urtw_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, + enum ieee80211_opmode opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct urtw_vap *uvp; + struct ieee80211vap *vap; + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return (NULL); + uvp = (struct urtw_vap *) malloc(sizeof(struct urtw_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (uvp == NULL) + return (NULL); + vap = &uvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + uvp->newstate = vap->iv_newstate; + vap->iv_newstate = urtw_newstate; + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, + ieee80211_media_status); + ic->ic_opmode = opmode; + return (vap); +} + +static void +urtw_vap_delete(struct ieee80211vap *vap) +{ + struct urtw_vap *uvp = URTW_VAP(vap); + + ieee80211_vap_detach(vap); + free(uvp, M_80211_VAP); +} + +static void +urtw_init_locked(void *arg) +{ + int ret; + struct urtw_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + usb_error_t error; + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + urtw_stop_locked(ifp, 0); + + error = (sc->sc_flags & URTW_RTL8187B) ? urtw_adapter_start_b(sc) : + urtw_adapter_start(sc); + if (error != 0) + goto fail; + + /* reset softc variables */ + sc->sc_txtimer = 0; + + if (!(sc->sc_flags & URTW_INIT_ONCE)) { + ret = urtw_alloc_rx_data_list(sc); + if (ret != 0) + goto fail; + ret = urtw_alloc_tx_data_list(sc); + if (ret != 0) + goto fail; + sc->sc_flags |= URTW_INIT_ONCE; + } + + error = urtw_rx_enable(sc); + if (error != 0) + goto fail; + error = urtw_tx_enable(sc); + if (error != 0) + goto fail; + + if (sc->sc_flags & URTW_RTL8187B) + usbd_transfer_start(sc->sc_xfer[URTW_8187B_BULK_TX_STATUS]); + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + callout_reset(&sc->sc_watchdog_ch, hz, urtw_watchdog, sc); +fail: + return; +} + +static void +urtw_init(void *arg) +{ + struct urtw_softc *sc = arg; + + URTW_LOCK(sc); + urtw_init_locked(arg); + URTW_UNLOCK(sc); +} + +static usb_error_t +urtw_adapter_start_b(struct urtw_softc *sc) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + uint8_t data8; + usb_error_t error; + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + + urtw_read8_m(sc, URTW_CONFIG3, &data8); + urtw_write8_m(sc, URTW_CONFIG3, + data8 | URTW_CONFIG3_ANAPARAM_WRITE | URTW_CONFIG3_GNT_SELECT); + urtw_write32_m(sc, URTW_ANAPARAM2, URTW_8187B_8225_ANAPARAM2_ON); + urtw_write32_m(sc, URTW_ANAPARAM, URTW_8187B_8225_ANAPARAM_ON); + urtw_write8_m(sc, URTW_ANAPARAM3, URTW_8187B_8225_ANAPARAM3_ON); + + urtw_write8_m(sc, 0x61, 0x10); + urtw_read8_m(sc, 0x62, &data8); + urtw_write8_m(sc, 0x62, data8 & ~(1 << 5)); + urtw_write8_m(sc, 0x62, data8 | (1 << 5)); + + urtw_read8_m(sc, URTW_CONFIG3, &data8); + data8 &= ~URTW_CONFIG3_ANAPARAM_WRITE; + urtw_write8_m(sc, URTW_CONFIG3, data8); + + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; + + error = urtw_8187b_cmd_reset(sc); + if (error) + goto fail; + + error = sc->sc_rf_init(sc); + if (error != 0) + goto fail; + urtw_write8_m(sc, URTW_CMD, URTW_CMD_RX_ENABLE | URTW_CMD_TX_ENABLE); + + /* fix RTL8187B RX stall */ + error = urtw_intr_enable(sc); + if (error) + goto fail; + + error = urtw_write8e(sc, 0x41, 0xf4); + if (error) + goto fail; + error = urtw_write8e(sc, 0x40, 0x00); + if (error) + goto fail; + error = urtw_write8e(sc, 0x42, 0x00); + if (error) + goto fail; + error = urtw_write8e(sc, 0x42, 0x01); + if (error) + goto fail; + error = urtw_write8e(sc, 0x40, 0x0f); + if (error) + goto fail; + error = urtw_write8e(sc, 0x42, 0x00); + if (error) + goto fail; + error = urtw_write8e(sc, 0x42, 0x01); + if (error) + goto fail; + + urtw_read8_m(sc, 0xdb, &data8); + urtw_write8_m(sc, 0xdb, data8 | (1 << 2)); + urtw_write16_m(sc, 0x372, 0x59fa); + urtw_write16_m(sc, 0x374, 0x59d2); + urtw_write16_m(sc, 0x376, 0x59d2); + urtw_write16_m(sc, 0x378, 0x19fa); + urtw_write16_m(sc, 0x37a, 0x19fa); + urtw_write16_m(sc, 0x37c, 0x00d0); + urtw_write8_m(sc, 0x61, 0); + + urtw_write8_m(sc, 0x180, 0x0f); + urtw_write8_m(sc, 0x183, 0x03); + urtw_write8_m(sc, 0xda, 0x10); + urtw_write8_m(sc, 0x24d, 0x08); + urtw_write32_m(sc, URTW_HSSI_PARA, 0x0600321b); + + urtw_write16_m(sc, 0x1ec, 0x800); /* RX MAX SIZE */ +fail: + return (error); +#undef N +} + +static usb_error_t +urtw_adapter_start(struct urtw_softc *sc) +{ + usb_error_t error; + + error = urtw_reset(sc); + if (error) + goto fail; + + urtw_write8_m(sc, URTW_ADDR_MAGIC1, 0); + urtw_write8_m(sc, URTW_GPIO, 0); + + /* for led */ + urtw_write8_m(sc, URTW_ADDR_MAGIC1, 4); + error = urtw_led_ctl(sc, URTW_LED_CTL_POWER_ON); + if (error != 0) + goto fail; + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + /* applying MAC address again. */ + urtw_write32_m(sc, URTW_MAC0, ((uint32_t *)sc->sc_bssid)[0]); + urtw_write16_m(sc, URTW_MAC4, ((uint32_t *)sc->sc_bssid)[1] & 0xffff); + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; + + error = urtw_update_msr(sc); + if (error) + goto fail; + + urtw_write32_m(sc, URTW_INT_TIMEOUT, 0); + urtw_write8_m(sc, URTW_WPA_CONFIG, 0); + urtw_write8_m(sc, URTW_RATE_FALLBACK, URTW_RATE_FALLBACK_ENABLE | 0x1); + error = urtw_set_rate(sc); + if (error != 0) + goto fail; + + error = sc->sc_rf_init(sc); + if (error != 0) + goto fail; + if (sc->sc_rf_set_sens != NULL) + sc->sc_rf_set_sens(sc, sc->sc_sens); + + /* XXX correct? to call write16 */ + urtw_write16_m(sc, URTW_PSR, 1); + urtw_write16_m(sc, URTW_ADDR_MAGIC2, 0x10); + urtw_write8_m(sc, URTW_TALLY_SEL, 0x80); + urtw_write8_m(sc, URTW_ADDR_MAGIC3, 0x60); + /* XXX correct? to call write16 */ + urtw_write16_m(sc, URTW_PSR, 0); + urtw_write8_m(sc, URTW_ADDR_MAGIC1, 4); + + error = urtw_intr_enable(sc); + if (error != 0) + goto fail; + +fail: + return (error); +} + +static usb_error_t +urtw_set_mode(struct urtw_softc *sc, uint32_t mode) +{ + uint8_t data; + usb_error_t error; + + urtw_read8_m(sc, URTW_EPROM_CMD, &data); + data = (data & ~URTW_EPROM_CMD_MASK) | (mode << URTW_EPROM_CMD_SHIFT); + data = data & ~(URTW_EPROM_CS | URTW_EPROM_CK); + urtw_write8_m(sc, URTW_EPROM_CMD, data); +fail: + return (error); +} + +static usb_error_t +urtw_8187b_cmd_reset(struct urtw_softc *sc) +{ + int i; + uint8_t data8; + usb_error_t error; + + /* XXX the code can be duplicate with urtw_reset(). */ + urtw_read8_m(sc, URTW_CMD, &data8); + data8 = (data8 & 0x2) | URTW_CMD_RST; + urtw_write8_m(sc, URTW_CMD, data8); + + for (i = 0; i < 20; i++) { + usb_pause_mtx(&sc->sc_mtx, 2); + urtw_read8_m(sc, URTW_CMD, &data8); + if (!(data8 & URTW_CMD_RST)) + break; + } + if (i >= 20) { + device_printf(sc->sc_dev, "reset timeout\n"); + goto fail; + } +fail: + return (error); +} + +static usb_error_t +urtw_do_request(struct urtw_softc *sc, + struct usb_device_request *req, void *data) +{ + usb_error_t err; + int ntries = 10; + + URTW_ASSERT_LOCKED(sc); + + while (ntries--) { + err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, + req, data, 0, NULL, 250 /* ms */); + if (err == 0) + break; + + DPRINTF(sc, URTW_DEBUG_INIT, + "Control request failed, %s (retrying)\n", + usbd_errstr(err)); + usb_pause_mtx(&sc->sc_mtx, hz / 100); + } + return (err); +} + +static void +urtw_stop_locked(struct ifnet *ifp, int disable) +{ + struct urtw_softc *sc = ifp->if_softc; + uint8_t data8; + usb_error_t error; + + (void)disable; + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + error = urtw_intr_disable(sc); + if (error) + goto fail; + urtw_read8_m(sc, URTW_CMD, &data8); + data8 &= ~(URTW_CMD_RX_ENABLE | URTW_CMD_TX_ENABLE); + urtw_write8_m(sc, URTW_CMD, data8); + + error = sc->sc_rf_stop(sc); + if (error != 0) + goto fail; + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + urtw_read8_m(sc, URTW_CONFIG4, &data8); + urtw_write8_m(sc, URTW_CONFIG4, data8 | URTW_CONFIG4_VCOOFF); + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; +fail: + if (error) + device_printf(sc->sc_dev, "failed to stop (%s)\n", + usbd_errstr(error)); + + usb_callout_stop(&sc->sc_led_ch); + callout_stop(&sc->sc_watchdog_ch); + + urtw_abort_xfers(sc); +} + +static void +urtw_stop(struct ifnet *ifp, int disable) +{ + struct urtw_softc *sc = ifp->if_softc; + + URTW_LOCK(sc); + urtw_stop_locked(ifp, disable); + URTW_UNLOCK(sc); +} + +static void +urtw_abort_xfers(struct urtw_softc *sc) +{ + int i, max; + + URTW_ASSERT_LOCKED(sc); + + max = (sc->sc_flags & URTW_RTL8187B) ? URTW_8187B_N_XFERS : + URTW_8187L_N_XFERS; + + /* abort any pending transfers */ + for (i = 0; i < max; i++) + usbd_transfer_stop(sc->sc_xfer[i]); +} + +static int +urtw_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct urtw_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *) data; + int error = 0, startall = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + if ((ifp->if_flags ^ sc->sc_if_flags) & + (IFF_ALLMULTI | IFF_PROMISC)) + urtw_set_multi(sc); + } else { + urtw_init(ifp->if_softc); + startall = 1; + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + urtw_stop(ifp, 1); + } + sc->sc_if_flags = ifp->if_flags; + if (startall) + ieee80211_start_all(ic); + break; + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + case SIOCGIFADDR: + error = ether_ioctl(ifp, cmd, data); + break; + default: + error = EINVAL; + break; + } + + return (error); +} + +static void +urtw_start(struct ifnet *ifp) +{ + struct urtw_data *bf; + struct urtw_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + struct mbuf *m; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + URTW_LOCK(sc); + for (;;) { + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + bf = urtw_getbuf(sc); + if (bf == NULL) { + IFQ_DRV_PREPEND(&ifp->if_snd, m); + break; + } + + ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; + m->m_pkthdr.rcvif = NULL; + + if (urtw_tx_start(sc, ni, m, bf, URTW_PRIORITY_NORMAL) != 0) { + ifp->if_oerrors++; + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); + ieee80211_free_node(ni); + break; + } + + sc->sc_txtimer = 5; + callout_reset(&sc->sc_watchdog_ch, hz, urtw_watchdog, sc); + } + URTW_UNLOCK(sc); +} + +static int +urtw_alloc_data_list(struct urtw_softc *sc, struct urtw_data data[], + int ndata, int maxsz, int fillmbuf) +{ + int i, error; + + for (i = 0; i < ndata; i++) { + struct urtw_data *dp = &data[i]; + + dp->sc = sc; + if (fillmbuf) { + dp->m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (dp->m == NULL) { + device_printf(sc->sc_dev, + "could not allocate rx mbuf\n"); + error = ENOMEM; + goto fail; + } + dp->buf = mtod(dp->m, uint8_t *); + } else { + dp->m = NULL; + dp->buf = malloc(maxsz, M_USBDEV, M_NOWAIT); + if (dp->buf == NULL) { + device_printf(sc->sc_dev, + "could not allocate buffer\n"); + error = ENOMEM; + goto fail; + } + if (((unsigned long)dp->buf) % 4) + device_printf(sc->sc_dev, + "warn: unaligned buffer %p\n", dp->buf); + } + dp->ni = NULL; + } + + return 0; + +fail: urtw_free_data_list(sc, data, ndata, fillmbuf); + return error; +} + +static int +urtw_alloc_rx_data_list(struct urtw_softc *sc) +{ + int error, i; + + error = urtw_alloc_data_list(sc, + sc->sc_rx, URTW_RX_DATA_LIST_COUNT, MCLBYTES, 1 /* mbufs */); + if (error != 0) + return (error); + + STAILQ_INIT(&sc->sc_rx_active); + STAILQ_INIT(&sc->sc_rx_inactive); + + for (i = 0; i < URTW_RX_DATA_LIST_COUNT; i++) + STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i], next); + + return (0); +} + +static int +urtw_alloc_tx_data_list(struct urtw_softc *sc) +{ + int error, i; + + error = urtw_alloc_data_list(sc, + sc->sc_tx, URTW_TX_DATA_LIST_COUNT, URTW_TX_MAXSIZE, + 0 /* no mbufs */); + if (error != 0) + return (error); + + STAILQ_INIT(&sc->sc_tx_active); + STAILQ_INIT(&sc->sc_tx_inactive); + STAILQ_INIT(&sc->sc_tx_pending); + + for (i = 0; i < URTW_TX_DATA_LIST_COUNT; i++) + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i], + next); + + return (0); +} + +static int +urtw_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct urtw_data *bf; + struct urtw_softc *sc = ifp->if_softc; + + /* prevent management frames from being sent if we're not ready */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + m_freem(m); + ieee80211_free_node(ni); + return ENETDOWN; + } + URTW_LOCK(sc); + bf = urtw_getbuf(sc); + if (bf == NULL) { + ieee80211_free_node(ni); + m_freem(m); + URTW_UNLOCK(sc); + return (ENOBUFS); /* XXX */ + } + + ifp->if_opackets++; + if (urtw_tx_start(sc, ni, m, bf, URTW_PRIORITY_LOW) != 0) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next); + URTW_UNLOCK(sc); + return (EIO); + } + URTW_UNLOCK(sc); + + sc->sc_txtimer = 5; + return (0); +} + +static void +urtw_scan_start(struct ieee80211com *ic) +{ + + /* XXX do nothing? */ +} + +static void +urtw_scan_end(struct ieee80211com *ic) +{ + + /* XXX do nothing? */ +} + +static void +urtw_set_channel(struct ieee80211com *ic) +{ + struct urtw_softc *sc = ic->ic_ifp->if_softc; + struct ifnet *ifp = sc->sc_ifp; + uint32_t data, orig; + usb_error_t error; + + /* + * if the user set a channel explicitly using ifconfig(8) this function + * can be called earlier than we're expected that in some cases the + * initialization would be failed if setting a channel is called before + * the init have done. + */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + return; + + if (sc->sc_curchan != NULL && sc->sc_curchan == ic->ic_curchan) + return; + + URTW_LOCK(sc); + + /* + * during changing th channel we need to temporarily be disable + * TX. + */ + urtw_read32_m(sc, URTW_TX_CONF, &orig); + data = orig & ~URTW_TX_LOOPBACK_MASK; + urtw_write32_m(sc, URTW_TX_CONF, data | URTW_TX_LOOPBACK_MAC); + + error = sc->sc_rf_set_chan(sc, ieee80211_chan2ieee(ic, ic->ic_curchan)); + if (error != 0) + goto fail; + usb_pause_mtx(&sc->sc_mtx, 10); + urtw_write32_m(sc, URTW_TX_CONF, orig); + + urtw_write16_m(sc, URTW_ATIM_WND, 2); + urtw_write16_m(sc, URTW_ATIM_TR_ITV, 100); + urtw_write16_m(sc, URTW_BEACON_INTERVAL, 100); + urtw_write16_m(sc, URTW_BEACON_INTERVAL_TIME, 100); + +fail: + URTW_UNLOCK(sc); + + sc->sc_curchan = ic->ic_curchan; + + if (error != 0) + device_printf(sc->sc_dev, "could not change the channel\n"); +} + +static void +urtw_update_mcast(struct ifnet *ifp) +{ + + /* XXX do nothing? */ +} + +static int +urtw_tx_start(struct urtw_softc *sc, struct ieee80211_node *ni, struct mbuf *m0, + struct urtw_data *data, int prior) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211_frame *wh = mtod(m0, struct ieee80211_frame *); + struct ieee80211_key *k; + const struct ieee80211_txparam *tp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = ni->ni_vap; + struct usb_xfer *rtl8187b_pipes[URTW_8187B_TXPIPE_MAX] = { + sc->sc_xfer[URTW_8187B_BULK_TX_BE], + sc->sc_xfer[URTW_8187B_BULK_TX_BK], + sc->sc_xfer[URTW_8187B_BULK_TX_VI], + sc->sc_xfer[URTW_8187B_BULK_TX_VO] + }; + struct usb_xfer *xfer; + int dur = 0, rtsdur = 0, rtsenable = 0, ctsenable = 0, rate, + pkttime = 0, txdur = 0, isshort = 0, xferlen; + uint16_t acktime, rtstime, ctstime; + uint32_t flags; + usb_error_t error; + + URTW_ASSERT_LOCKED(sc); + + /* + * Software crypto. + */ + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + device_printf(sc->sc_dev, + "ieee80211_crypto_encap returns NULL.\n"); + /* XXX we don't expect the fragmented frames */ + m_freem(m0); + return (ENOBUFS); + } + + /* in case packet header moved, reset pointer */ + wh = mtod(m0, struct ieee80211_frame *); + } + + if (ieee80211_radiotap_active_vap(vap)) { + struct urtw_tx_radiotap_header *tap = &sc->sc_txtap; + + /* XXX Are variables correct? */ + tap->wt_flags = 0; + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + + ieee80211_radiotap_tx(vap, m0); + } + + if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_MGT || + (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_CTL) { + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + rate = tp->mgmtrate; + } else { + tp = &vap->iv_txparms[ieee80211_chan2mode(ni->ni_chan)]; + /* for data frames */ + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) + rate = tp->mcastrate; + else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) + rate = tp->ucastrate; + else + rate = urtw_rtl2rate(sc->sc_currate); + } + + sc->sc_stats.txrates[sc->sc_currate]++; + + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) + txdur = pkttime = urtw_compute_txtime(m0->m_pkthdr.len + + IEEE80211_CRC_LEN, rate, 0, 0); + else { + acktime = urtw_compute_txtime(14, 2,0, 0); + if ((m0->m_pkthdr.len + 4) > vap->iv_rtsthreshold) { + rtsenable = 1; + ctsenable = 0; + rtstime = urtw_compute_txtime(URTW_ACKCTS_LEN, 2, 0, 0); + ctstime = urtw_compute_txtime(14, 2, 0, 0); + pkttime = urtw_compute_txtime(m0->m_pkthdr.len + + IEEE80211_CRC_LEN, rate, 0, isshort); + rtsdur = ctstime + pkttime + acktime + + 3 * URTW_ASIFS_TIME; + txdur = rtstime + rtsdur; + } else { + rtsenable = ctsenable = rtsdur = 0; + pkttime = urtw_compute_txtime(m0->m_pkthdr.len + + IEEE80211_CRC_LEN, rate, 0, isshort); + txdur = pkttime + URTW_ASIFS_TIME + acktime; + } + + if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG) + dur = urtw_compute_txtime(m0->m_pkthdr.len + + IEEE80211_CRC_LEN, rate, 0, isshort) + + 3 * URTW_ASIFS_TIME + + 2 * acktime; + else + dur = URTW_ASIFS_TIME + acktime; + } + *(uint16_t *)wh->i_dur = htole16(dur); + + xferlen = m0->m_pkthdr.len; + xferlen += (sc->sc_flags & URTW_RTL8187B) ? (4 * 8) : (4 * 3); + if ((0 == xferlen % 64) || (0 == xferlen % 512)) + xferlen += 1; + + memset(data->buf, 0, URTW_TX_MAXSIZE); + flags = m0->m_pkthdr.len & 0xfff; + flags |= URTW_TX_FLAG_NO_ENC; + if ((ic->ic_flags & IEEE80211_F_SHPREAMBLE) && + (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE) && + (sc->sc_preamble_mode == URTW_PREAMBLE_MODE_SHORT) && + (sc->sc_currate != 0)) + flags |= URTW_TX_FLAG_SPLCP; + if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG) + flags |= URTW_TX_FLAG_MOREFRAG; + + flags |= (sc->sc_currate & 0xf) << URTW_TX_FLAG_TXRATE_SHIFT; + + if (sc->sc_flags & URTW_RTL8187B) { + struct urtw_8187b_txhdr *tx; + + tx = (struct urtw_8187b_txhdr *)data->buf; + if (ctsenable) + flags |= URTW_TX_FLAG_CTS; + if (rtsenable) { + flags |= URTW_TX_FLAG_RTS; + flags |= (urtw_rate2rtl(11) & 0xf) << + URTW_TX_FLAG_RTSRATE_SHIFT; + tx->rtsdur = rtsdur; + } + tx->flag = htole32(flags); + tx->txdur = txdur; + if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == + IEEE80211_FC0_TYPE_MGT && + (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == + IEEE80211_FC0_SUBTYPE_PROBE_RESP) + tx->retry = 1; + else + tx->retry = URTW_TX_MAXRETRY; + m_copydata(m0, 0, m0->m_pkthdr.len, (uint8_t *)(tx + 1)); + } else { + struct urtw_8187l_txhdr *tx; + + tx = (struct urtw_8187l_txhdr *)data->buf; + if (rtsenable) { + flags |= URTW_TX_FLAG_RTS; + tx->rtsdur = rtsdur; + } + flags |= (urtw_rate2rtl(11) & 0xf) << URTW_TX_FLAG_RTSRATE_SHIFT; + tx->flag = htole32(flags); + tx->retry = 3; /* CW minimum */ + tx->retry = 7 << 4; /* CW maximum */ + tx->retry = URTW_TX_MAXRETRY << 8; /* retry limitation */ + m_copydata(m0, 0, m0->m_pkthdr.len, (uint8_t *)(tx + 1)); + } + + data->buflen = xferlen; + data->ni = ni; + data->m = m0; + + if (sc->sc_flags & URTW_RTL8187B) { + switch (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) { + case IEEE80211_FC0_TYPE_CTL: + case IEEE80211_FC0_TYPE_MGT: + xfer = sc->sc_xfer[URTW_8187B_BULK_TX_EP12]; + break; + default: + KASSERT(M_WME_GETAC(m0) < URTW_8187B_TXPIPE_MAX, + ("unsupported WME pipe %d", M_WME_GETAC(m0))); + xfer = rtl8187b_pipes[M_WME_GETAC(m0)]; + break; + } + } else + xfer = (prior == URTW_PRIORITY_LOW) ? + sc->sc_xfer[URTW_8187L_BULK_TX_LOW] : + sc->sc_xfer[URTW_8187L_BULK_TX_NORMAL]; + + STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next); + usbd_transfer_start(xfer); + + error = urtw_led_ctl(sc, URTW_LED_CTL_TX); + if (error != 0) + device_printf(sc->sc_dev, "could not control LED (%d)\n", + error); + return (0); +} + +static int +urtw_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct ieee80211com *ic = vap->iv_ic; + struct urtw_softc *sc = ic->ic_ifp->if_softc; + struct urtw_vap *uvp = URTW_VAP(vap); + struct ieee80211_node *ni; + usb_error_t error = 0; + + DPRINTF(sc, URTW_DEBUG_STATE, "%s: %s -> %s\n", __func__, + ieee80211_state_name[vap->iv_state], + ieee80211_state_name[nstate]); + + sc->sc_state = nstate; + + IEEE80211_UNLOCK(ic); + URTW_LOCK(sc); + usb_callout_stop(&sc->sc_led_ch); + callout_stop(&sc->sc_watchdog_ch); + + switch (nstate) { + case IEEE80211_S_INIT: + case IEEE80211_S_SCAN: + case IEEE80211_S_AUTH: + case IEEE80211_S_ASSOC: + break; + case IEEE80211_S_RUN: + ni = ieee80211_ref_node(vap->iv_bss); + /* setting bssid. */ + urtw_write32_m(sc, URTW_BSSID, ((uint32_t *)ni->ni_bssid)[0]); + urtw_write16_m(sc, URTW_BSSID + 4, + ((uint16_t *)ni->ni_bssid)[2]); + urtw_update_msr(sc); + /* XXX maybe the below would be incorrect. */ + urtw_write16_m(sc, URTW_ATIM_WND, 2); + urtw_write16_m(sc, URTW_ATIM_TR_ITV, 100); + urtw_write16_m(sc, URTW_BEACON_INTERVAL, 0x64); + urtw_write16_m(sc, URTW_BEACON_INTERVAL_TIME, 100); + error = urtw_led_ctl(sc, URTW_LED_CTL_LINK); + if (error != 0) + device_printf(sc->sc_dev, + "could not control LED (%d)\n", error); + ieee80211_free_node(ni); + break; + default: + break; + } +fail: + URTW_UNLOCK(sc); + IEEE80211_LOCK(ic); + return (uvp->newstate(vap, nstate, arg)); +} + +static void +urtw_watchdog(void *arg) +{ + struct urtw_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + + if (sc->sc_txtimer > 0) { + if (--sc->sc_txtimer == 0) { + device_printf(sc->sc_dev, "device timeout\n"); + ifp->if_oerrors++; + return; + } + callout_reset(&sc->sc_watchdog_ch, hz, urtw_watchdog, sc); + } +} + +static void +urtw_set_multi(void *arg) +{ + struct urtw_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + + if (!(ifp->if_flags & IFF_UP)) + return; + + /* + * XXX don't know how to set a device. Lack of docs. Just try to set + * IFF_ALLMULTI flag here. + */ + ifp->if_flags |= IFF_ALLMULTI; +} + +static usb_error_t +urtw_set_rate(struct urtw_softc *sc) +{ + int i, basic_rate, min_rr_rate, max_rr_rate; + uint16_t data; + usb_error_t error; + + basic_rate = urtw_rate2rtl(48); + min_rr_rate = urtw_rate2rtl(12); + max_rr_rate = urtw_rate2rtl(48); + + urtw_write8_m(sc, URTW_RESP_RATE, + max_rr_rate << URTW_RESP_MAX_RATE_SHIFT | + min_rr_rate << URTW_RESP_MIN_RATE_SHIFT); + + urtw_read16_m(sc, URTW_BRSR, &data); + data &= ~URTW_BRSR_MBR_8185; + + for (i = 0; i <= basic_rate; i++) + data |= (1 << i); + + urtw_write16_m(sc, URTW_BRSR, data); +fail: + return (error); +} + +static uint16_t +urtw_rate2rtl(int rate) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int i; + + for (i = 0; i < N(urtw_ratetable); i++) { + if (rate == urtw_ratetable[i].reg) + return urtw_ratetable[i].val; + } + + return (3); +#undef N +} + +static uint16_t +urtw_rtl2rate(int rate) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int i; + + for (i = 0; i < N(urtw_ratetable); i++) { + if (rate == urtw_ratetable[i].val) + return urtw_ratetable[i].reg; + } + + return (0); +#undef N +} + +static usb_error_t +urtw_update_msr(struct urtw_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint8_t data; + usb_error_t error; + + urtw_read8_m(sc, URTW_MSR, &data); + data &= ~URTW_MSR_LINK_MASK; + + if (sc->sc_state == IEEE80211_S_RUN) { + switch (ic->ic_opmode) { + case IEEE80211_M_STA: + case IEEE80211_M_MONITOR: + data |= URTW_MSR_LINK_STA; + if (sc->sc_flags & URTW_RTL8187B) + data |= URTW_MSR_LINK_ENEDCA; + break; + case IEEE80211_M_IBSS: + data |= URTW_MSR_LINK_ADHOC; + break; + case IEEE80211_M_HOSTAP: + data |= URTW_MSR_LINK_HOSTAP; + break; + default: + panic("unsupported operation mode 0x%x\n", + ic->ic_opmode); + /* never reach */ + } + } else + data |= URTW_MSR_LINK_NONE; + + urtw_write8_m(sc, URTW_MSR, data); +fail: + return (error); +} + +static usb_error_t +urtw_read8_c(struct urtw_softc *sc, int val, uint8_t *data) +{ + struct usb_device_request req; + usb_error_t error; + + URTW_ASSERT_LOCKED(sc); + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = URTW_8187_GETREGS_REQ; + USETW(req.wValue, (val & 0xff) | 0xff00); + USETW(req.wIndex, (val >> 8) & 0x3); + USETW(req.wLength, sizeof(uint8_t)); + + error = urtw_do_request(sc, &req, data); + return (error); +} + +static usb_error_t +urtw_read16_c(struct urtw_softc *sc, int val, uint16_t *data) +{ + struct usb_device_request req; + usb_error_t error; + + URTW_ASSERT_LOCKED(sc); + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = URTW_8187_GETREGS_REQ; + USETW(req.wValue, (val & 0xff) | 0xff00); + USETW(req.wIndex, (val >> 8) & 0x3); + USETW(req.wLength, sizeof(uint16_t)); + + error = urtw_do_request(sc, &req, data); + return (error); +} + +static usb_error_t +urtw_read32_c(struct urtw_softc *sc, int val, uint32_t *data) +{ + struct usb_device_request req; + usb_error_t error; + + URTW_ASSERT_LOCKED(sc); + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = URTW_8187_GETREGS_REQ; + USETW(req.wValue, (val & 0xff) | 0xff00); + USETW(req.wIndex, (val >> 8) & 0x3); + USETW(req.wLength, sizeof(uint32_t)); + + error = urtw_do_request(sc, &req, data); + return (error); +} + +static usb_error_t +urtw_write8_c(struct urtw_softc *sc, int val, uint8_t data) +{ + struct usb_device_request req; + + URTW_ASSERT_LOCKED(sc); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = URTW_8187_SETREGS_REQ; + USETW(req.wValue, (val & 0xff) | 0xff00); + USETW(req.wIndex, (val >> 8) & 0x3); + USETW(req.wLength, sizeof(uint8_t)); + + return (urtw_do_request(sc, &req, &data)); +} + +static usb_error_t +urtw_write16_c(struct urtw_softc *sc, int val, uint16_t data) +{ + struct usb_device_request req; + + URTW_ASSERT_LOCKED(sc); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = URTW_8187_SETREGS_REQ; + USETW(req.wValue, (val & 0xff) | 0xff00); + USETW(req.wIndex, (val >> 8) & 0x3); + USETW(req.wLength, sizeof(uint16_t)); + + return (urtw_do_request(sc, &req, &data)); +} + +static usb_error_t +urtw_write32_c(struct urtw_softc *sc, int val, uint32_t data) +{ + struct usb_device_request req; + + URTW_ASSERT_LOCKED(sc); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = URTW_8187_SETREGS_REQ; + USETW(req.wValue, (val & 0xff) | 0xff00); + USETW(req.wIndex, (val >> 8) & 0x3); + USETW(req.wLength, sizeof(uint32_t)); + + return (urtw_do_request(sc, &req, &data)); +} + +static usb_error_t +urtw_get_macaddr(struct urtw_softc *sc) +{ + uint32_t data; + usb_error_t error; + + error = urtw_eprom_read32(sc, URTW_EPROM_MACADDR, &data); + if (error != 0) + goto fail; + sc->sc_bssid[0] = data & 0xff; + sc->sc_bssid[1] = (data & 0xff00) >> 8; + error = urtw_eprom_read32(sc, URTW_EPROM_MACADDR + 1, &data); + if (error != 0) + goto fail; + sc->sc_bssid[2] = data & 0xff; + sc->sc_bssid[3] = (data & 0xff00) >> 8; + error = urtw_eprom_read32(sc, URTW_EPROM_MACADDR + 2, &data); + if (error != 0) + goto fail; + sc->sc_bssid[4] = data & 0xff; + sc->sc_bssid[5] = (data & 0xff00) >> 8; +fail: + return (error); +} + +static usb_error_t +urtw_eprom_read32(struct urtw_softc *sc, uint32_t addr, uint32_t *data) +{ +#define URTW_READCMD_LEN 3 + int addrlen, i; + int16_t addrstr[8], data16, readcmd[] = { 1, 1, 0 }; + usb_error_t error; + + /* NB: make sure the buffer is initialized */ + *data = 0; + + /* enable EPROM programming */ + urtw_write8_m(sc, URTW_EPROM_CMD, URTW_EPROM_CMD_PROGRAM_MODE); + DELAY(URTW_EPROM_DELAY); + + error = urtw_eprom_cs(sc, URTW_EPROM_ENABLE); + if (error != 0) + goto fail; + error = urtw_eprom_ck(sc); + if (error != 0) + goto fail; + error = urtw_eprom_sendbits(sc, readcmd, URTW_READCMD_LEN); + if (error != 0) + goto fail; + if (sc->sc_epromtype == URTW_EEPROM_93C56) { + addrlen = 8; + addrstr[0] = addr & (1 << 7); + addrstr[1] = addr & (1 << 6); + addrstr[2] = addr & (1 << 5); + addrstr[3] = addr & (1 << 4); + addrstr[4] = addr & (1 << 3); + addrstr[5] = addr & (1 << 2); + addrstr[6] = addr & (1 << 1); + addrstr[7] = addr & (1 << 0); + } else { + addrlen=6; + addrstr[0] = addr & (1 << 5); + addrstr[1] = addr & (1 << 4); + addrstr[2] = addr & (1 << 3); + addrstr[3] = addr & (1 << 2); + addrstr[4] = addr & (1 << 1); + addrstr[5] = addr & (1 << 0); + } + error = urtw_eprom_sendbits(sc, addrstr, addrlen); + if (error != 0) + goto fail; + + error = urtw_eprom_writebit(sc, 0); + if (error != 0) + goto fail; + + for (i = 0; i < 16; i++) { + error = urtw_eprom_ck(sc); + if (error != 0) + goto fail; + error = urtw_eprom_readbit(sc, &data16); + if (error != 0) + goto fail; + + (*data) |= (data16 << (15 - i)); + } + + error = urtw_eprom_cs(sc, URTW_EPROM_DISABLE); + if (error != 0) + goto fail; + error = urtw_eprom_ck(sc); + if (error != 0) + goto fail; + + /* now disable EPROM programming */ + urtw_write8_m(sc, URTW_EPROM_CMD, URTW_EPROM_CMD_NORMAL_MODE); +fail: + return (error); +#undef URTW_READCMD_LEN +} + +static usb_error_t +urtw_eprom_cs(struct urtw_softc *sc, int able) +{ + uint8_t data; + usb_error_t error; + + urtw_read8_m(sc, URTW_EPROM_CMD, &data); + if (able == URTW_EPROM_ENABLE) + urtw_write8_m(sc, URTW_EPROM_CMD, data | URTW_EPROM_CS); + else + urtw_write8_m(sc, URTW_EPROM_CMD, data & ~URTW_EPROM_CS); + DELAY(URTW_EPROM_DELAY); +fail: + return (error); +} + +static usb_error_t +urtw_eprom_ck(struct urtw_softc *sc) +{ + uint8_t data; + usb_error_t error; + + /* masking */ + urtw_read8_m(sc, URTW_EPROM_CMD, &data); + urtw_write8_m(sc, URTW_EPROM_CMD, data | URTW_EPROM_CK); + DELAY(URTW_EPROM_DELAY); + /* unmasking */ + urtw_read8_m(sc, URTW_EPROM_CMD, &data); + urtw_write8_m(sc, URTW_EPROM_CMD, data & ~URTW_EPROM_CK); + DELAY(URTW_EPROM_DELAY); +fail: + return (error); +} + +static usb_error_t +urtw_eprom_readbit(struct urtw_softc *sc, int16_t *data) +{ + uint8_t data8; + usb_error_t error; + + urtw_read8_m(sc, URTW_EPROM_CMD, &data8); + *data = (data8 & URTW_EPROM_READBIT) ? 1 : 0; + DELAY(URTW_EPROM_DELAY); + +fail: + return (error); +} + +static usb_error_t +urtw_eprom_writebit(struct urtw_softc *sc, int16_t bit) +{ + uint8_t data; + usb_error_t error; + + urtw_read8_m(sc, URTW_EPROM_CMD, &data); + if (bit != 0) + urtw_write8_m(sc, URTW_EPROM_CMD, data | URTW_EPROM_WRITEBIT); + else + urtw_write8_m(sc, URTW_EPROM_CMD, data & ~URTW_EPROM_WRITEBIT); + DELAY(URTW_EPROM_DELAY); +fail: + return (error); +} + +static usb_error_t +urtw_eprom_sendbits(struct urtw_softc *sc, int16_t *buf, int buflen) +{ + int i = 0; + usb_error_t error = 0; + + for (i = 0; i < buflen; i++) { + error = urtw_eprom_writebit(sc, buf[i]); + if (error != 0) + goto fail; + error = urtw_eprom_ck(sc); + if (error != 0) + goto fail; + } +fail: + return (error); +} + + +static usb_error_t +urtw_get_txpwr(struct urtw_softc *sc) +{ + int i, j; + uint32_t data; + usb_error_t error; + + error = urtw_eprom_read32(sc, URTW_EPROM_TXPW_BASE, &data); + if (error != 0) + goto fail; + sc->sc_txpwr_cck_base = data & 0xf; + sc->sc_txpwr_ofdm_base = (data >> 4) & 0xf; + + for (i = 1, j = 0; i < 6; i += 2, j++) { + error = urtw_eprom_read32(sc, URTW_EPROM_TXPW0 + j, &data); + if (error != 0) + goto fail; + sc->sc_txpwr_cck[i] = data & 0xf; + sc->sc_txpwr_cck[i + 1] = (data & 0xf00) >> 8; + sc->sc_txpwr_ofdm[i] = (data & 0xf0) >> 4; + sc->sc_txpwr_ofdm[i + 1] = (data & 0xf000) >> 12; + } + for (i = 1, j = 0; i < 4; i += 2, j++) { + error = urtw_eprom_read32(sc, URTW_EPROM_TXPW1 + j, &data); + if (error != 0) + goto fail; + sc->sc_txpwr_cck[i + 6] = data & 0xf; + sc->sc_txpwr_cck[i + 6 + 1] = (data & 0xf00) >> 8; + sc->sc_txpwr_ofdm[i + 6] = (data & 0xf0) >> 4; + sc->sc_txpwr_ofdm[i + 6 + 1] = (data & 0xf000) >> 12; + } + if (sc->sc_flags & URTW_RTL8187B) { + error = urtw_eprom_read32(sc, URTW_EPROM_TXPW2, &data); + if (error != 0) + goto fail; + sc->sc_txpwr_cck[1 + 6 + 4] = data & 0xf; + sc->sc_txpwr_ofdm[1 + 6 + 4] = (data & 0xf0) >> 4; + error = urtw_eprom_read32(sc, 0x0a, &data); + if (error != 0) + goto fail; + sc->sc_txpwr_cck[2 + 6 + 4] = data & 0xf; + sc->sc_txpwr_ofdm[2 + 6 + 4] = (data & 0xf0) >> 4; + error = urtw_eprom_read32(sc, 0x1c, &data); + if (error != 0) + goto fail; + sc->sc_txpwr_cck[3 + 6 + 4] = data & 0xf; + sc->sc_txpwr_cck[3 + 6 + 4 + 1] = (data & 0xf00) >> 8; + sc->sc_txpwr_ofdm[3 + 6 + 4] = (data & 0xf0) >> 4; + sc->sc_txpwr_ofdm[3 + 6 + 4 + 1] = (data & 0xf000) >> 12; + } else { + for (i = 1, j = 0; i < 4; i += 2, j++) { + error = urtw_eprom_read32(sc, URTW_EPROM_TXPW2 + j, + &data); + if (error != 0) + goto fail; + sc->sc_txpwr_cck[i + 6 + 4] = data & 0xf; + sc->sc_txpwr_cck[i + 6 + 4 + 1] = (data & 0xf00) >> 8; + sc->sc_txpwr_ofdm[i + 6 + 4] = (data & 0xf0) >> 4; + sc->sc_txpwr_ofdm[i + 6 + 4 + 1] = (data & 0xf000) >> 12; + } + } +fail: + return (error); +} + + +static usb_error_t +urtw_get_rfchip(struct urtw_softc *sc) +{ + int ret; + uint8_t data8; + uint32_t data; + usb_error_t error; + + if (sc->sc_flags & URTW_RTL8187B) { + urtw_read8_m(sc, 0xe1, &data8); + switch (data8) { + case 0: + sc->sc_flags |= URTW_RTL8187B_REV_B; + break; + case 1: + sc->sc_flags |= URTW_RTL8187B_REV_D; + break; + case 2: + sc->sc_flags |= URTW_RTL8187B_REV_E; + break; + default: + device_printf(sc->sc_dev, "unknown type: %#x\n", data8); + sc->sc_flags |= URTW_RTL8187B_REV_B; + break; + } + } else { + urtw_read32_m(sc, URTW_TX_CONF, &data); + switch (data & URTW_TX_HWMASK) { + case URTW_TX_R8187vD_B: + sc->sc_flags |= URTW_RTL8187B; + break; + case URTW_TX_R8187vD: + break; + default: + device_printf(sc->sc_dev, "unknown RTL8187L type: %#x\n", + data & URTW_TX_HWMASK); + break; + } + } + + error = urtw_eprom_read32(sc, URTW_EPROM_RFCHIPID, &data); + if (error != 0) + goto fail; + switch (data & 0xff) { + case URTW_EPROM_RFCHIPID_RTL8225U: + error = urtw_8225_isv2(sc, &ret); + if (error != 0) + goto fail; + if (ret == 0) { + sc->sc_rf_init = urtw_8225_rf_init; + sc->sc_rf_set_sens = urtw_8225_rf_set_sens; + sc->sc_rf_set_chan = urtw_8225_rf_set_chan; + sc->sc_rf_stop = urtw_8225_rf_stop; + } else { + sc->sc_rf_init = urtw_8225v2_rf_init; + sc->sc_rf_set_chan = urtw_8225v2_rf_set_chan; + sc->sc_rf_stop = urtw_8225_rf_stop; + } + sc->sc_max_sens = URTW_8225_RF_MAX_SENS; + sc->sc_sens = URTW_8225_RF_DEF_SENS; + break; + case URTW_EPROM_RFCHIPID_RTL8225Z2: + sc->sc_rf_init = urtw_8225v2b_rf_init; + sc->sc_rf_set_chan = urtw_8225v2b_rf_set_chan; + sc->sc_max_sens = URTW_8225_RF_MAX_SENS; + sc->sc_sens = URTW_8225_RF_DEF_SENS; + sc->sc_rf_stop = urtw_8225_rf_stop; + break; + default: + panic("unsupported RF chip %d\n", data & 0xff); + /* never reach */ + } + + device_printf(sc->sc_dev, "%s rf %s hwrev %s\n", + (sc->sc_flags & URTW_RTL8187B) ? "rtl8187b" : "rtl8187l", + ((data & 0xff) == URTW_EPROM_RFCHIPID_RTL8225U) ? "rtl8225u" : + "rtl8225z2", + (sc->sc_flags & URTW_RTL8187B) ? ((data8 == 0) ? "b" : + (data8 == 1) ? "d" : "e") : "none"); + +fail: + return (error); +} + + +static usb_error_t +urtw_led_init(struct urtw_softc *sc) +{ + uint32_t rev; + usb_error_t error; + + urtw_read8_m(sc, URTW_PSR, &sc->sc_psr); + error = urtw_eprom_read32(sc, URTW_EPROM_SWREV, &rev); + if (error != 0) + goto fail; + + switch (rev & URTW_EPROM_CID_MASK) { + case URTW_EPROM_CID_ALPHA0: + sc->sc_strategy = URTW_SW_LED_MODE1; + break; + case URTW_EPROM_CID_SERCOMM_PS: + sc->sc_strategy = URTW_SW_LED_MODE3; + break; + case URTW_EPROM_CID_HW_LED: + sc->sc_strategy = URTW_HW_LED; + break; + case URTW_EPROM_CID_RSVD0: + case URTW_EPROM_CID_RSVD1: + default: + sc->sc_strategy = URTW_SW_LED_MODE0; + break; + } + + sc->sc_gpio_ledpin = URTW_LED_PIN_GPIO0; + +fail: + return (error); +} + + +static usb_error_t +urtw_8225_rf_init(struct urtw_softc *sc) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int i; + uint16_t data; + usb_error_t error; + + error = urtw_8180_set_anaparam(sc, URTW_8225_ANAPARAM_ON); + if (error) + goto fail; + + error = urtw_8225_usb_init(sc); + if (error) + goto fail; + + urtw_write32_m(sc, URTW_RF_TIMING, 0x000a8008); + urtw_read16_m(sc, URTW_BRSR, &data); /* XXX ??? */ + urtw_write16_m(sc, URTW_BRSR, 0xffff); + urtw_write32_m(sc, URTW_RF_PARA, 0x100044); + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + urtw_write8_m(sc, URTW_CONFIG3, 0x44); + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; + + error = urtw_8185_rf_pins_enable(sc); + if (error) + goto fail; + usb_pause_mtx(&sc->sc_mtx, 1000); + + for (i = 0; i < N(urtw_8225_rf_part1); i++) { + urtw_8225_write(sc, urtw_8225_rf_part1[i].reg, + urtw_8225_rf_part1[i].val); + usb_pause_mtx(&sc->sc_mtx, 1); + } + usb_pause_mtx(&sc->sc_mtx, 100); + urtw_8225_write(sc, + URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC1); + usb_pause_mtx(&sc->sc_mtx, 200); + urtw_8225_write(sc, + URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC2); + usb_pause_mtx(&sc->sc_mtx, 200); + urtw_8225_write(sc, + URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC3); + + for (i = 0; i < 95; i++) { + urtw_8225_write(sc, URTW_8225_ADDR_1_MAGIC, (uint8_t)(i + 1)); + urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, urtw_8225_rxgain[i]); + } + + urtw_8225_write(sc, + URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC4); + urtw_8225_write(sc, + URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC5); + + for (i = 0; i < 128; i++) { + urtw_8187_write_phy_ofdm(sc, 0xb, urtw_8225_agc[i]); + usb_pause_mtx(&sc->sc_mtx, 1); + urtw_8187_write_phy_ofdm(sc, 0xa, (uint8_t)i + 0x80); + usb_pause_mtx(&sc->sc_mtx, 1); + } + + for (i = 0; i < N(urtw_8225_rf_part2); i++) { + urtw_8187_write_phy_ofdm(sc, urtw_8225_rf_part2[i].reg, + urtw_8225_rf_part2[i].val); + usb_pause_mtx(&sc->sc_mtx, 1); + } + + error = urtw_8225_setgain(sc, 4); + if (error) + goto fail; + + for (i = 0; i < N(urtw_8225_rf_part3); i++) { + urtw_8187_write_phy_cck(sc, urtw_8225_rf_part3[i].reg, + urtw_8225_rf_part3[i].val); + usb_pause_mtx(&sc->sc_mtx, 1); + } + + urtw_write8_m(sc, URTW_TESTR, 0x0d); + + error = urtw_8225_set_txpwrlvl(sc, 1); + if (error) + goto fail; + + urtw_8187_write_phy_cck(sc, 0x10, 0x9b); + usb_pause_mtx(&sc->sc_mtx, 1); + urtw_8187_write_phy_ofdm(sc, 0x26, 0x90); + usb_pause_mtx(&sc->sc_mtx, 1); + + /* TX ant A, 0x0 for B */ + error = urtw_8185_tx_antenna(sc, 0x3); + if (error) + goto fail; + urtw_write32_m(sc, URTW_HSSI_PARA, 0x3dc00002); + + error = urtw_8225_rf_set_chan(sc, 1); +fail: + return (error); +#undef N +} + +static usb_error_t +urtw_8185_rf_pins_enable(struct urtw_softc *sc) +{ + usb_error_t error = 0; + + urtw_write16_m(sc, URTW_RF_PINS_ENABLE, 0x1ff7); +fail: + return (error); +} + +static usb_error_t +urtw_8185_tx_antenna(struct urtw_softc *sc, uint8_t ant) +{ + usb_error_t error; + + urtw_write8_m(sc, URTW_TX_ANTENNA, ant); + usb_pause_mtx(&sc->sc_mtx, 1); +fail: + return (error); +} + +static usb_error_t +urtw_8187_write_phy_ofdm_c(struct urtw_softc *sc, uint8_t addr, uint32_t data) +{ + + data = data & 0xff; + return urtw_8187_write_phy(sc, addr, data); +} + +static usb_error_t +urtw_8187_write_phy_cck_c(struct urtw_softc *sc, uint8_t addr, uint32_t data) +{ + + data = data & 0xff; + return urtw_8187_write_phy(sc, addr, data | 0x10000); +} + +static usb_error_t +urtw_8187_write_phy(struct urtw_softc *sc, uint8_t addr, uint32_t data) +{ + uint32_t phyw; + usb_error_t error; + + phyw = ((data << 8) | (addr | 0x80)); + urtw_write8_m(sc, URTW_PHY_MAGIC4, ((phyw & 0xff000000) >> 24)); + urtw_write8_m(sc, URTW_PHY_MAGIC3, ((phyw & 0x00ff0000) >> 16)); + urtw_write8_m(sc, URTW_PHY_MAGIC2, ((phyw & 0x0000ff00) >> 8)); + urtw_write8_m(sc, URTW_PHY_MAGIC1, ((phyw & 0x000000ff))); + usb_pause_mtx(&sc->sc_mtx, 1); +fail: + return (error); +} + +static usb_error_t +urtw_8225_setgain(struct urtw_softc *sc, int16_t gain) +{ + usb_error_t error; + + urtw_8187_write_phy_ofdm(sc, 0x0d, urtw_8225_gain[gain * 4]); + urtw_8187_write_phy_ofdm(sc, 0x1b, urtw_8225_gain[gain * 4 + 2]); + urtw_8187_write_phy_ofdm(sc, 0x1d, urtw_8225_gain[gain * 4 + 3]); + urtw_8187_write_phy_ofdm(sc, 0x23, urtw_8225_gain[gain * 4 + 1]); +fail: + return (error); +} + +static usb_error_t +urtw_8225_usb_init(struct urtw_softc *sc) +{ + uint8_t data; + usb_error_t error; + + urtw_write8_m(sc, URTW_RF_PINS_SELECT + 1, 0); + urtw_write8_m(sc, URTW_GPIO, 0); + error = urtw_read8e(sc, 0x53, &data); + if (error) + goto fail; + error = urtw_write8e(sc, 0x53, data | (1 << 7)); + if (error) + goto fail; + urtw_write8_m(sc, URTW_RF_PINS_SELECT + 1, 4); + urtw_write8_m(sc, URTW_GPIO, 0x20); + urtw_write8_m(sc, URTW_GP_ENABLE, 0); + + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, 0x80); + urtw_write16_m(sc, URTW_RF_PINS_SELECT, 0x80); + urtw_write16_m(sc, URTW_RF_PINS_ENABLE, 0x80); + + usb_pause_mtx(&sc->sc_mtx, 500); +fail: + return (error); +} + +static usb_error_t +urtw_8225_write_c(struct urtw_softc *sc, uint8_t addr, uint16_t data) +{ + uint16_t d80, d82, d84; + usb_error_t error; + + urtw_read16_m(sc, URTW_RF_PINS_OUTPUT, &d80); + d80 &= URTW_RF_PINS_MAGIC1; + urtw_read16_m(sc, URTW_RF_PINS_ENABLE, &d82); + urtw_read16_m(sc, URTW_RF_PINS_SELECT, &d84); + d84 &= URTW_RF_PINS_MAGIC2; + urtw_write16_m(sc, URTW_RF_PINS_ENABLE, d82 | URTW_RF_PINS_MAGIC3); + urtw_write16_m(sc, URTW_RF_PINS_SELECT, d84 | URTW_RF_PINS_MAGIC3); + DELAY(10); + + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, d80 | URTW_BB_HOST_BANG_EN); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, d80); + DELAY(10); + + error = urtw_8225_write_s16(sc, addr, 0x8225, &data); + if (error != 0) + goto fail; + + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, d80 | URTW_BB_HOST_BANG_EN); + DELAY(10); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, d80 | URTW_BB_HOST_BANG_EN); + urtw_write16_m(sc, URTW_RF_PINS_SELECT, d84); + usb_pause_mtx(&sc->sc_mtx, 2); +fail: + return (error); +} + +/* XXX why we should allocalte memory buffer instead of using memory stack? */ +static usb_error_t +urtw_8225_write_s16(struct urtw_softc *sc, uint8_t addr, int index, + uint16_t *data) +{ + uint8_t *buf; + uint16_t data16; + struct usb_device_request *req; + usb_error_t error = 0; + + data16 = *data; + req = (usb_device_request_t *)malloc(sizeof(usb_device_request_t), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (req == NULL) { + device_printf(sc->sc_dev, "could not allocate a memory\n"); + goto fail0; + } + buf = (uint8_t *)malloc(2, M_80211_VAP, M_NOWAIT | M_ZERO); + if (req == NULL) { + device_printf(sc->sc_dev, "could not allocate a memory\n"); + goto fail1; + } + + req->bmRequestType = UT_WRITE_VENDOR_DEVICE; + req->bRequest = URTW_8187_SETREGS_REQ; + USETW(req->wValue, addr); + USETW(req->wIndex, index); + USETW(req->wLength, sizeof(uint16_t)); + buf[0] = (data16 & 0x00ff); + buf[1] = (data16 & 0xff00) >> 8; + + error = urtw_do_request(sc, req, buf); + + free(buf, M_80211_VAP); +fail1: free(req, M_80211_VAP); +fail0: return (error); +} + +static usb_error_t +urtw_8225_rf_set_chan(struct urtw_softc *sc, int chan) +{ + usb_error_t error; + + error = urtw_8225_set_txpwrlvl(sc, chan); + if (error) + goto fail; + urtw_8225_write(sc, URTW_8225_ADDR_7_MAGIC, urtw_8225_channel[chan]); + usb_pause_mtx(&sc->sc_mtx, 10); +fail: + return (error); +} + +static usb_error_t +urtw_8225_rf_set_sens(struct urtw_softc *sc, int sens) +{ + usb_error_t error; + + if (sens < 0 || sens > 6) + return -1; + + if (sens > 4) + urtw_8225_write(sc, + URTW_8225_ADDR_C_MAGIC, URTW_8225_ADDR_C_DATA_MAGIC1); + else + urtw_8225_write(sc, + URTW_8225_ADDR_C_MAGIC, URTW_8225_ADDR_C_DATA_MAGIC2); + + sens = 6 - sens; + error = urtw_8225_setgain(sc, sens); + if (error) + goto fail; + + urtw_8187_write_phy_cck(sc, 0x41, urtw_8225_threshold[sens]); + +fail: + return (error); +} + +static usb_error_t +urtw_8225_set_txpwrlvl(struct urtw_softc *sc, int chan) +{ + int i, idx, set; + uint8_t *cck_pwltable; + uint8_t cck_pwrlvl_max, ofdm_pwrlvl_min, ofdm_pwrlvl_max; + uint8_t cck_pwrlvl = sc->sc_txpwr_cck[chan] & 0xff; + uint8_t ofdm_pwrlvl = sc->sc_txpwr_ofdm[chan] & 0xff; + usb_error_t error; + + cck_pwrlvl_max = 11; + ofdm_pwrlvl_max = 25; /* 12 -> 25 */ + ofdm_pwrlvl_min = 10; + + /* CCK power setting */ + cck_pwrlvl = (cck_pwrlvl > cck_pwrlvl_max) ? cck_pwrlvl_max : cck_pwrlvl; + idx = cck_pwrlvl % 6; + set = cck_pwrlvl / 6; + cck_pwltable = (chan == 14) ? urtw_8225_txpwr_cck_ch14 : + urtw_8225_txpwr_cck; + + urtw_write8_m(sc, URTW_TX_GAIN_CCK, + urtw_8225_tx_gain_cck_ofdm[set] >> 1); + for (i = 0; i < 8; i++) { + urtw_8187_write_phy_cck(sc, 0x44 + i, + cck_pwltable[idx * 8 + i]); + } + usb_pause_mtx(&sc->sc_mtx, 1); + + /* OFDM power setting */ + ofdm_pwrlvl = (ofdm_pwrlvl > (ofdm_pwrlvl_max - ofdm_pwrlvl_min)) ? + ofdm_pwrlvl_max : ofdm_pwrlvl + ofdm_pwrlvl_min; + ofdm_pwrlvl = (ofdm_pwrlvl > 35) ? 35 : ofdm_pwrlvl; + + idx = ofdm_pwrlvl % 6; + set = ofdm_pwrlvl / 6; + + error = urtw_8185_set_anaparam2(sc, URTW_8225_ANAPARAM2_ON); + if (error) + goto fail; + urtw_8187_write_phy_ofdm(sc, 2, 0x42); + urtw_8187_write_phy_ofdm(sc, 6, 0); + urtw_8187_write_phy_ofdm(sc, 8, 0); + + urtw_write8_m(sc, URTW_TX_GAIN_OFDM, + urtw_8225_tx_gain_cck_ofdm[set] >> 1); + urtw_8187_write_phy_ofdm(sc, 0x5, urtw_8225_txpwr_ofdm[idx]); + urtw_8187_write_phy_ofdm(sc, 0x7, urtw_8225_txpwr_ofdm[idx]); + usb_pause_mtx(&sc->sc_mtx, 1); +fail: + return (error); +} + + +static usb_error_t +urtw_8225_rf_stop(struct urtw_softc *sc) +{ + uint8_t data; + usb_error_t error; + + urtw_8225_write(sc, 0x4, 0x1f); + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + + urtw_read8_m(sc, URTW_CONFIG3, &data); + urtw_write8_m(sc, URTW_CONFIG3, data | URTW_CONFIG3_ANAPARAM_WRITE); + if (sc->sc_flags & URTW_RTL8187B) { + urtw_write32_m(sc, URTW_ANAPARAM2, + URTW_8187B_8225_ANAPARAM2_OFF); + urtw_write32_m(sc, URTW_ANAPARAM, URTW_8187B_8225_ANAPARAM_OFF); + urtw_write32_m(sc, URTW_ANAPARAM3, + URTW_8187B_8225_ANAPARAM3_OFF); + } else { + urtw_write32_m(sc, URTW_ANAPARAM2, URTW_8225_ANAPARAM2_OFF); + urtw_write32_m(sc, URTW_ANAPARAM, URTW_8225_ANAPARAM_OFF); + } + + urtw_write8_m(sc, URTW_CONFIG3, data & ~URTW_CONFIG3_ANAPARAM_WRITE); + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; + +fail: + return (error); +} + +static usb_error_t +urtw_8225v2_rf_init(struct urtw_softc *sc) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int i; + uint16_t data; + uint32_t data32; + usb_error_t error; + + error = urtw_8180_set_anaparam(sc, URTW_8225_ANAPARAM_ON); + if (error) + goto fail; + + error = urtw_8225_usb_init(sc); + if (error) + goto fail; + + urtw_write32_m(sc, URTW_RF_TIMING, 0x000a8008); + urtw_read16_m(sc, URTW_BRSR, &data); /* XXX ??? */ + urtw_write16_m(sc, URTW_BRSR, 0xffff); + urtw_write32_m(sc, URTW_RF_PARA, 0x100044); + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + urtw_write8_m(sc, URTW_CONFIG3, 0x44); + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; + + error = urtw_8185_rf_pins_enable(sc); + if (error) + goto fail; + + usb_pause_mtx(&sc->sc_mtx, 500); + + for (i = 0; i < N(urtw_8225v2_rf_part1); i++) { + urtw_8225_write(sc, urtw_8225v2_rf_part1[i].reg, + urtw_8225v2_rf_part1[i].val); + } + usb_pause_mtx(&sc->sc_mtx, 50); + + urtw_8225_write(sc, + URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC1); + + for (i = 0; i < 95; i++) { + urtw_8225_write(sc, URTW_8225_ADDR_1_MAGIC, (uint8_t)(i + 1)); + urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, + urtw_8225v2_rxgain[i]); + } + + urtw_8225_write(sc, + URTW_8225_ADDR_3_MAGIC, URTW_8225_ADDR_3_DATA_MAGIC1); + urtw_8225_write(sc, + URTW_8225_ADDR_5_MAGIC, URTW_8225_ADDR_5_DATA_MAGIC1); + urtw_8225_write(sc, + URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC2); + urtw_8225_write(sc, + URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC1); + usb_pause_mtx(&sc->sc_mtx, 100); + urtw_8225_write(sc, + URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC2); + usb_pause_mtx(&sc->sc_mtx, 100); + + error = urtw_8225_read(sc, URTW_8225_ADDR_6_MAGIC, &data32); + if (error != 0) + goto fail; + if (data32 != URTW_8225_ADDR_6_DATA_MAGIC1) + device_printf(sc->sc_dev, "expect 0xe6!! (0x%x)\n", data32); + if (!(data32 & URTW_8225_ADDR_6_DATA_MAGIC2)) { + urtw_8225_write(sc, + URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC1); + usb_pause_mtx(&sc->sc_mtx, 100); + urtw_8225_write(sc, + URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC2); + usb_pause_mtx(&sc->sc_mtx, 50); + error = urtw_8225_read(sc, URTW_8225_ADDR_6_MAGIC, &data32); + if (error != 0) + goto fail; + if (!(data32 & URTW_8225_ADDR_6_DATA_MAGIC2)) + device_printf(sc->sc_dev, "RF calibration failed\n"); + } + usb_pause_mtx(&sc->sc_mtx, 100); + + urtw_8225_write(sc, + URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC6); + for (i = 0; i < 128; i++) { + urtw_8187_write_phy_ofdm(sc, 0xb, urtw_8225_agc[i]); + urtw_8187_write_phy_ofdm(sc, 0xa, (uint8_t)i + 0x80); + } + + for (i = 0; i < N(urtw_8225v2_rf_part2); i++) { + urtw_8187_write_phy_ofdm(sc, urtw_8225v2_rf_part2[i].reg, + urtw_8225v2_rf_part2[i].val); + } + + error = urtw_8225v2_setgain(sc, 4); + if (error) + goto fail; + + for (i = 0; i < N(urtw_8225v2_rf_part3); i++) { + urtw_8187_write_phy_cck(sc, urtw_8225v2_rf_part3[i].reg, + urtw_8225v2_rf_part3[i].val); + } + + urtw_write8_m(sc, URTW_TESTR, 0x0d); + + error = urtw_8225v2_set_txpwrlvl(sc, 1); + if (error) + goto fail; + + urtw_8187_write_phy_cck(sc, 0x10, 0x9b); + urtw_8187_write_phy_ofdm(sc, 0x26, 0x90); + + /* TX ant A, 0x0 for B */ + error = urtw_8185_tx_antenna(sc, 0x3); + if (error) + goto fail; + urtw_write32_m(sc, URTW_HSSI_PARA, 0x3dc00002); + + error = urtw_8225_rf_set_chan(sc, 1); +fail: + return (error); +#undef N +} + +static usb_error_t +urtw_8225v2_rf_set_chan(struct urtw_softc *sc, int chan) +{ + usb_error_t error; + + error = urtw_8225v2_set_txpwrlvl(sc, chan); + if (error) + goto fail; + + urtw_8225_write(sc, URTW_8225_ADDR_7_MAGIC, urtw_8225_channel[chan]); + usb_pause_mtx(&sc->sc_mtx, 10); +fail: + return (error); +} + +static usb_error_t +urtw_8225_read(struct urtw_softc *sc, uint8_t addr, uint32_t *data) +{ + int i; + int16_t bit; + uint8_t rlen = 12, wlen = 6; + uint16_t o1, o2, o3, tmp; + uint32_t d2w = ((uint32_t)(addr & 0x1f)) << 27; + uint32_t mask = 0x80000000, value = 0; + usb_error_t error; + + urtw_read16_m(sc, URTW_RF_PINS_OUTPUT, &o1); + urtw_read16_m(sc, URTW_RF_PINS_ENABLE, &o2); + urtw_read16_m(sc, URTW_RF_PINS_SELECT, &o3); + urtw_write16_m(sc, URTW_RF_PINS_ENABLE, o2 | URTW_RF_PINS_MAGIC4); + urtw_write16_m(sc, URTW_RF_PINS_SELECT, o3 | URTW_RF_PINS_MAGIC4); + o1 &= ~URTW_RF_PINS_MAGIC4; + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_EN); + DELAY(5); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1); + DELAY(5); + + for (i = 0; i < (wlen / 2); i++, mask = mask >> 1) { + bit = ((d2w & mask) != 0) ? 1 : 0; + + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | + URTW_BB_HOST_BANG_CLK); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | + URTW_BB_HOST_BANG_CLK); + DELAY(2); + mask = mask >> 1; + if (i == 2) + break; + bit = ((d2w & mask) != 0) ? 1 : 0; + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | + URTW_BB_HOST_BANG_CLK); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | + URTW_BB_HOST_BANG_CLK); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1); + DELAY(1); + } + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | URTW_BB_HOST_BANG_RW | + URTW_BB_HOST_BANG_CLK); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | URTW_BB_HOST_BANG_RW); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_RW); + DELAY(2); + + mask = 0x800; + for (i = 0; i < rlen; i++, mask = mask >> 1) { + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, + o1 | URTW_BB_HOST_BANG_RW); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, + o1 | URTW_BB_HOST_BANG_RW | URTW_BB_HOST_BANG_CLK); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, + o1 | URTW_BB_HOST_BANG_RW | URTW_BB_HOST_BANG_CLK); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, + o1 | URTW_BB_HOST_BANG_RW | URTW_BB_HOST_BANG_CLK); + DELAY(2); + + urtw_read16_m(sc, URTW_RF_PINS_INPUT, &tmp); + value |= ((tmp & URTW_BB_HOST_BANG_CLK) ? mask : 0); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, + o1 | URTW_BB_HOST_BANG_RW); + DELAY(2); + } + + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_EN | + URTW_BB_HOST_BANG_RW); + DELAY(2); + + urtw_write16_m(sc, URTW_RF_PINS_ENABLE, o2); + urtw_write16_m(sc, URTW_RF_PINS_SELECT, o3); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, URTW_RF_PINS_OUTPUT_MAGIC1); + + if (data != NULL) + *data = value; +fail: + return (error); +} + + +static usb_error_t +urtw_8225v2_set_txpwrlvl(struct urtw_softc *sc, int chan) +{ + int i; + uint8_t *cck_pwrtable; + uint8_t cck_pwrlvl_max = 15, ofdm_pwrlvl_max = 25, ofdm_pwrlvl_min = 10; + uint8_t cck_pwrlvl = sc->sc_txpwr_cck[chan] & 0xff; + uint8_t ofdm_pwrlvl = sc->sc_txpwr_ofdm[chan] & 0xff; + usb_error_t error; + + /* CCK power setting */ + cck_pwrlvl = (cck_pwrlvl > cck_pwrlvl_max) ? cck_pwrlvl_max : cck_pwrlvl; + cck_pwrlvl += sc->sc_txpwr_cck_base; + cck_pwrlvl = (cck_pwrlvl > 35) ? 35 : cck_pwrlvl; + cck_pwrtable = (chan == 14) ? urtw_8225v2_txpwr_cck_ch14 : + urtw_8225v2_txpwr_cck; + + for (i = 0; i < 8; i++) + urtw_8187_write_phy_cck(sc, 0x44 + i, cck_pwrtable[i]); + + urtw_write8_m(sc, URTW_TX_GAIN_CCK, + urtw_8225v2_tx_gain_cck_ofdm[cck_pwrlvl]); + usb_pause_mtx(&sc->sc_mtx, 1); + + /* OFDM power setting */ + ofdm_pwrlvl = (ofdm_pwrlvl > (ofdm_pwrlvl_max - ofdm_pwrlvl_min)) ? + ofdm_pwrlvl_max : ofdm_pwrlvl + ofdm_pwrlvl_min; + ofdm_pwrlvl += sc->sc_txpwr_ofdm_base; + ofdm_pwrlvl = (ofdm_pwrlvl > 35) ? 35 : ofdm_pwrlvl; + + error = urtw_8185_set_anaparam2(sc, URTW_8225_ANAPARAM2_ON); + if (error) + goto fail; + + urtw_8187_write_phy_ofdm(sc, 2, 0x42); + urtw_8187_write_phy_ofdm(sc, 5, 0x0); + urtw_8187_write_phy_ofdm(sc, 6, 0x40); + urtw_8187_write_phy_ofdm(sc, 7, 0x0); + urtw_8187_write_phy_ofdm(sc, 8, 0x40); + + urtw_write8_m(sc, URTW_TX_GAIN_OFDM, + urtw_8225v2_tx_gain_cck_ofdm[ofdm_pwrlvl]); + usb_pause_mtx(&sc->sc_mtx, 1); +fail: + return (error); +} + +static usb_error_t +urtw_8225v2_setgain(struct urtw_softc *sc, int16_t gain) +{ + uint8_t *gainp; + usb_error_t error; + + /* XXX for A? */ + gainp = urtw_8225v2_gain_bg; + urtw_8187_write_phy_ofdm(sc, 0x0d, gainp[gain * 3]); + usb_pause_mtx(&sc->sc_mtx, 1); + urtw_8187_write_phy_ofdm(sc, 0x1b, gainp[gain * 3 + 1]); + usb_pause_mtx(&sc->sc_mtx, 1); + urtw_8187_write_phy_ofdm(sc, 0x1d, gainp[gain * 3 + 2]); + usb_pause_mtx(&sc->sc_mtx, 1); + urtw_8187_write_phy_ofdm(sc, 0x21, 0x17); + usb_pause_mtx(&sc->sc_mtx, 1); +fail: + return (error); +} + +static usb_error_t +urtw_8225_isv2(struct urtw_softc *sc, int *ret) +{ + uint32_t data; + usb_error_t error; + + *ret = 1; + + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, URTW_RF_PINS_MAGIC5); + urtw_write16_m(sc, URTW_RF_PINS_SELECT, URTW_RF_PINS_MAGIC5); + urtw_write16_m(sc, URTW_RF_PINS_ENABLE, URTW_RF_PINS_MAGIC5); + usb_pause_mtx(&sc->sc_mtx, 500); + + urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, + URTW_8225_ADDR_0_DATA_MAGIC1); + + error = urtw_8225_read(sc, URTW_8225_ADDR_8_MAGIC, &data); + if (error != 0) + goto fail; + if (data != URTW_8225_ADDR_8_DATA_MAGIC1) + *ret = 0; + else { + error = urtw_8225_read(sc, URTW_8225_ADDR_9_MAGIC, &data); + if (error != 0) + goto fail; + if (data != URTW_8225_ADDR_9_DATA_MAGIC1) + *ret = 0; + } + + urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, + URTW_8225_ADDR_0_DATA_MAGIC2); +fail: + return (error); +} + +static usb_error_t +urtw_8225v2b_rf_init(struct urtw_softc *sc) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int i; + uint8_t data8; + usb_error_t error; + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + + /* + * initialize extra registers on 8187 + */ + urtw_write16_m(sc, URTW_BRSR_8187B, 0xfff); + + /* retry limit */ + urtw_read8_m(sc, URTW_CW_CONF, &data8); + data8 |= URTW_CW_CONF_PERPACKET_RETRY; + urtw_write8_m(sc, URTW_CW_CONF, data8); + + /* TX AGC */ + urtw_read8_m(sc, URTW_TX_AGC_CTL, &data8); + data8 |= URTW_TX_AGC_CTL_PERPACKET_GAIN; + urtw_write8_m(sc, URTW_TX_AGC_CTL, data8); + + /* Auto Rate Fallback Control */ +#define URTW_ARFR 0x1e0 + urtw_write16_m(sc, URTW_ARFR, 0xfff); + urtw_read8_m(sc, URTW_RATE_FALLBACK, &data8); + urtw_write8_m(sc, URTW_RATE_FALLBACK, + data8 | URTW_RATE_FALLBACK_ENABLE); + + urtw_read8_m(sc, URTW_MSR, &data8); + urtw_write8_m(sc, URTW_MSR, data8 & 0xf3); + urtw_read8_m(sc, URTW_MSR, &data8); + urtw_write8_m(sc, URTW_MSR, data8 | URTW_MSR_LINK_ENEDCA); + urtw_write8_m(sc, URTW_ACM_CONTROL, sc->sc_acmctl); + + urtw_write16_m(sc, URTW_ATIM_WND, 2); + urtw_write16_m(sc, URTW_BEACON_INTERVAL, 100); +#define URTW_FEMR_FOR_8187B 0x1d4 + urtw_write16_m(sc, URTW_FEMR_FOR_8187B, 0xffff); + + /* led type */ + urtw_read8_m(sc, URTW_CONFIG1, &data8); + data8 = (data8 & 0x3f) | 0x80; + urtw_write8_m(sc, URTW_CONFIG1, data8); + + /* applying MAC address again. */ + urtw_write32_m(sc, URTW_MAC0, ((uint32_t *)sc->sc_bssid)[0]); + urtw_write16_m(sc, URTW_MAC4, ((uint32_t *)sc->sc_bssid)[1] & 0xffff); + + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; + + urtw_write8_m(sc, URTW_WPA_CONFIG, 0); + + /* + * MAC configuration + */ + for (i = 0; i < N(urtw_8225v2b_rf_part1); i++) + urtw_write8_m(sc, urtw_8225v2b_rf_part1[i].reg, + urtw_8225v2b_rf_part1[i].val); + urtw_write16_m(sc, URTW_TID_AC_MAP, 0xfa50); + urtw_write16_m(sc, URTW_INT_MIG, 0x0000); + urtw_write32_m(sc, 0x1f0, 0); + urtw_write32_m(sc, 0x1f4, 0); + urtw_write8_m(sc, 0x1f8, 0); + urtw_write32_m(sc, URTW_RF_TIMING, 0x4001); + +#define URTW_RFSW_CTRL 0x272 + urtw_write16_m(sc, URTW_RFSW_CTRL, 0x569a); + + /* + * initialize PHY + */ + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + urtw_read8_m(sc, URTW_CONFIG3, &data8); + urtw_write8_m(sc, URTW_CONFIG3, + data8 | URTW_CONFIG3_ANAPARAM_WRITE); + + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; + + /* setup RFE initial timing */ + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, 0x0480); + urtw_write16_m(sc, URTW_RF_PINS_SELECT, 0x2488); + urtw_write16_m(sc, URTW_RF_PINS_ENABLE, 0x1fff); + usb_pause_mtx(&sc->sc_mtx, 1100); + + for (i = 0; i < N(urtw_8225v2b_rf_part0); i++) { + urtw_8225_write(sc, urtw_8225v2b_rf_part0[i].reg, + urtw_8225v2b_rf_part0[i].val); + usb_pause_mtx(&sc->sc_mtx, 1); + } + urtw_8225_write(sc, 0x00, 0x01b7); + + for (i = 0; i < 95; i++) { + urtw_8225_write(sc, URTW_8225_ADDR_1_MAGIC, (uint8_t)(i + 1)); + usb_pause_mtx(&sc->sc_mtx, 1); + urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, + urtw_8225v2b_rxgain[i]); + usb_pause_mtx(&sc->sc_mtx, 1); + } + + urtw_8225_write(sc, URTW_8225_ADDR_3_MAGIC, 0x080); + usb_pause_mtx(&sc->sc_mtx, 1); + urtw_8225_write(sc, URTW_8225_ADDR_5_MAGIC, 0x004); + usb_pause_mtx(&sc->sc_mtx, 1); + urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, 0x0b7); + usb_pause_mtx(&sc->sc_mtx, 1); + usb_pause_mtx(&sc->sc_mtx, 3000); + urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, 0xc4d); + usb_pause_mtx(&sc->sc_mtx, 2000); + urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, 0x44d); + usb_pause_mtx(&sc->sc_mtx, 1); + urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, 0x2bf); + usb_pause_mtx(&sc->sc_mtx, 1); + + urtw_write8_m(sc, URTW_TX_GAIN_CCK, 0x03); + urtw_write8_m(sc, URTW_TX_GAIN_OFDM, 0x07); + urtw_write8_m(sc, URTW_TX_ANTENNA, 0x03); + + urtw_8187_write_phy_ofdm(sc, 0x80, 0x12); + for (i = 0; i < 128; i++) { + uint32_t addr, data; + + data = (urtw_8225z2_agc[i] << 8) | 0x0000008f; + addr = ((i + 0x80) << 8) | 0x0000008e; + + urtw_8187_write_phy_ofdm(sc, data & 0x7f, (data >> 8) & 0xff); + urtw_8187_write_phy_ofdm(sc, addr & 0x7f, (addr >> 8) & 0xff); + urtw_8187_write_phy_ofdm(sc, 0x0e, 0x00); + } + urtw_8187_write_phy_ofdm(sc, 0x80, 0x10); + + for (i = 0; i < N(urtw_8225v2b_rf_part2); i++) + urtw_8187_write_phy_ofdm(sc, i, urtw_8225v2b_rf_part2[i].val); + + urtw_write32_m(sc, URTW_8187B_AC_VO, (7 << 12) | (3 << 8) | 0x1c); + urtw_write32_m(sc, URTW_8187B_AC_VI, (7 << 12) | (3 << 8) | 0x1c); + urtw_write32_m(sc, URTW_8187B_AC_BE, (7 << 12) | (3 << 8) | 0x1c); + urtw_write32_m(sc, URTW_8187B_AC_BK, (7 << 12) | (3 << 8) | 0x1c); + + urtw_8187_write_phy_ofdm(sc, 0x97, 0x46); + urtw_8187_write_phy_ofdm(sc, 0xa4, 0xb6); + urtw_8187_write_phy_ofdm(sc, 0x85, 0xfc); + urtw_8187_write_phy_cck(sc, 0xc1, 0x88); + +fail: + return (error); +#undef N +} + +static usb_error_t +urtw_8225v2b_rf_set_chan(struct urtw_softc *sc, int chan) +{ + usb_error_t error; + + error = urtw_8225v2b_set_txpwrlvl(sc, chan); + if (error) + goto fail; + + urtw_8225_write(sc, URTW_8225_ADDR_7_MAGIC, urtw_8225_channel[chan]); + usb_pause_mtx(&sc->sc_mtx, 10); +fail: + return (error); +} + +static usb_error_t +urtw_8225v2b_set_txpwrlvl(struct urtw_softc *sc, int chan) +{ + int i; + uint8_t *cck_pwrtable; + uint8_t cck_pwrlvl_max = 15; + uint8_t cck_pwrlvl = sc->sc_txpwr_cck[chan] & 0xff; + uint8_t ofdm_pwrlvl = sc->sc_txpwr_ofdm[chan] & 0xff; + usb_error_t error; + + /* CCK power setting */ + cck_pwrlvl = (cck_pwrlvl > cck_pwrlvl_max) ? + ((sc->sc_flags & URTW_RTL8187B_REV_B) ? cck_pwrlvl_max : 22) : + (cck_pwrlvl + ((sc->sc_flags & URTW_RTL8187B_REV_B) ? 0 : 7)); + cck_pwrlvl += sc->sc_txpwr_cck_base; + cck_pwrlvl = (cck_pwrlvl > 35) ? 35 : cck_pwrlvl; + cck_pwrtable = (chan == 14) ? urtw_8225v2b_txpwr_cck_ch14 : + urtw_8225v2b_txpwr_cck; + + if (sc->sc_flags & URTW_RTL8187B_REV_B) + cck_pwrtable += (cck_pwrlvl <= 6) ? 0 : + ((cck_pwrlvl <= 11) ? 8 : 16); + else + cck_pwrtable += (cck_pwrlvl <= 5) ? 0 : + ((cck_pwrlvl <= 11) ? 8 : ((cck_pwrlvl <= 17) ? 16 : 24)); + + for (i = 0; i < 8; i++) + urtw_8187_write_phy_cck(sc, 0x44 + i, cck_pwrtable[i]); + + urtw_write8_m(sc, URTW_TX_GAIN_CCK, + urtw_8225v2_tx_gain_cck_ofdm[cck_pwrlvl] << 1); + usb_pause_mtx(&sc->sc_mtx, 1); + + /* OFDM power setting */ + ofdm_pwrlvl = (ofdm_pwrlvl > 15) ? + ((sc->sc_flags & URTW_RTL8187B_REV_B) ? 17 : 25) : + (ofdm_pwrlvl + ((sc->sc_flags & URTW_RTL8187B_REV_B) ? 2 : 10)); + ofdm_pwrlvl += sc->sc_txpwr_ofdm_base; + ofdm_pwrlvl = (ofdm_pwrlvl > 35) ? 35 : ofdm_pwrlvl; + + urtw_write8_m(sc, URTW_TX_GAIN_OFDM, + urtw_8225v2_tx_gain_cck_ofdm[ofdm_pwrlvl] << 1); + + if (sc->sc_flags & URTW_RTL8187B_REV_B) { + if (ofdm_pwrlvl <= 11) { + urtw_8187_write_phy_ofdm(sc, 0x87, 0x60); + urtw_8187_write_phy_ofdm(sc, 0x89, 0x60); + } else { + urtw_8187_write_phy_ofdm(sc, 0x87, 0x5c); + urtw_8187_write_phy_ofdm(sc, 0x89, 0x5c); + } + } else { + if (ofdm_pwrlvl <= 11) { + urtw_8187_write_phy_ofdm(sc, 0x87, 0x5c); + urtw_8187_write_phy_ofdm(sc, 0x89, 0x5c); + } else if (ofdm_pwrlvl <= 17) { + urtw_8187_write_phy_ofdm(sc, 0x87, 0x54); + urtw_8187_write_phy_ofdm(sc, 0x89, 0x54); + } else { + urtw_8187_write_phy_ofdm(sc, 0x87, 0x50); + urtw_8187_write_phy_ofdm(sc, 0x89, 0x50); + } + } + usb_pause_mtx(&sc->sc_mtx, 1); +fail: + return (error); +} + +static usb_error_t +urtw_read8e(struct urtw_softc *sc, int val, uint8_t *data) +{ + struct usb_device_request req; + usb_error_t error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = URTW_8187_GETREGS_REQ; + USETW(req.wValue, val | 0xfe00); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(uint8_t)); + + error = urtw_do_request(sc, &req, data); + return (error); +} + +static usb_error_t +urtw_write8e(struct urtw_softc *sc, int val, uint8_t data) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = URTW_8187_SETREGS_REQ; + USETW(req.wValue, val | 0xfe00); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(uint8_t)); + + return (urtw_do_request(sc, &req, &data)); +} + +static usb_error_t +urtw_8180_set_anaparam(struct urtw_softc *sc, uint32_t val) +{ + uint8_t data; + usb_error_t error; + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + + urtw_read8_m(sc, URTW_CONFIG3, &data); + urtw_write8_m(sc, URTW_CONFIG3, data | URTW_CONFIG3_ANAPARAM_WRITE); + urtw_write32_m(sc, URTW_ANAPARAM, val); + urtw_read8_m(sc, URTW_CONFIG3, &data); + urtw_write8_m(sc, URTW_CONFIG3, data & ~URTW_CONFIG3_ANAPARAM_WRITE); + + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; +fail: + return (error); +} + +static usb_error_t +urtw_8185_set_anaparam2(struct urtw_softc *sc, uint32_t val) +{ + uint8_t data; + usb_error_t error; + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + + urtw_read8_m(sc, URTW_CONFIG3, &data); + urtw_write8_m(sc, URTW_CONFIG3, data | URTW_CONFIG3_ANAPARAM_WRITE); + urtw_write32_m(sc, URTW_ANAPARAM2, val); + urtw_read8_m(sc, URTW_CONFIG3, &data); + urtw_write8_m(sc, URTW_CONFIG3, data & ~URTW_CONFIG3_ANAPARAM_WRITE); + + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; +fail: + return (error); +} + +static usb_error_t +urtw_intr_enable(struct urtw_softc *sc) +{ + usb_error_t error; + + urtw_write16_m(sc, URTW_INTR_MASK, 0xffff); +fail: + return (error); +} + +static usb_error_t +urtw_intr_disable(struct urtw_softc *sc) +{ + usb_error_t error; + + urtw_write16_m(sc, URTW_INTR_MASK, 0); +fail: + return (error); +} + +static usb_error_t +urtw_reset(struct urtw_softc *sc) +{ + uint8_t data; + usb_error_t error; + + error = urtw_8180_set_anaparam(sc, URTW_8225_ANAPARAM_ON); + if (error) + goto fail; + error = urtw_8185_set_anaparam2(sc, URTW_8225_ANAPARAM2_ON); + if (error) + goto fail; + + error = urtw_intr_disable(sc); + if (error) + goto fail; + usb_pause_mtx(&sc->sc_mtx, 100); + + error = urtw_write8e(sc, 0x18, 0x10); + if (error != 0) + goto fail; + error = urtw_write8e(sc, 0x18, 0x11); + if (error != 0) + goto fail; + error = urtw_write8e(sc, 0x18, 0x00); + if (error != 0) + goto fail; + usb_pause_mtx(&sc->sc_mtx, 100); + + urtw_read8_m(sc, URTW_CMD, &data); + data = (data & 0x2) | URTW_CMD_RST; + urtw_write8_m(sc, URTW_CMD, data); + usb_pause_mtx(&sc->sc_mtx, 100); + + urtw_read8_m(sc, URTW_CMD, &data); + if (data & URTW_CMD_RST) { + device_printf(sc->sc_dev, "reset timeout\n"); + goto fail; + } + + error = urtw_set_mode(sc, URTW_EPROM_CMD_LOAD); + if (error) + goto fail; + usb_pause_mtx(&sc->sc_mtx, 100); + + error = urtw_8180_set_anaparam(sc, URTW_8225_ANAPARAM_ON); + if (error) + goto fail; + error = urtw_8185_set_anaparam2(sc, URTW_8225_ANAPARAM2_ON); + if (error) + goto fail; +fail: + return (error); +} + +static usb_error_t +urtw_led_ctl(struct urtw_softc *sc, int mode) +{ + usb_error_t error = 0; + + switch (sc->sc_strategy) { + case URTW_SW_LED_MODE0: + error = urtw_led_mode0(sc, mode); + break; + case URTW_SW_LED_MODE1: + error = urtw_led_mode1(sc, mode); + break; + case URTW_SW_LED_MODE2: + error = urtw_led_mode2(sc, mode); + break; + case URTW_SW_LED_MODE3: + error = urtw_led_mode3(sc, mode); + break; + default: + panic("unsupported LED mode %d\n", sc->sc_strategy); + /* never reach */ + } + + return (error); +} + +static usb_error_t +urtw_led_mode0(struct urtw_softc *sc, int mode) +{ + + switch (mode) { + case URTW_LED_CTL_POWER_ON: + sc->sc_gpio_ledstate = URTW_LED_POWER_ON_BLINK; + break; + case URTW_LED_CTL_TX: + if (sc->sc_gpio_ledinprogress == 1) + return (0); + + sc->sc_gpio_ledstate = URTW_LED_BLINK_NORMAL; + sc->sc_gpio_blinktime = 2; + break; + case URTW_LED_CTL_LINK: + sc->sc_gpio_ledstate = URTW_LED_ON; + break; + default: + panic("unsupported LED mode 0x%x", mode); + /* never reach */ + } + + switch (sc->sc_gpio_ledstate) { + case URTW_LED_ON: + if (sc->sc_gpio_ledinprogress != 0) + break; + urtw_led_on(sc, URTW_LED_GPIO); + break; + case URTW_LED_BLINK_NORMAL: + if (sc->sc_gpio_ledinprogress != 0) + break; + sc->sc_gpio_ledinprogress = 1; + sc->sc_gpio_blinkstate = (sc->sc_gpio_ledon != 0) ? + URTW_LED_OFF : URTW_LED_ON; + usb_callout_reset(&sc->sc_led_ch, hz, urtw_led_ch, sc); + break; + case URTW_LED_POWER_ON_BLINK: + urtw_led_on(sc, URTW_LED_GPIO); + usb_pause_mtx(&sc->sc_mtx, 100); + urtw_led_off(sc, URTW_LED_GPIO); + break; + default: + panic("unknown LED status 0x%x", sc->sc_gpio_ledstate); + /* never reach */ + } + return (0); +} + +static usb_error_t +urtw_led_mode1(struct urtw_softc *sc, int mode) +{ + + return (USB_ERR_INVAL); +} + +static usb_error_t +urtw_led_mode2(struct urtw_softc *sc, int mode) +{ + + return (USB_ERR_INVAL); +} + +static usb_error_t +urtw_led_mode3(struct urtw_softc *sc, int mode) +{ + + return (USB_ERR_INVAL); +} + +static usb_error_t +urtw_led_on(struct urtw_softc *sc, int type) +{ + usb_error_t error; + + if (type == URTW_LED_GPIO) { + switch (sc->sc_gpio_ledpin) { + case URTW_LED_PIN_GPIO0: + urtw_write8_m(sc, URTW_GPIO, 0x01); + urtw_write8_m(sc, URTW_GP_ENABLE, 0x00); + break; + default: + panic("unsupported LED PIN type 0x%x", + sc->sc_gpio_ledpin); + /* never reach */ + } + } else { + panic("unsupported LED type 0x%x", type); + /* never reach */ + } + + sc->sc_gpio_ledon = 1; +fail: + return (error); +} + +static usb_error_t +urtw_led_off(struct urtw_softc *sc, int type) +{ + usb_error_t error; + + if (type == URTW_LED_GPIO) { + switch (sc->sc_gpio_ledpin) { + case URTW_LED_PIN_GPIO0: + urtw_write8_m(sc, URTW_GPIO, URTW_GPIO_DATA_MAGIC1); + urtw_write8_m(sc, + URTW_GP_ENABLE, URTW_GP_ENABLE_DATA_MAGIC1); + break; + default: + panic("unsupported LED PIN type 0x%x", + sc->sc_gpio_ledpin); + /* never reach */ + } + } else { + panic("unsupported LED type 0x%x", type); + /* never reach */ + } + + sc->sc_gpio_ledon = 0; + +fail: + return (error); +} + +static void +urtw_led_ch(void *arg) +{ + struct urtw_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + ieee80211_runtask(ic, &sc->sc_led_task); +} + +static void +urtw_ledtask(void *arg, int pending) +{ + struct urtw_softc *sc = arg; + + if (sc->sc_strategy != URTW_SW_LED_MODE0) + panic("could not process a LED strategy 0x%x", sc->sc_strategy); + + URTW_LOCK(sc); + urtw_led_blink(sc); + URTW_UNLOCK(sc); +} + +static usb_error_t +urtw_led_blink(struct urtw_softc *sc) +{ + uint8_t ing = 0; + usb_error_t error; + + if (sc->sc_gpio_blinkstate == URTW_LED_ON) + error = urtw_led_on(sc, URTW_LED_GPIO); + else + error = urtw_led_off(sc, URTW_LED_GPIO); + sc->sc_gpio_blinktime--; + if (sc->sc_gpio_blinktime == 0) + ing = 1; + else { + if (sc->sc_gpio_ledstate != URTW_LED_BLINK_NORMAL && + sc->sc_gpio_ledstate != URTW_LED_BLINK_SLOWLY && + sc->sc_gpio_ledstate != URTW_LED_BLINK_CM3) + ing = 1; + } + if (ing == 1) { + if (sc->sc_gpio_ledstate == URTW_LED_ON && + sc->sc_gpio_ledon == 0) + error = urtw_led_on(sc, URTW_LED_GPIO); + else if (sc->sc_gpio_ledstate == URTW_LED_OFF && + sc->sc_gpio_ledon == 1) + error = urtw_led_off(sc, URTW_LED_GPIO); + + sc->sc_gpio_blinktime = 0; + sc->sc_gpio_ledinprogress = 0; + return (0); + } + + sc->sc_gpio_blinkstate = (sc->sc_gpio_blinkstate != URTW_LED_ON) ? + URTW_LED_ON : URTW_LED_OFF; + + switch (sc->sc_gpio_ledstate) { + case URTW_LED_BLINK_NORMAL: + usb_callout_reset(&sc->sc_led_ch, hz, urtw_led_ch, sc); + break; + default: + panic("unknown LED status 0x%x", sc->sc_gpio_ledstate); + /* never reach */ + } + return (0); +} + +static usb_error_t +urtw_rx_enable(struct urtw_softc *sc) +{ + uint8_t data; + usb_error_t error; + + usbd_transfer_start((sc->sc_flags & URTW_RTL8187B) ? + sc->sc_xfer[URTW_8187B_BULK_RX] : sc->sc_xfer[URTW_8187L_BULK_RX]); + + error = urtw_rx_setconf(sc); + if (error != 0) + goto fail; + + if ((sc->sc_flags & URTW_RTL8187B) == 0) { + urtw_read8_m(sc, URTW_CMD, &data); + urtw_write8_m(sc, URTW_CMD, data | URTW_CMD_RX_ENABLE); + } +fail: + return (error); +} + +static usb_error_t +urtw_tx_enable(struct urtw_softc *sc) +{ + uint8_t data8; + uint32_t data; + usb_error_t error; + + if (sc->sc_flags & URTW_RTL8187B) { + urtw_read32_m(sc, URTW_TX_CONF, &data); + data &= ~URTW_TX_LOOPBACK_MASK; + data &= ~(URTW_TX_DPRETRY_MASK | URTW_TX_RTSRETRY_MASK); + data &= ~(URTW_TX_NOCRC | URTW_TX_MXDMA_MASK); + data &= ~URTW_TX_SWPLCPLEN; + data |= URTW_TX_HW_SEQNUM | URTW_TX_DISREQQSIZE | + (7 << 8) | /* short retry limit */ + (7 << 0) | /* long retry limit */ + (7 << 21); /* MAX TX DMA */ + urtw_write32_m(sc, URTW_TX_CONF, data); + + urtw_read8_m(sc, URTW_MSR, &data8); + data8 |= URTW_MSR_LINK_ENEDCA; + urtw_write8_m(sc, URTW_MSR, data8); + return (error); + } + + urtw_read8_m(sc, URTW_CW_CONF, &data8); + data8 &= ~(URTW_CW_CONF_PERPACKET_CW | URTW_CW_CONF_PERPACKET_RETRY); + urtw_write8_m(sc, URTW_CW_CONF, data8); + + urtw_read8_m(sc, URTW_TX_AGC_CTL, &data8); + data8 &= ~URTW_TX_AGC_CTL_PERPACKET_GAIN; + data8 &= ~URTW_TX_AGC_CTL_PERPACKET_ANTSEL; + data8 &= ~URTW_TX_AGC_CTL_FEEDBACK_ANT; + urtw_write8_m(sc, URTW_TX_AGC_CTL, data8); + + urtw_read32_m(sc, URTW_TX_CONF, &data); + data &= ~URTW_TX_LOOPBACK_MASK; + data |= URTW_TX_LOOPBACK_NONE; + data &= ~(URTW_TX_DPRETRY_MASK | URTW_TX_RTSRETRY_MASK); + data |= sc->sc_tx_retry << URTW_TX_DPRETRY_SHIFT; + data |= sc->sc_rts_retry << URTW_TX_RTSRETRY_SHIFT; + data &= ~(URTW_TX_NOCRC | URTW_TX_MXDMA_MASK); + data |= URTW_TX_MXDMA_2048 | URTW_TX_CWMIN | URTW_TX_DISCW; + data &= ~URTW_TX_SWPLCPLEN; + data |= URTW_TX_NOICV; + urtw_write32_m(sc, URTW_TX_CONF, data); + + urtw_read8_m(sc, URTW_CMD, &data8); + urtw_write8_m(sc, URTW_CMD, data8 | URTW_CMD_TX_ENABLE); +fail: + return (error); +} + +static usb_error_t +urtw_rx_setconf(struct urtw_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t data; + usb_error_t error; + + urtw_read32_m(sc, URTW_RX, &data); + data = data &~ URTW_RX_FILTER_MASK; + if (sc->sc_flags & URTW_RTL8187B) { + data = data | URTW_RX_FILTER_MNG | URTW_RX_FILTER_DATA | + URTW_RX_FILTER_MCAST | URTW_RX_FILTER_BCAST | + URTW_RX_FILTER_NICMAC | URTW_RX_CHECK_BSSID | + URTW_RX_FIFO_THRESHOLD_NONE | + URTW_MAX_RX_DMA_2048 | + URTW_RX_AUTORESETPHY | URTW_RCR_ONLYERLPKT; + } else { + data = data | URTW_RX_FILTER_MNG | URTW_RX_FILTER_DATA; + data = data | URTW_RX_FILTER_BCAST | URTW_RX_FILTER_MCAST; + + if (ic->ic_opmode == IEEE80211_M_MONITOR) { + data = data | URTW_RX_FILTER_ICVERR; + data = data | URTW_RX_FILTER_PWR; + } + if (sc->sc_crcmon == 1 && ic->ic_opmode == IEEE80211_M_MONITOR) + data = data | URTW_RX_FILTER_CRCERR; + + if (ic->ic_opmode == IEEE80211_M_MONITOR || + (ifp->if_flags & (IFF_ALLMULTI | IFF_PROMISC))) { + data = data | URTW_RX_FILTER_ALLMAC; + } else { + data = data | URTW_RX_FILTER_NICMAC; + data = data | URTW_RX_CHECK_BSSID; + } + + data = data &~ URTW_RX_FIFO_THRESHOLD_MASK; + data = data | URTW_RX_FIFO_THRESHOLD_NONE | + URTW_RX_AUTORESETPHY; + data = data &~ URTW_MAX_RX_DMA_MASK; + data = data | URTW_MAX_RX_DMA_2048 | URTW_RCR_ONLYERLPKT; + } + + urtw_write32_m(sc, URTW_RX, data); +fail: + return (error); +} + +static struct mbuf * +urtw_rxeof(struct usb_xfer *xfer, struct urtw_data *data, int *rssi_p, + int8_t *nf_p) +{ + int actlen, flen, rssi; + struct ieee80211_frame *wh; + struct mbuf *m, *mnew; + struct urtw_softc *sc = data->sc; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint8_t noise = 0, rate; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + if (actlen < URTW_MIN_RXBUFSZ) { + ifp->if_ierrors++; + return (NULL); + } + + if (sc->sc_flags & URTW_RTL8187B) { + struct urtw_8187b_rxhdr *rx; + + rx = (struct urtw_8187b_rxhdr *)(data->buf + + (actlen - (sizeof(struct urtw_8187b_rxhdr)))); + flen = le32toh(rx->flag) & 0xfff; + if (flen > actlen) { + ifp->if_ierrors++; + return (NULL); + } + rate = (le32toh(rx->flag) >> URTW_RX_FLAG_RXRATE_SHIFT) & 0xf; + /* XXX correct? */ + rssi = rx->rssi & URTW_RX_RSSI_MASK; + noise = rx->noise; + } else { + struct urtw_8187l_rxhdr *rx; + + rx = (struct urtw_8187l_rxhdr *)(data->buf + + (actlen - (sizeof(struct urtw_8187l_rxhdr)))); + flen = le32toh(rx->flag) & 0xfff; + if (flen > actlen) { + ifp->if_ierrors++; + return (NULL); + } + + rate = (le32toh(rx->flag) >> URTW_RX_FLAG_RXRATE_SHIFT) & 0xf; + /* XXX correct? */ + rssi = rx->rssi & URTW_RX_8187L_RSSI_MASK; + noise = rx->noise; + } + + mnew = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (mnew == NULL) { + ifp->if_ierrors++; + return (NULL); + } + + m = data->m; + data->m = mnew; + data->buf = mtod(mnew, uint8_t *); + + /* finalize mbuf */ + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = flen - IEEE80211_CRC_LEN; + + if (ieee80211_radiotap_active(ic)) { + struct urtw_rx_radiotap_header *tap = &sc->sc_rxtap; + + /* XXX Are variables correct? */ + tap->wr_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wr_dbm_antsignal = (int8_t)rssi; + } + + wh = mtod(m, struct ieee80211_frame *); + if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_DATA) + sc->sc_currate = (rate > 0) ? rate : sc->sc_currate; + + *rssi_p = rssi; + *nf_p = noise; /* XXX correct? */ + + return (m); +} + +static void +urtw_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct urtw_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211_frame *wh; + struct ieee80211_node *ni; + struct mbuf *m = NULL; + struct urtw_data *data; + int8_t nf = -95; + int rssi = 1; + + URTW_ASSERT_LOCKED(sc); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + data = STAILQ_FIRST(&sc->sc_rx_active); + if (data == NULL) + goto setup; + STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); + m = urtw_rxeof(xfer, data, &rssi, &nf); + STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); + /* FALLTHROUGH */ + case USB_ST_SETUP: +setup: + data = STAILQ_FIRST(&sc->sc_rx_inactive); + if (data == NULL) { + KASSERT(m == NULL, ("mbuf isn't NULL")); + return; + } + STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next); + STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next); + usbd_xfer_set_frame_data(xfer, 0, data->buf, + usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + + /* + * To avoid LOR we should unlock our private mutex here to call + * ieee80211_input() because here is at the end of a USB + * callback and safe to unlock. + */ + URTW_UNLOCK(sc); + if (m != NULL) { + wh = mtod(m, struct ieee80211_frame *); + ni = ieee80211_find_rxnode(ic, + (struct ieee80211_frame_min *)wh); + if (ni != NULL) { + (void) ieee80211_input(ni, m, rssi, nf); + /* node is no longer needed */ + ieee80211_free_node(ni); + } else + (void) ieee80211_input_all(ic, m, rssi, nf); + m = NULL; + } + URTW_LOCK(sc); + break; + default: + /* needs it to the inactive queue due to a error. */ + data = STAILQ_FIRST(&sc->sc_rx_active); + if (data != NULL) { + STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); + STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); + } + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + ifp->if_ierrors++; + goto setup; + } + break; + } +} + +#define URTW_STATUS_TYPE_TXCLOSE 1 +#define URTW_STATUS_TYPE_BEACON_INTR 0 + +static void +urtw_txstatus_eof(struct usb_xfer *xfer) +{ + struct urtw_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + int actlen, type, pktretry, seq; + uint64_t val; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + if (actlen != sizeof(uint64_t)) + return; + + val = le64toh(sc->sc_txstatus); + type = (val >> 30) & 0x3; + if (type == URTW_STATUS_TYPE_TXCLOSE) { + pktretry = val & 0xff; + seq = (val >> 16) & 0xff; + if (pktretry == URTW_TX_MAXRETRY) + ifp->if_oerrors++; + DPRINTF(sc, URTW_DEBUG_TXSTATUS, "pktretry %d seq %#x\n", + pktretry, seq); + } +} + +static void +urtw_bulk_tx_status_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct urtw_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + + URTW_ASSERT_LOCKED(sc); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + urtw_txstatus_eof(xfer); + /* FALLTHROUGH */ + case USB_ST_SETUP: +setup: + usbd_xfer_set_frame_data(xfer, 0, &sc->sc_txstatus, + sizeof(int64_t)); + usbd_transfer_submit(xfer); + break; + default: + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + ifp->if_ierrors++; + goto setup; + } + break; + } +} + +static void +urtw_txeof(struct usb_xfer *xfer, struct urtw_data *data) +{ + struct urtw_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + + URTW_ASSERT_LOCKED(sc); + + /* + * Do any tx complete callback. Note this must be done before releasing + * the node reference. + */ + if (data->m) { + m = data->m; + if (m->m_flags & M_TXCB) { + /* XXX status? */ + ieee80211_process_callback(data->ni, m, 0); + } + m_freem(m); + data->m = NULL; + } + if (data->ni) { + ieee80211_free_node(data->ni); + data->ni = NULL; + } + sc->sc_txtimer = 0; + ifp->if_opackets++; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; +} + +static void +urtw_bulk_tx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct urtw_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct urtw_data *data; + + URTW_ASSERT_LOCKED(sc); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + data = STAILQ_FIRST(&sc->sc_tx_active); + if (data == NULL) + goto setup; + STAILQ_REMOVE_HEAD(&sc->sc_tx_active, next); + urtw_txeof(xfer, data); + STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next); + /* FALLTHROUGH */ + case USB_ST_SETUP: +setup: + data = STAILQ_FIRST(&sc->sc_tx_pending); + if (data == NULL) { + DPRINTF(sc, URTW_DEBUG_XMIT, + "%s: empty pending queue\n", __func__); + return; + } + STAILQ_REMOVE_HEAD(&sc->sc_tx_pending, next); + STAILQ_INSERT_TAIL(&sc->sc_tx_active, data, next); + + usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen); + usbd_transfer_submit(xfer); + + URTW_UNLOCK(sc); + urtw_start(ifp); + URTW_LOCK(sc); + break; + default: + data = STAILQ_FIRST(&sc->sc_tx_active); + if (data == NULL) + goto setup; + if (data->ni != NULL) { + ieee80211_free_node(data->ni); + data->ni = NULL; + ifp->if_oerrors++; + } + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto setup; + } + break; + } +} + +static struct urtw_data * +_urtw_getbuf(struct urtw_softc *sc) +{ + struct urtw_data *bf; + + bf = STAILQ_FIRST(&sc->sc_tx_inactive); + if (bf != NULL) + STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next); + else + bf = NULL; + if (bf == NULL) + DPRINTF(sc, URTW_DEBUG_XMIT, "%s: %s\n", __func__, + "out of xmit buffers"); + return (bf); +} + +static struct urtw_data * +urtw_getbuf(struct urtw_softc *sc) +{ + struct urtw_data *bf; + + URTW_ASSERT_LOCKED(sc); + + bf = _urtw_getbuf(sc); + if (bf == NULL) { + struct ifnet *ifp = sc->sc_ifp; + + DPRINTF(sc, URTW_DEBUG_XMIT, "%s: stop queue\n", __func__); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + } + return (bf); +} + +static int +urtw_isbmode(uint16_t rate) +{ + + return ((rate <= 22 && rate != 12 && rate != 18) || + rate == 44) ? (1) : (0); +} + +static uint16_t +urtw_rate2dbps(uint16_t rate) +{ + + switch(rate) { + case 12: + case 18: + case 24: + case 36: + case 48: + case 72: + case 96: + case 108: + return (rate * 2); + default: + break; + } + return (24); +} + +static int +urtw_compute_txtime(uint16_t framelen, uint16_t rate, + uint8_t ismgt, uint8_t isshort) +{ + uint16_t ceiling, frametime, n_dbps; + + if (urtw_isbmode(rate)) { + if (ismgt || !isshort || rate == 2) + frametime = (uint16_t)(144 + 48 + + (framelen * 8 / (rate / 2))); + else + frametime = (uint16_t)(72 + 24 + + (framelen * 8 / (rate / 2))); + if ((framelen * 8 % (rate / 2)) != 0) + frametime++; + } else { + n_dbps = urtw_rate2dbps(rate); + ceiling = (16 + 8 * framelen + 6) / n_dbps + + (((16 + 8 * framelen + 6) % n_dbps) ? 1 : 0); + frametime = (uint16_t)(16 + 4 + 4 * ceiling + 6); + } + return (frametime); +} + +/* + * Callback from the 802.11 layer to update the + * slot time based on the current setting. + */ +static void +urtw_updateslot(struct ifnet *ifp) +{ + struct urtw_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + + ieee80211_runtask(ic, &sc->sc_updateslot_task); +} + +static void +urtw_updateslottask(void *arg, int pending) +{ + struct urtw_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + int error; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + URTW_LOCK(sc); + if (sc->sc_flags & URTW_RTL8187B) { + urtw_write8_m(sc, URTW_SIFS, 0x22); + if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan)) + urtw_write8_m(sc, URTW_SLOT, 0x9); + else + urtw_write8_m(sc, URTW_SLOT, 0x14); + urtw_write8_m(sc, URTW_8187B_EIFS, 0x5b); + urtw_write8_m(sc, URTW_CARRIER_SCOUNT, 0x5b); + } else { + urtw_write8_m(sc, URTW_SIFS, 0x22); + if (sc->sc_state == IEEE80211_S_ASSOC && + ic->ic_flags & IEEE80211_F_SHSLOT) + urtw_write8_m(sc, URTW_SLOT, 0x9); + else + urtw_write8_m(sc, URTW_SLOT, 0x14); + if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan)) { + urtw_write8_m(sc, URTW_DIFS, 0x14); + urtw_write8_m(sc, URTW_EIFS, 0x5b - 0x14); + urtw_write8_m(sc, URTW_CW_VAL, 0x73); + } else { + urtw_write8_m(sc, URTW_DIFS, 0x24); + urtw_write8_m(sc, URTW_EIFS, 0x5b - 0x24); + urtw_write8_m(sc, URTW_CW_VAL, 0xa5); + } + } +fail: + URTW_UNLOCK(sc); +} + +static void +urtw_sysctl_node(struct urtw_softc *sc) +{ +#define URTW_SYSCTL_STAT_ADD32(c, h, n, p, d) \ + SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d) + struct sysctl_ctx_list *ctx; + struct sysctl_oid_list *child, *parent; + struct sysctl_oid *tree; + struct urtw_stats *stats = &sc->sc_stats; + + ctx = device_get_sysctl_ctx(sc->sc_dev); + child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)); + + tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats", CTLFLAG_RD, + NULL, "URTW statistics"); + parent = SYSCTL_CHILDREN(tree); + + /* Tx statistics. */ + tree = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "tx", CTLFLAG_RD, + NULL, "Tx MAC statistics"); + child = SYSCTL_CHILDREN(tree); + URTW_SYSCTL_STAT_ADD32(ctx, child, "1m", &stats->txrates[0], + "1 Mbit/s"); + URTW_SYSCTL_STAT_ADD32(ctx, child, "2m", &stats->txrates[1], + "2 Mbit/s"); + URTW_SYSCTL_STAT_ADD32(ctx, child, "5.5m", &stats->txrates[2], + "5.5 Mbit/s"); + URTW_SYSCTL_STAT_ADD32(ctx, child, "6m", &stats->txrates[4], + "6 Mbit/s"); + URTW_SYSCTL_STAT_ADD32(ctx, child, "9m", &stats->txrates[5], + "9 Mbit/s"); + URTW_SYSCTL_STAT_ADD32(ctx, child, "11m", &stats->txrates[3], + "11 Mbit/s"); + URTW_SYSCTL_STAT_ADD32(ctx, child, "12m", &stats->txrates[6], + "12 Mbit/s"); + URTW_SYSCTL_STAT_ADD32(ctx, child, "18m", &stats->txrates[7], + "18 Mbit/s"); + URTW_SYSCTL_STAT_ADD32(ctx, child, "24m", &stats->txrates[8], + "24 Mbit/s"); + URTW_SYSCTL_STAT_ADD32(ctx, child, "36m", &stats->txrates[9], + "36 Mbit/s"); + URTW_SYSCTL_STAT_ADD32(ctx, child, "48m", &stats->txrates[10], + "48 Mbit/s"); + URTW_SYSCTL_STAT_ADD32(ctx, child, "54m", &stats->txrates[11], + "54 Mbit/s"); +#undef URTW_SYSCTL_STAT_ADD32 +} + +static device_method_t urtw_methods[] = { + DEVMETHOD(device_probe, urtw_match), + DEVMETHOD(device_attach, urtw_attach), + DEVMETHOD(device_detach, urtw_detach), + { 0, 0 } +}; +static driver_t urtw_driver = { + "urtw", + urtw_methods, + sizeof(struct urtw_softc) +}; +static devclass_t urtw_devclass; + +DRIVER_MODULE(urtw, uhub, urtw_driver, urtw_devclass, NULL, 0); +MODULE_DEPEND(urtw, wlan, 1, 1, 1); +MODULE_DEPEND(urtw, usb, 1, 1, 1); +MODULE_VERSION(urtw, 1); diff --git a/sys/bus/u4b/wlan/if_urtwreg.h b/sys/bus/u4b/wlan/if_urtwreg.h new file mode 100644 index 0000000000..a0fe5ffd71 --- /dev/null +++ b/sys/bus/u4b/wlan/if_urtwreg.h @@ -0,0 +1,432 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2008 Weongyo Jeong + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define URTW_CONFIG_INDEX 0 +#define URTW_IFACE_INDEX 0 + +/* for 8187 */ +#define URTW_MAC0 0x0000 /* 1 byte */ +#define URTW_MAC1 0x0001 /* 1 byte */ +#define URTW_MAC2 0x0002 /* 1 byte */ +#define URTW_MAC3 0x0003 /* 1 byte */ +#define URTW_MAC4 0x0004 /* 1 byte */ +#define URTW_MAC5 0x0005 /* 1 byte */ +#define URTW_MAR 0x0008 /* 6 byte */ +#define URTW_RXFIFO_CNT 0x0010 /* 1 byte */ +#define URTW_TXFIFO_CNT 0x0012 /* 1 byte */ +#define URTW_BQREQ 0x0013 /* 1 byte */ +#define URTW_TSFT 0x0018 /* 6 byte */ +#define URTW_TLPDA 0x0020 /* 4 byte */ +#define URTW_TNPDA 0x0024 /* 4 byte */ +#define URTW_THPDA 0x0028 /* 4 byte */ +#define URTW_BRSR 0x002c /* 2 byte */ +#define URTW_BRSR_MBR_8185 (0x0fff) +#define URTW_8187B_EIFS 0x002d /* 1 byte for 8187B */ +#define URTW_BSSID 0x002e /* 6 byte */ +#define URTW_BRSR_8187B 0x0034 /* 2 byte for 8187B */ +#define URTW_RESP_RATE 0x0034 /* 1 byte for 8187L */ +#define URTW_RESP_MAX_RATE_SHIFT (4) +#define URTW_RESP_MIN_RATE_SHIFT (0) +#define URTW_EIFS 0x0035 /* 1 byte */ +#define URTW_CMD 0x0037 /* 1 byte */ +#define URTW_CMD_TX_ENABLE (0x4) +#define URTW_CMD_RX_ENABLE (0x8) +#define URTW_CMD_RST (0x10) +#define URTW_INTR_MASK 0x003c /* 2 byte */ +#define URTW_INTR_STATUS 0x003e /* 2 byte */ +#define URTW_TX_CONF 0x0040 /* 4 byte */ +#define URTW_TX_LOOPBACK_SHIFT (17) +#define URTW_TX_LOOPBACK_NONE (0 << URTW_TX_LOOPBACK_SHIFT) +#define URTW_TX_LOOPBACK_MAC (1 << URTW_TX_LOOPBACK_SHIFT) +#define URTW_TX_LOOPBACK_BASEBAND (2 << URTW_TX_LOOPBACK_SHIFT) +#define URTW_TX_LOOPBACK_CONTINUE (3 << URTW_TX_LOOPBACK_SHIFT) +#define URTW_TX_LOOPBACK_MASK (0x60000) +#define URTW_TX_DPRETRY_MASK (0xff00) +#define URTW_TX_RTSRETRY_MASK (0xff) +#define URTW_TX_DPRETRY_SHIFT (0) +#define URTW_TX_RTSRETRY_SHIFT (8) +#define URTW_TX_NOCRC (0x10000) +#define URTW_TX_MXDMA_MASK (0xe00000) +#define URTW_TX_MXDMA_1024 (6 << URTW_TX_MXDMA_SHIFT) +#define URTW_TX_MXDMA_2048 (7 << URTW_TX_MXDMA_SHIFT) +#define URTW_TX_MXDMA_SHIFT (21) +#define URTW_TX_DISCW (1 << 20) +#define URTW_TX_SWPLCPLEN (1 << 24) +#define URTW_TX_R8187vD (5 << 25) +#define URTW_TX_R8187vD_B (6 << 25) +#define URTW_TX_HWMASK (7 << 25) +#define URTW_TX_DISREQQSIZE (1 << 28) +#define URTW_TX_HW_SEQNUM (1 << 30) +#define URTW_TX_CWMIN (1 << 31) +#define URTW_TX_NOICV (0x80000) +#define URTW_RX 0x0044 /* 4 byte */ +#define URTW_RX_9356SEL (1 << 6) +#define URTW_RX_FILTER_MASK \ + (URTW_RX_FILTER_ALLMAC | URTW_RX_FILTER_NICMAC | URTW_RX_FILTER_MCAST | \ + URTW_RX_FILTER_BCAST | URTW_RX_FILTER_CRCERR | URTW_RX_FILTER_ICVERR | \ + URTW_RX_FILTER_DATA | URTW_RX_FILTER_CTL | URTW_RX_FILTER_MNG | \ + (1 << 21) | \ + URTW_RX_FILTER_PWR | URTW_RX_CHECK_BSSID) +#define URTW_RX_FILTER_ALLMAC (0x00000001) +#define URTW_RX_FILTER_NICMAC (0x00000002) +#define URTW_RX_FILTER_MCAST (0x00000004) +#define URTW_RX_FILTER_BCAST (0x00000008) +#define URTW_RX_FILTER_CRCERR (0x00000020) +#define URTW_RX_FILTER_ICVERR (0x00001000) +#define URTW_RX_FILTER_DATA (0x00040000) +#define URTW_RX_FILTER_CTL (0x00080000) +#define URTW_RX_FILTER_MNG (0x00100000) +#define URTW_RX_FILTER_PWR (0x00400000) +#define URTW_RX_CHECK_BSSID (0x00800000) +#define URTW_RX_FIFO_THRESHOLD_MASK ((1 << 13) | (1 << 14) | (1 << 15)) +#define URTW_RX_FIFO_THRESHOLD_SHIFT (13) +#define URTW_RX_FIFO_THRESHOLD_128 (3) +#define URTW_RX_FIFO_THRESHOLD_256 (4) +#define URTW_RX_FIFO_THRESHOLD_512 (5) +#define URTW_RX_FIFO_THRESHOLD_1024 (6) +#define URTW_RX_FIFO_THRESHOLD_NONE (7 << URTW_RX_FIFO_THRESHOLD_SHIFT) +#define URTW_RX_AUTORESETPHY (1 << URTW_RX_AUTORESETPHY_SHIFT) +#define URTW_RX_AUTORESETPHY_SHIFT (28) +#define URTW_MAX_RX_DMA_MASK ((1<<8) | (1<<9) | (1<<10)) +#define URTW_MAX_RX_DMA_2048 (7 << URTW_MAX_RX_DMA_SHIFT) +#define URTW_MAX_RX_DMA_1024 (6) +#define URTW_MAX_RX_DMA_SHIFT (10) +#define URTW_RCR_ONLYERLPKT (1 << 31) +#define URTW_INT_TIMEOUT 0x0048 /* 4 byte */ +#define URTW_INT_TBDA 0x004c /* 4 byte */ +#define URTW_EPROM_CMD 0x0050 /* 1 byte */ +#define URTW_EPROM_CMD_NORMAL (0x0) +#define URTW_EPROM_CMD_NORMAL_MODE \ + (URTW_EPROM_CMD_NORMAL << URTW_EPROM_CMD_SHIFT) +#define URTW_EPROM_CMD_LOAD (0x1) +#define URTW_EPROM_CMD_PROGRAM (0x2) +#define URTW_EPROM_CMD_PROGRAM_MODE \ + (URTW_EPROM_CMD_PROGRAM << URTW_EPROM_CMD_SHIFT) +#define URTW_EPROM_CMD_CONFIG (0x3) +#define URTW_EPROM_CMD_SHIFT (6) +#define URTW_EPROM_CMD_MASK ((1 << 7) | (1 << 6)) +#define URTW_EPROM_READBIT (0x1) +#define URTW_EPROM_WRITEBIT (0x2) +#define URTW_EPROM_CK (0x4) +#define URTW_EPROM_CS (0x8) +#define URTW_CONFIG0 0x0051 /* 1 byte */ +#define URTW_CONFIG1 0x0052 /* 1 byte */ +#define URTW_CONFIG2 0x0053 /* 1 byte */ +#define URTW_ANAPARAM 0x0054 /* 4 byte */ +#define URTW_8225_ANAPARAM_ON (0xa0000a59) +#define URTW_8225_ANAPARAM_OFF (0xa00beb59) +#define URTW_8187B_8225_ANAPARAM_ON (0x45090658) +#define URTW_8187B_8225_ANAPARAM_OFF (0x55480658) +#define URTW_MSR 0x0058 /* 1 byte */ +#define URTW_MSR_LINK_MASK ((1 << 2) | (1 << 3)) +#define URTW_MSR_LINK_SHIFT (2) +#define URTW_MSR_LINK_NONE (0 << URTW_MSR_LINK_SHIFT) +#define URTW_MSR_LINK_ADHOC (1 << URTW_MSR_LINK_SHIFT) +#define URTW_MSR_LINK_STA (2 << URTW_MSR_LINK_SHIFT) +#define URTW_MSR_LINK_HOSTAP (3 << URTW_MSR_LINK_SHIFT) +#define URTW_MSR_LINK_ENEDCA (1 << 4) +#define URTW_CONFIG3 0x0059 /* 1 byte */ +#define URTW_CONFIG3_ANAPARAM_WRITE (0x40) +#define URTW_CONFIG3_GNT_SELECT (0x80) +#define URTW_CONFIG3_ANAPARAM_W_SHIFT (6) +#define URTW_CONFIG4 0x005a /* 1 byte */ +#define URTW_CONFIG4_VCOOFF (1 << 7) +#define URTW_TESTR 0x005b /* 1 byte */ +#define URTW_PSR 0x005e /* 1 byte */ +#define URTW_SECURITY 0x005f /* 1 byte */ +#define URTW_ANAPARAM2 0x0060 /* 4 byte */ +#define URTW_8225_ANAPARAM2_ON (0x860c7312) +#define URTW_8225_ANAPARAM2_OFF (0x840dec11) +#define URTW_8187B_8225_ANAPARAM2_ON (0x727f3f52) +#define URTW_8187B_8225_ANAPARAM2_OFF (0x72003f50) +#define URTW_BEACON_INTERVAL 0x0070 /* 2 byte */ +#define URTW_ATIM_WND 0x0072 /* 2 byte */ +#define URTW_BEACON_INTERVAL_TIME 0x0074 /* 2 byte */ +#define URTW_ATIM_TR_ITV 0x0076 /* 2 byte */ +#define URTW_PHY_DELAY 0x0078 /* 1 byte */ +#define URTW_CARRIER_SCOUNT 0x0079 /* 1 byte */ +#define URTW_PHY_MAGIC1 0x007c /* 1 byte */ +#define URTW_PHY_MAGIC2 0x007d /* 1 byte */ +#define URTW_PHY_MAGIC3 0x007e /* 1 byte */ +#define URTW_PHY_MAGIC4 0x007f /* 1 byte */ +#define URTW_RF_PINS_OUTPUT 0x0080 /* 2 byte */ +#define URTW_RF_PINS_OUTPUT_MAGIC1 (0x3a0) +#define URTW_BB_HOST_BANG_CLK (1 << 1) +#define URTW_BB_HOST_BANG_EN (1 << 2) +#define URTW_BB_HOST_BANG_RW (1 << 3) +#define URTW_RF_PINS_ENABLE 0x0082 /* 2 byte */ +#define URTW_RF_PINS_SELECT 0x0084 /* 2 byte */ +#define URTW_ADDR_MAGIC1 0x0085 /* broken? */ +#define URTW_RF_PINS_INPUT 0x0086 /* 2 byte */ +#define URTW_RF_PINS_MAGIC1 (0xfff3) +#define URTW_RF_PINS_MAGIC2 (0xfff0) +#define URTW_RF_PINS_MAGIC3 (0x0007) +#define URTW_RF_PINS_MAGIC4 (0xf) +#define URTW_RF_PINS_MAGIC5 (0x0080) +#define URTW_RF_PARA 0x0088 /* 4 byte */ +#define URTW_RF_TIMING 0x008c /* 4 byte */ +#define URTW_GP_ENABLE 0x0090 /* 1 byte */ +#define URTW_GP_ENABLE_DATA_MAGIC1 (0x1) +#define URTW_GPIO 0x0091 /* 1 byte */ +#define URTW_GPIO_DATA_MAGIC1 (0x1) +#define URTW_HSSI_PARA 0x0094 /* 4 byte */ +#define URTW_TX_AGC_CTL 0x009c /* 1 byte */ +#define URTW_TX_AGC_CTL_PERPACKET_GAIN (0x1) +#define URTW_TX_AGC_CTL_PERPACKET_ANTSEL (0x2) +#define URTW_TX_AGC_CTL_FEEDBACK_ANT (0x4) +#define URTW_TX_GAIN_CCK 0x009d /* 1 byte */ +#define URTW_TX_GAIN_OFDM 0x009e /* 1 byte */ +#define URTW_TX_ANTENNA 0x009f /* 1 byte */ +#define URTW_WPA_CONFIG 0x00b0 /* 1 byte */ +#define URTW_SIFS 0x00b4 /* 1 byte */ +#define URTW_DIFS 0x00b5 /* 1 byte */ +#define URTW_SLOT 0x00b6 /* 1 byte */ +#define URTW_CW_CONF 0x00bc /* 1 byte */ +#define URTW_CW_CONF_PERPACKET_RETRY (0x2) +#define URTW_CW_CONF_PERPACKET_CW (0x1) +#define URTW_CW_VAL 0x00bd /* 1 byte */ +#define URTW_RATE_FALLBACK 0x00be /* 1 byte */ +#define URTW_RATE_FALLBACK_ENABLE (0x80) +#define URTW_ACM_CONTROL 0x00bf /* 1 byte */ +#define URTW_CONFIG5 0x00d8 /* 1 byte */ +#define URTW_TXDMA_POLLING 0x00d9 /* 1 byte */ +#define URTW_CWR 0x00dc /* 2 byte */ +#define URTW_RETRY_CTR 0x00de /* 1 byte */ +#define URTW_INT_MIG 0x00e2 /* 2 byte */ +#define URTW_RDSAR 0x00e4 /* 4 byte */ +#define URTW_TID_AC_MAP 0x00e8 /* 2 byte */ +#define URTW_ANAPARAM3 0x00ee /* 1 byte */ +#define URTW_8187B_8225_ANAPARAM3_ON (0x0) +#define URTW_8187B_8225_ANAPARAM3_OFF (0x0) +#define URTW_8187B_AC_VO 0x00f0 /* 4 byte for 8187B */ +#define URTW_FEMR 0x00f4 /* 2 byte */ +#define URTW_8187B_AC_VI 0x00f4 /* 4 byte for 8187B */ +#define URTW_8187B_AC_BE 0x00f8 /* 4 byte for 8187B */ +#define URTW_TALLY_CNT 0x00fa /* 2 byte */ +#define URTW_TALLY_SEL 0x00fc /* 1 byte */ +#define URTW_8187B_AC_BK 0x00fc /* 4 byte for 8187B */ +#define URTW_ADDR_MAGIC2 0x00fe /* 2 byte */ +#define URTW_ADDR_MAGIC3 0x00ff /* 1 byte */ + +/* for 8225 */ +#define URTW_8225_ADDR_0_MAGIC 0x0 +#define URTW_8225_ADDR_0_DATA_MAGIC1 (0x1b7) +#define URTW_8225_ADDR_0_DATA_MAGIC2 (0x0b7) +#define URTW_8225_ADDR_0_DATA_MAGIC3 (0x127) +#define URTW_8225_ADDR_0_DATA_MAGIC4 (0x027) +#define URTW_8225_ADDR_0_DATA_MAGIC5 (0x22f) +#define URTW_8225_ADDR_0_DATA_MAGIC6 (0x2bf) +#define URTW_8225_ADDR_1_MAGIC 0x1 +#define URTW_8225_ADDR_2_MAGIC 0x2 +#define URTW_8225_ADDR_2_DATA_MAGIC1 (0xc4d) +#define URTW_8225_ADDR_2_DATA_MAGIC2 (0x44d) +#define URTW_8225_ADDR_3_MAGIC 0x3 +#define URTW_8225_ADDR_3_DATA_MAGIC1 (0x2) +#define URTW_8225_ADDR_5_MAGIC 0x5 +#define URTW_8225_ADDR_5_DATA_MAGIC1 (0x4) +#define URTW_8225_ADDR_6_MAGIC 0x6 +#define URTW_8225_ADDR_6_DATA_MAGIC1 (0xe6) +#define URTW_8225_ADDR_6_DATA_MAGIC2 (0x80) +#define URTW_8225_ADDR_7_MAGIC 0x7 +#define URTW_8225_ADDR_8_MAGIC 0x8 +#define URTW_8225_ADDR_8_DATA_MAGIC1 (0x588) +#define URTW_8225_ADDR_9_MAGIC 0x9 +#define URTW_8225_ADDR_9_DATA_MAGIC1 (0x700) +#define URTW_8225_ADDR_C_MAGIC 0xc +#define URTW_8225_ADDR_C_DATA_MAGIC1 (0x850) +#define URTW_8225_ADDR_C_DATA_MAGIC2 (0x050) + +/* for EEPROM */ +#define URTW_EPROM_CHANPLAN 0x03 +#define URTW_EPROM_TXPW_BASE 0x05 +#define URTW_EPROM_RFCHIPID 0x06 +#define URTW_EPROM_RFCHIPID_RTL8225U (5) +#define URTW_EPROM_RFCHIPID_RTL8225Z2 (6) +#define URTW_EPROM_MACADDR 0x07 +#define URTW_EPROM_TXPW0 0x16 +#define URTW_EPROM_TXPW2 0x1b +#define URTW_EPROM_TXPW1 0x3d +#define URTW_EPROM_SWREV 0x3f +#define URTW_EPROM_CID_MASK (0xff) +#define URTW_EPROM_CID_RSVD0 (0x00) +#define URTW_EPROM_CID_RSVD1 (0xff) +#define URTW_EPROM_CID_ALPHA0 (0x01) +#define URTW_EPROM_CID_SERCOMM_PS (0x02) +#define URTW_EPROM_CID_HW_LED (0x03) + +/* LED */ +#define URTW_CID_DEFAULT 0 +#define URTW_CID_8187_ALPHA0 1 +#define URTW_CID_8187_SERCOMM_PS 2 +#define URTW_CID_8187_HW_LED 3 +#define URTW_SW_LED_MODE0 0 +#define URTW_SW_LED_MODE1 1 +#define URTW_SW_LED_MODE2 2 +#define URTW_SW_LED_MODE3 3 +#define URTW_HW_LED 4 +#define URTW_LED_CTL_POWER_ON 0 +#define URTW_LED_CTL_LINK 2 +#define URTW_LED_CTL_TX 4 +#define URTW_LED_PIN_GPIO0 0 +#define URTW_LED_PIN_LED0 1 +#define URTW_LED_PIN_LED1 2 +#define URTW_LED_UNKNOWN 0 +#define URTW_LED_ON 1 +#define URTW_LED_OFF 2 +#define URTW_LED_BLINK_NORMAL 3 +#define URTW_LED_BLINK_SLOWLY 4 +#define URTW_LED_POWER_ON_BLINK 5 +#define URTW_LED_SCAN_BLINK 6 +#define URTW_LED_NO_LINK_BLINK 7 +#define URTW_LED_BLINK_CM3 8 + +/* for extra area */ +#define URTW_EPROM_DISABLE 0 +#define URTW_EPROM_ENABLE 1 +#define URTW_EPROM_DELAY 10 +#define URTW_8187_GETREGS_REQ 5 +#define URTW_8187_SETREGS_REQ 5 +#define URTW_8225_RF_MAX_SENS 6 +#define URTW_8225_RF_DEF_SENS 4 +#define URTW_DEFAULT_RTS_RETRY 7 +#define URTW_DEFAULT_TX_RETRY 7 +#define URTW_DEFAULT_RTS_THRESHOLD 2342U + +#define URTW_ASIFS_TIME 10 +#define URTW_ACKCTS_LEN 14 /* len for ACK and CTS */ + +struct urtw_8187b_rxhdr { + uint32_t flag; +#define URTW_RX_FLAG_LEN /* 0 ~ 11 bits */ +#define URTW_RX_FLAG_ICV_ERR (1 << 12) +#define URTW_RX_FLAG_CRC32_ERR (1 << 13) +#define URTW_RX_FLAG_PM (1 << 14) +#define URTW_RX_FLAG_RX_ERR (1 << 15) +#define URTW_RX_FLAG_BCAST (1 << 16) +#define URTW_RX_FLAG_PAM (1 << 17) +#define URTW_RX_FLAG_MCAST (1 << 18) +#define URTW_RX_FLAG_QOS (1 << 19) /* only for RTL8187B */ +#define URTW_RX_FLAG_RXRATE /* 20 ~ 23 bits */ +#define URTW_RX_FLAG_RXRATE_SHIFT 20 +#define URTW_RX_FLAG_TRSW (1 << 24) /* only for RTL8187B */ +#define URTW_RX_FLAG_SPLCP (1 << 25) +#define URTW_RX_FLAG_FOF (1 << 26) +#define URTW_RX_FLAG_DMA_FAIL (1 << 27) +#define URTW_RX_FLAG_LAST (1 << 28) +#define URTW_RX_FLAG_FIRST (1 << 29) +#define URTW_RX_FLAG_EOR (1 << 30) +#define URTW_RX_FLAG_OWN (1 << 31) + uint64_t mactime; + uint8_t noise; + uint8_t rssi; +#define URTW_RX_RSSI /* 0 ~ 6 bits */ +#define URTW_RX_RSSI_MASK 0x3f +#define URTW_RX_ANTENNA (1 << 7) + uint8_t agc; + uint8_t flag2; +#define URTW_RX_FLAG2_DECRYPTED (1 << 0) +#define URTW_RX_FLAG2_WAKUP (1 << 1) +#define URTW_RX_FLAG2_SHIFT (1 << 2) +#define URTW_RX_FLAG2_RSVD0 /* 3 ~ 7 bits */ + uint16_t flag3; +#define URTW_RX_FLAG3_NUMMCSI /* 0 ~ 3 bits */ +#define URTW_RX_FLAG3_SNR_L2E /* 4 ~ 9 bits */ +#define URTW_RX_FLAG3_CFO_BIAS /* 10 ~ 15 bits */ + int8_t pwdb; + uint8_t fot; +} __packed; + +struct urtw_8187b_txhdr { + uint32_t flag; +#define URTW_TX_FLAG_PKTLEN /* 0 ~ 11 bits */ +#define URTW_TX_FLAG_RSVD0 /* 12 ~ 14 bits */ +#define URTW_TX_FLAG_NO_ENC (1 << 15) +#define URTW_TX_FLAG_SPLCP (1 << 16) +#define URTW_TX_FLAG_MOREFRAG (1 << 17) +#define URTW_TX_FLAG_CTS (1 << 18) +#define URTW_TX_FLAG_RTSRATE /* 19 ~ 22 bits */ +#define URTW_TX_FLAG_RTSRATE_SHIFT 19 +#define URTW_TX_FLAG_RTS (1 << 23) +#define URTW_TX_FLAG_TXRATE /* 24 ~ 27 bits */ +#define URTW_TX_FLAG_TXRATE_SHIFT 24 +#define URTW_TX_FLAG_LAST (1 << 28) +#define URTW_TX_FLAG_FIRST (1 << 29) +#define URTW_TX_FLAG_DMA (1 << 30) +#define URTW_TX_FLAG_OWN (1 << 31) + uint16_t rtsdur; + uint16_t len; +#define URTW_TX_LEN /* 0 ~ 14 bits */ +#define URTW_TX_LEN_EXT (1 << 15) + uint32_t bufaddr; + uint16_t flag1; +#define URTW_TX_FLAG1_RXLEN /* 0 ~ 11 bits */ +#define URTW_TX_FLAG1_RSVD0 /* 12 ~ 14 bits */ +#define URTW_TX_FLAG1_MICCAL (1 << 15) + uint16_t txdur; + uint32_t nextdescaddr; + uint8_t rtsagc; + uint8_t retry; + uint16_t flag2; +#define URTW_TX_FLAG2_RTDB (1 << 0) +#define URTW_TX_FLAG2_NOACM (1 << 1) +#define URTW_TX_FLAG2_PIFS (1 << 2) +#define URTW_TX_FLAG2_RSVD0 /* 3 ~ 6 bits */ +#define URTW_TX_FLAG2_RTSRATEFALLBACK /* 7 ~ 10 bits */ +#define URTW_TX_FLAG2_RATEFALLBACK /* 11 ~ 15 bits */ + uint16_t delaybound; + uint16_t flag3; +#define URTW_TX_FLAG3_RSVD0 /* 0 ~ 3 bits */ +#define URTW_TX_FLAG3_AGC /* 4 ~ 11 bits */ +#define URTW_TX_FLAG3_ANTENNA (1 << 12) +#define URTW_TX_FLAG3_SPC /* 13 ~ 14 bits */ +#define URTW_TX_FLAG3_RSVD1 (1 << 15) + uint32_t flag4; +#define URTW_TX_FLAG4_LENADJUST /* 0 ~ 1 bits */ +#define URTW_TX_FLAG4_RSVD0 (1 << 2) +#define URTW_TX_FLAG4_TPCDESEN (1 << 3) +#define URTW_TX_FLAG4_TPCPOLARITY /* 4 ~ 5 bits */ +#define URTW_TX_FLAG4_TPCEN (1 << 6) +#define URTW_TX_FLAG4_PTEN (1 << 7) +#define URTW_TX_FLAG4_BCKEY /* 8 ~ 13 bits */ +#define URTW_TX_FLAG4_ENBCKEY (1 << 14) +#define URTW_TX_FLAG4_ENPMPD (1 << 15) +#define URTW_TX_FLAG4_FRAGQSZ /* 16 ~ 31 bits */ +} __packed; + +struct urtw_8187l_rxhdr { + uint32_t flag; + uint8_t noise; + uint8_t rssi; +#define URTW_RX_8187L_RSSI /* 0 ~ 6 bits */ +#define URTW_RX_8187L_RSSI_MASK 0x3f +#define URTW_RX_8187L_ANTENNA (1 << 7) + uint8_t agc; + uint8_t flag2; +#define URTW_RX_8187L_DECRYPTED (1 << 0) +#define URTW_RX_8187L_WAKEUP (1 << 1) +#define URTW_RX_8187L_SHIFT (1 << 2) +#define URTW_RX_8187L_RSVD0 /* 3 ~ 7 bits */ + uint64_t mactime; +} __packed; + +struct urtw_8187l_txhdr { + uint32_t flag; + uint16_t rtsdur; + uint16_t len; + uint32_t retry; +} __packed; diff --git a/sys/bus/u4b/wlan/if_urtwvar.h b/sys/bus/u4b/wlan/if_urtwvar.h new file mode 100644 index 0000000000..e4a872147f --- /dev/null +++ b/sys/bus/u4b/wlan/if_urtwvar.h @@ -0,0 +1,186 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2008 Weongyo Jeong + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +enum { + URTW_8187B_BULK_RX, + URTW_8187B_BULK_TX_STATUS, + URTW_8187B_BULK_TX_BE, + URTW_8187B_BULK_TX_BK, + URTW_8187B_BULK_TX_VI, + URTW_8187B_BULK_TX_VO, + URTW_8187B_BULK_TX_EP12, + URTW_8187B_N_XFERS = 7 +}; + +enum { + URTW_8187L_BULK_RX, + URTW_8187L_BULK_TX_LOW, + URTW_8187L_BULK_TX_NORMAL, + URTW_8187L_N_XFERS = 3 +}; + +/* XXX no definition at net80211? */ +#define URTW_MAX_CHANNELS 15 + +struct urtw_data { + struct urtw_softc *sc; + uint8_t *buf; + uint16_t buflen; + struct mbuf *m; + struct ieee80211_node *ni; /* NB: tx only */ + STAILQ_ENTRY(urtw_data) next; +}; +typedef STAILQ_HEAD(, urtw_data) urtw_datahead; + +/* XXX not correct.. */ +#define URTW_MIN_RXBUFSZ \ + (sizeof(struct ieee80211_frame_min)) + +#define URTW_RX_DATA_LIST_COUNT 4 +#define URTW_TX_DATA_LIST_COUNT 16 +#define URTW_RX_MAXSIZE 0x9c4 +#define URTW_TX_MAXSIZE 0x9c4 +#define URTW_TX_MAXRETRY 11 + +struct urtw_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + int8_t wr_dbm_antsignal; +} __packed; + +#define URTW_RX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL)) + +struct urtw_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; +} __packed; + +#define URTW_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL)) + +struct urtw_stats { + unsigned int txrates[12]; +}; + +struct urtw_vap { + struct ieee80211vap vap; + int (*newstate)(struct ieee80211vap *, + enum ieee80211_state, int); +}; +#define URTW_VAP(vap) ((struct urtw_vap *)(vap)) + +struct urtw_softc { + struct ifnet *sc_ifp; + device_t sc_dev; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + int sc_debug; + int sc_if_flags; + int sc_flags; +#define URTW_INIT_ONCE (1 << 1) +#define URTW_RTL8187B (1 << 2) +#define URTW_RTL8187B_REV_B (1 << 3) +#define URTW_RTL8187B_REV_D (1 << 4) +#define URTW_RTL8187B_REV_E (1 << 5) + enum ieee80211_state sc_state; + + int sc_epromtype; +#define URTW_EEPROM_93C46 0 +#define URTW_EEPROM_93C56 1 + uint8_t sc_crcmon; + uint8_t sc_bssid[IEEE80211_ADDR_LEN]; + + struct ieee80211_channel *sc_curchan; + + /* for RF */ + usb_error_t (*sc_rf_init)(struct urtw_softc *); + usb_error_t (*sc_rf_set_chan)(struct urtw_softc *, + int); + usb_error_t (*sc_rf_set_sens)(struct urtw_softc *, + int); + usb_error_t (*sc_rf_stop)(struct urtw_softc *); + uint8_t sc_rfchip; + uint32_t sc_max_sens; + uint32_t sc_sens; + /* for LED */ + struct usb_callout sc_led_ch; + struct task sc_led_task; + uint8_t sc_psr; + uint8_t sc_strategy; +#define URTW_LED_GPIO 1 + uint8_t sc_gpio_ledon; + uint8_t sc_gpio_ledinprogress; + uint8_t sc_gpio_ledstate; + uint8_t sc_gpio_ledpin; + uint8_t sc_gpio_blinktime; + uint8_t sc_gpio_blinkstate; + /* RX/TX */ + struct usb_xfer *sc_xfer[URTW_8187B_N_XFERS]; +#define URTW_PRIORITY_LOW 0 +#define URTW_PRIORITY_NORMAL 1 +#define URTW_DATA_TIMEOUT 10000 /* 10 sec */ +#define URTW_8187B_TXPIPE_BE 0x6 /* best effort */ +#define URTW_8187B_TXPIPE_BK 0x7 /* background */ +#define URTW_8187B_TXPIPE_VI 0x5 /* video */ +#define URTW_8187B_TXPIPE_VO 0x4 /* voice */ +#define URTW_8187B_TXPIPE_MAX 4 + struct urtw_data sc_rx[URTW_RX_DATA_LIST_COUNT]; + urtw_datahead sc_rx_active; + urtw_datahead sc_rx_inactive; + struct urtw_data sc_tx[URTW_TX_DATA_LIST_COUNT]; + urtw_datahead sc_tx_active; + urtw_datahead sc_tx_inactive; + urtw_datahead sc_tx_pending; + uint8_t sc_rts_retry; + uint8_t sc_tx_retry; + uint8_t sc_preamble_mode; +#define URTW_PREAMBLE_MODE_SHORT 1 +#define URTW_PREAMBLE_MODE_LONG 2 + struct callout sc_watchdog_ch; + int sc_txtimer; + int sc_currate; + /* TX power */ + uint8_t sc_txpwr_cck[URTW_MAX_CHANNELS]; + uint8_t sc_txpwr_cck_base; + uint8_t sc_txpwr_ofdm[URTW_MAX_CHANNELS]; + uint8_t sc_txpwr_ofdm_base; + + uint8_t sc_acmctl; + uint64_t sc_txstatus; /* only for 8187B */ + struct task sc_updateslot_task; + + struct urtw_stats sc_stats; + + struct urtw_rx_radiotap_header sc_rxtap; + int sc_rxtap_len; + struct urtw_tx_radiotap_header sc_txtap; + int sc_txtap_len; +}; + +#define URTW_LOCK(sc) mtx_lock(&(sc)->sc_mtx) +#define URTW_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) +#define URTW_ASSERT_LOCKED(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED) diff --git a/sys/bus/u4b/wlan/if_zyd.c b/sys/bus/u4b/wlan/if_zyd.c new file mode 100644 index 0000000000..7e2cee9666 --- /dev/null +++ b/sys/bus/u4b/wlan/if_zyd.c @@ -0,0 +1,2946 @@ +/* $OpenBSD: if_zyd.c,v 1.52 2007/02/11 00:08:04 jsg Exp $ */ +/* $NetBSD: if_zyd.c,v 1.7 2007/06/21 04:04:29 kiyohara Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2006 by Damien Bergamini + * Copyright (c) 2006 by Florian Stoehr + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * ZyDAS ZD1211/ZD1211B USB WLAN driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef INET +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#include +#include + +#ifdef USB_DEBUG +static int zyd_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, zyd, CTLFLAG_RW, 0, "USB zyd"); +SYSCTL_INT(_hw_usb_zyd, OID_AUTO, debug, CTLFLAG_RW, &zyd_debug, 0, + "zyd debug level"); + +enum { + ZYD_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ + ZYD_DEBUG_RECV = 0x00000002, /* basic recv operation */ + ZYD_DEBUG_RESET = 0x00000004, /* reset processing */ + ZYD_DEBUG_INIT = 0x00000008, /* device init */ + ZYD_DEBUG_TX_PROC = 0x00000010, /* tx ISR proc */ + ZYD_DEBUG_RX_PROC = 0x00000020, /* rx ISR proc */ + ZYD_DEBUG_STATE = 0x00000040, /* 802.11 state transitions */ + ZYD_DEBUG_STAT = 0x00000080, /* statistic */ + ZYD_DEBUG_FW = 0x00000100, /* firmware */ + ZYD_DEBUG_CMD = 0x00000200, /* fw commands */ + ZYD_DEBUG_ANY = 0xffffffff +}; +#define DPRINTF(sc, m, fmt, ...) do { \ + if (zyd_debug & (m)) \ + printf("%s: " fmt, __func__, ## __VA_ARGS__); \ +} while (0) +#else +#define DPRINTF(sc, m, fmt, ...) do { \ + (void) sc; \ +} while (0) +#endif + +#define zyd_do_request(sc,req,data) \ + usbd_do_request_flags((sc)->sc_udev, &(sc)->sc_mtx, req, data, 0, NULL, 5000) + +static device_probe_t zyd_match; +static device_attach_t zyd_attach; +static device_detach_t zyd_detach; + +static usb_callback_t zyd_intr_read_callback; +static usb_callback_t zyd_intr_write_callback; +static usb_callback_t zyd_bulk_read_callback; +static usb_callback_t zyd_bulk_write_callback; + +static struct ieee80211vap *zyd_vap_create(struct ieee80211com *, + const char [IFNAMSIZ], int, enum ieee80211_opmode, int, + const uint8_t [IEEE80211_ADDR_LEN], + const uint8_t [IEEE80211_ADDR_LEN]); +static void zyd_vap_delete(struct ieee80211vap *); +static void zyd_tx_free(struct zyd_tx_data *, int); +static void zyd_setup_tx_list(struct zyd_softc *); +static void zyd_unsetup_tx_list(struct zyd_softc *); +static int zyd_newstate(struct ieee80211vap *, enum ieee80211_state, int); +static int zyd_cmd(struct zyd_softc *, uint16_t, const void *, int, + void *, int, int); +static int zyd_read16(struct zyd_softc *, uint16_t, uint16_t *); +static int zyd_read32(struct zyd_softc *, uint16_t, uint32_t *); +static int zyd_write16(struct zyd_softc *, uint16_t, uint16_t); +static int zyd_write32(struct zyd_softc *, uint16_t, uint32_t); +static int zyd_rfwrite(struct zyd_softc *, uint32_t); +static int zyd_lock_phy(struct zyd_softc *); +static int zyd_unlock_phy(struct zyd_softc *); +static int zyd_rf_attach(struct zyd_softc *, uint8_t); +static const char *zyd_rf_name(uint8_t); +static int zyd_hw_init(struct zyd_softc *); +static int zyd_read_pod(struct zyd_softc *); +static int zyd_read_eeprom(struct zyd_softc *); +static int zyd_get_macaddr(struct zyd_softc *); +static int zyd_set_macaddr(struct zyd_softc *, const uint8_t *); +static int zyd_set_bssid(struct zyd_softc *, const uint8_t *); +static int zyd_switch_radio(struct zyd_softc *, int); +static int zyd_set_led(struct zyd_softc *, int, int); +static void zyd_set_multi(struct zyd_softc *); +static void zyd_update_mcast(struct ifnet *); +static int zyd_set_rxfilter(struct zyd_softc *); +static void zyd_set_chan(struct zyd_softc *, struct ieee80211_channel *); +static int zyd_set_beacon_interval(struct zyd_softc *, int); +static void zyd_rx_data(struct usb_xfer *, int, uint16_t); +static int zyd_tx_start(struct zyd_softc *, struct mbuf *, + struct ieee80211_node *); +static void zyd_start(struct ifnet *); +static int zyd_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static int zyd_ioctl(struct ifnet *, u_long, caddr_t); +static void zyd_init_locked(struct zyd_softc *); +static void zyd_init(void *); +static void zyd_stop(struct zyd_softc *); +static int zyd_loadfirmware(struct zyd_softc *); +static void zyd_scan_start(struct ieee80211com *); +static void zyd_scan_end(struct ieee80211com *); +static void zyd_set_channel(struct ieee80211com *); +static int zyd_rfmd_init(struct zyd_rf *); +static int zyd_rfmd_switch_radio(struct zyd_rf *, int); +static int zyd_rfmd_set_channel(struct zyd_rf *, uint8_t); +static int zyd_al2230_init(struct zyd_rf *); +static int zyd_al2230_switch_radio(struct zyd_rf *, int); +static int zyd_al2230_set_channel(struct zyd_rf *, uint8_t); +static int zyd_al2230_set_channel_b(struct zyd_rf *, uint8_t); +static int zyd_al2230_init_b(struct zyd_rf *); +static int zyd_al7230B_init(struct zyd_rf *); +static int zyd_al7230B_switch_radio(struct zyd_rf *, int); +static int zyd_al7230B_set_channel(struct zyd_rf *, uint8_t); +static int zyd_al2210_init(struct zyd_rf *); +static int zyd_al2210_switch_radio(struct zyd_rf *, int); +static int zyd_al2210_set_channel(struct zyd_rf *, uint8_t); +static int zyd_gct_init(struct zyd_rf *); +static int zyd_gct_switch_radio(struct zyd_rf *, int); +static int zyd_gct_set_channel(struct zyd_rf *, uint8_t); +static int zyd_gct_mode(struct zyd_rf *); +static int zyd_gct_set_channel_synth(struct zyd_rf *, int, int); +static int zyd_gct_write(struct zyd_rf *, uint16_t); +static int zyd_gct_txgain(struct zyd_rf *, uint8_t); +static int zyd_maxim2_init(struct zyd_rf *); +static int zyd_maxim2_switch_radio(struct zyd_rf *, int); +static int zyd_maxim2_set_channel(struct zyd_rf *, uint8_t); + +static const struct zyd_phy_pair zyd_def_phy[] = ZYD_DEF_PHY; +static const struct zyd_phy_pair zyd_def_phyB[] = ZYD_DEF_PHYB; + +/* various supported device vendors/products */ +#define ZYD_ZD1211 0 +#define ZYD_ZD1211B 1 + +#define ZYD_ZD1211_DEV(v,p) \ + { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, ZYD_ZD1211) } +#define ZYD_ZD1211B_DEV(v,p) \ + { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, ZYD_ZD1211B) } +static const STRUCT_USB_HOST_ID zyd_devs[] = { + /* ZYD_ZD1211 */ + ZYD_ZD1211_DEV(3COM2, 3CRUSB10075), + ZYD_ZD1211_DEV(ABOCOM, WL54), + ZYD_ZD1211_DEV(ASUS, WL159G), + ZYD_ZD1211_DEV(CYBERTAN, TG54USB), + ZYD_ZD1211_DEV(DRAYTEK, VIGOR550), + ZYD_ZD1211_DEV(PLANEX2, GWUS54GD), + ZYD_ZD1211_DEV(PLANEX2, GWUS54GZL), + ZYD_ZD1211_DEV(PLANEX3, GWUS54GZ), + ZYD_ZD1211_DEV(PLANEX3, GWUS54MINI), + ZYD_ZD1211_DEV(SAGEM, XG760A), + ZYD_ZD1211_DEV(SENAO, NUB8301), + ZYD_ZD1211_DEV(SITECOMEU, WL113), + ZYD_ZD1211_DEV(SWEEX, ZD1211), + ZYD_ZD1211_DEV(TEKRAM, QUICKWLAN), + ZYD_ZD1211_DEV(TEKRAM, ZD1211_1), + ZYD_ZD1211_DEV(TEKRAM, ZD1211_2), + ZYD_ZD1211_DEV(TWINMOS, G240), + ZYD_ZD1211_DEV(UMEDIA, ALL0298V2), + ZYD_ZD1211_DEV(UMEDIA, TEW429UB_A), + ZYD_ZD1211_DEV(UMEDIA, TEW429UB), + ZYD_ZD1211_DEV(WISTRONNEWEB, UR055G), + ZYD_ZD1211_DEV(ZCOM, ZD1211), + ZYD_ZD1211_DEV(ZYDAS, ZD1211), + ZYD_ZD1211_DEV(ZYXEL, AG225H), + ZYD_ZD1211_DEV(ZYXEL, ZYAIRG220), + ZYD_ZD1211_DEV(ZYXEL, G200V2), + /* ZYD_ZD1211B */ + ZYD_ZD1211B_DEV(ACCTON, SMCWUSBG_NF), + ZYD_ZD1211B_DEV(ACCTON, SMCWUSBG), + ZYD_ZD1211B_DEV(ACCTON, ZD1211B), + ZYD_ZD1211B_DEV(ASUS, A9T_WIFI), + ZYD_ZD1211B_DEV(BELKIN, F5D7050_V4000), + ZYD_ZD1211B_DEV(BELKIN, ZD1211B), + ZYD_ZD1211B_DEV(CISCOLINKSYS, WUSBF54G), + ZYD_ZD1211B_DEV(FIBERLINE, WL430U), + ZYD_ZD1211B_DEV(MELCO, KG54L), + ZYD_ZD1211B_DEV(PHILIPS, SNU5600), + ZYD_ZD1211B_DEV(PLANEX2, GW_US54GXS), + ZYD_ZD1211B_DEV(SAGEM, XG76NA), + ZYD_ZD1211B_DEV(SITECOMEU, ZD1211B), + ZYD_ZD1211B_DEV(UMEDIA, TEW429UBC1), + ZYD_ZD1211B_DEV(USR, USR5423), + ZYD_ZD1211B_DEV(VTECH, ZD1211B), + ZYD_ZD1211B_DEV(ZCOM, ZD1211B), + ZYD_ZD1211B_DEV(ZYDAS, ZD1211B), + ZYD_ZD1211B_DEV(ZYXEL, M202), + ZYD_ZD1211B_DEV(ZYXEL, G202), + ZYD_ZD1211B_DEV(ZYXEL, G220V2) +}; + +static const struct usb_config zyd_config[ZYD_N_TRANSFER] = { + [ZYD_BULK_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = ZYD_MAX_TXBUFSZ, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = zyd_bulk_write_callback, + .ep_index = 0, + .timeout = 10000, /* 10 seconds */ + }, + [ZYD_BULK_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = ZYX_MAX_RXBUFSZ, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = zyd_bulk_read_callback, + .ep_index = 0, + }, + [ZYD_INTR_WR] = { + .type = UE_BULK_INTR, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = sizeof(struct zyd_cmd), + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = zyd_intr_write_callback, + .timeout = 1000, /* 1 second */ + .ep_index = 1, + }, + [ZYD_INTR_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = sizeof(struct zyd_cmd), + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = zyd_intr_read_callback, + }, +}; +#define zyd_read16_m(sc, val, data) do { \ + error = zyd_read16(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define zyd_write16_m(sc, val, data) do { \ + error = zyd_write16(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define zyd_read32_m(sc, val, data) do { \ + error = zyd_read32(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define zyd_write32_m(sc, val, data) do { \ + error = zyd_write32(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) + +static int +zyd_match(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != ZYD_CONFIG_INDEX) + return (ENXIO); + if (uaa->info.bIfaceIndex != ZYD_IFACE_INDEX) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(zyd_devs, sizeof(zyd_devs), uaa)); +} + +static int +zyd_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct zyd_softc *sc = device_get_softc(dev); + struct ifnet *ifp; + struct ieee80211com *ic; + uint8_t iface_index, bands; + int error; + + if (uaa->info.bcdDevice < 0x4330) { + device_printf(dev, "device version mismatch: 0x%X " + "(only >= 43.30 supported)\n", + uaa->info.bcdDevice); + return (EINVAL); + } + + device_set_usb_desc(dev); + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + sc->sc_macrev = USB_GET_DRIVER_INFO(uaa); + + mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), + MTX_NETWORK_LOCK, MTX_DEF); + STAILQ_INIT(&sc->sc_rqh); + + iface_index = ZYD_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, + &iface_index, sc->sc_xfer, zyd_config, + ZYD_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "could not allocate USB transfers, " + "err=%s\n", usbd_errstr(error)); + goto detach; + } + + ZYD_LOCK(sc); + if ((error = zyd_get_macaddr(sc)) != 0) { + device_printf(sc->sc_dev, "could not read EEPROM\n"); + ZYD_UNLOCK(sc); + goto detach; + } + ZYD_UNLOCK(sc); + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(sc->sc_dev, "can not if_alloc()\n"); + goto detach; + } + ifp->if_softc = sc; + if_initname(ifp, "zyd", device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_init = zyd_init; + ifp->if_ioctl = zyd_ioctl; + ifp->if_start = zyd_start; + IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); + IFQ_SET_READY(&ifp->if_snd); + + ic = ifp->if_l2com; + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + ic->ic_opmode = IEEE80211_M_STA; + + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA /* station mode */ + | IEEE80211_C_MONITOR /* monitor mode */ + | IEEE80211_C_SHPREAMBLE /* short preamble supported */ + | IEEE80211_C_SHSLOT /* short slot time supported */ + | IEEE80211_C_BGSCAN /* capable of bg scanning */ + | IEEE80211_C_WPA /* 802.11i */ + ; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + ieee80211_init_channels(ic, NULL, &bands); + + ieee80211_ifattach(ic, sc->sc_bssid); + ic->ic_raw_xmit = zyd_raw_xmit; + ic->ic_scan_start = zyd_scan_start; + ic->ic_scan_end = zyd_scan_end; + ic->ic_set_channel = zyd_set_channel; + + ic->ic_vap_create = zyd_vap_create; + ic->ic_vap_delete = zyd_vap_delete; + ic->ic_update_mcast = zyd_update_mcast; + ic->ic_update_promisc = zyd_update_mcast; + + ieee80211_radiotap_attach(ic, + &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), + ZYD_TX_RADIOTAP_PRESENT, + &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), + ZYD_RX_RADIOTAP_PRESENT); + + if (bootverbose) + ieee80211_announce(ic); + + return (0); + +detach: + zyd_detach(dev); + return (ENXIO); /* failure */ +} + +static int +zyd_detach(device_t dev) +{ + struct zyd_softc *sc = device_get_softc(dev); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic; + + /* stop all USB transfers */ + usbd_transfer_unsetup(sc->sc_xfer, ZYD_N_TRANSFER); + + /* free TX list, if any */ + zyd_unsetup_tx_list(sc); + + if (ifp) { + ic = ifp->if_l2com; + ieee80211_ifdetach(ic); + if_free(ifp); + } + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static struct ieee80211vap * +zyd_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, + enum ieee80211_opmode opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct zyd_vap *zvp; + struct ieee80211vap *vap; + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return (NULL); + zvp = (struct zyd_vap *) malloc(sizeof(struct zyd_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (zvp == NULL) + return (NULL); + vap = &zvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + zvp->newstate = vap->iv_newstate; + vap->iv_newstate = zyd_newstate; + + ieee80211_ratectl_init(vap); + ieee80211_ratectl_setinterval(vap, 1000 /* 1 sec */); + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, + ieee80211_media_status); + ic->ic_opmode = opmode; + return (vap); +} + +static void +zyd_vap_delete(struct ieee80211vap *vap) +{ + struct zyd_vap *zvp = ZYD_VAP(vap); + + ieee80211_ratectl_deinit(vap); + ieee80211_vap_detach(vap); + free(zvp, M_80211_VAP); +} + +static void +zyd_tx_free(struct zyd_tx_data *data, int txerr) +{ + struct zyd_softc *sc = data->sc; + + if (data->m != NULL) { + if (data->m->m_flags & M_TXCB) + ieee80211_process_callback(data->ni, data->m, + txerr ? ETIMEDOUT : 0); + m_freem(data->m); + data->m = NULL; + + ieee80211_free_node(data->ni); + data->ni = NULL; + } + STAILQ_INSERT_TAIL(&sc->tx_free, data, next); + sc->tx_nfree++; +} + +static void +zyd_setup_tx_list(struct zyd_softc *sc) +{ + struct zyd_tx_data *data; + int i; + + sc->tx_nfree = 0; + STAILQ_INIT(&sc->tx_q); + STAILQ_INIT(&sc->tx_free); + + for (i = 0; i < ZYD_TX_LIST_CNT; i++) { + data = &sc->tx_data[i]; + + data->sc = sc; + STAILQ_INSERT_TAIL(&sc->tx_free, data, next); + sc->tx_nfree++; + } +} + +static void +zyd_unsetup_tx_list(struct zyd_softc *sc) +{ + struct zyd_tx_data *data; + int i; + + /* make sure any subsequent use of the queues will fail */ + sc->tx_nfree = 0; + STAILQ_INIT(&sc->tx_q); + STAILQ_INIT(&sc->tx_free); + + /* free up all node references and mbufs */ + for (i = 0; i < ZYD_TX_LIST_CNT; i++) { + data = &sc->tx_data[i]; + + if (data->m != NULL) { + m_freem(data->m); + data->m = NULL; + } + if (data->ni != NULL) { + ieee80211_free_node(data->ni); + data->ni = NULL; + } + } +} + +static int +zyd_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct zyd_vap *zvp = ZYD_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + struct zyd_softc *sc = ic->ic_ifp->if_softc; + int error; + + DPRINTF(sc, ZYD_DEBUG_STATE, "%s: %s -> %s\n", __func__, + ieee80211_state_name[vap->iv_state], + ieee80211_state_name[nstate]); + + IEEE80211_UNLOCK(ic); + ZYD_LOCK(sc); + switch (nstate) { + case IEEE80211_S_AUTH: + zyd_set_chan(sc, ic->ic_curchan); + break; + case IEEE80211_S_RUN: + if (vap->iv_opmode == IEEE80211_M_MONITOR) + break; + + /* turn link LED on */ + error = zyd_set_led(sc, ZYD_LED1, 1); + if (error != 0) + break; + + /* make data LED blink upon Tx */ + zyd_write32_m(sc, sc->sc_fwbase + ZYD_FW_LINK_STATUS, 1); + + IEEE80211_ADDR_COPY(sc->sc_bssid, vap->iv_bss->ni_bssid); + zyd_set_bssid(sc, sc->sc_bssid); + break; + default: + break; + } +fail: + ZYD_UNLOCK(sc); + IEEE80211_LOCK(ic); + return (zvp->newstate(vap, nstate, arg)); +} + +/* + * Callback handler for interrupt transfer + */ +static void +zyd_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct zyd_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct ieee80211_node *ni; + struct zyd_cmd *cmd = &sc->sc_ibuf; + struct usb_page_cache *pc; + int datalen; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, cmd, sizeof(*cmd)); + + switch (le16toh(cmd->code)) { + case ZYD_NOTIF_RETRYSTATUS: + { + struct zyd_notif_retry *retry = + (struct zyd_notif_retry *)cmd->data; + + DPRINTF(sc, ZYD_DEBUG_TX_PROC, + "retry intr: rate=0x%x addr=%s count=%d (0x%x)\n", + le16toh(retry->rate), ether_sprintf(retry->macaddr), + le16toh(retry->count)&0xff, le16toh(retry->count)); + + /* + * Find the node to which the packet was sent and + * update its retry statistics. In BSS mode, this node + * is the AP we're associated to so no lookup is + * actually needed. + */ + ni = ieee80211_find_txnode(vap, retry->macaddr); + if (ni != NULL) { + int retrycnt = + (int)(le16toh(retry->count) & 0xff); + + ieee80211_ratectl_tx_complete(vap, ni, + IEEE80211_RATECTL_TX_FAILURE, + &retrycnt, NULL); + ieee80211_free_node(ni); + } + if (le16toh(retry->count) & 0x100) + ifp->if_oerrors++; /* too many retries */ + break; + } + case ZYD_NOTIF_IORD: + { + struct zyd_rq *rqp; + + if (le16toh(*(uint16_t *)cmd->data) == ZYD_CR_INTERRUPT) + break; /* HMAC interrupt */ + + datalen = actlen - sizeof(cmd->code); + datalen -= 2; /* XXX: padding? */ + + STAILQ_FOREACH(rqp, &sc->sc_rqh, rq) { + int i, cnt; + + if (rqp->olen != datalen) + continue; + cnt = rqp->olen / sizeof(struct zyd_pair); + for (i = 0; i < cnt; i++) { + if (*(((const uint16_t *)rqp->idata) + i) != + (((struct zyd_pair *)cmd->data) + i)->reg) + break; + } + if (i != cnt) + continue; + /* copy answer into caller-supplied buffer */ + memcpy(rqp->odata, cmd->data, rqp->olen); + DPRINTF(sc, ZYD_DEBUG_CMD, + "command %p complete, data = %*D \n", + rqp, rqp->olen, (char *)rqp->odata, ":"); + wakeup(rqp); /* wakeup caller */ + break; + } + if (rqp == NULL) { + device_printf(sc->sc_dev, + "unexpected IORD notification %*D\n", + datalen, cmd->data, ":"); + } + break; + } + default: + device_printf(sc->sc_dev, "unknown notification %x\n", + le16toh(cmd->code)); + } + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTF(sc, ZYD_DEBUG_CMD, "error = %s\n", + usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +zyd_intr_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct zyd_softc *sc = usbd_xfer_softc(xfer); + struct zyd_rq *rqp, *cmd; + struct usb_page_cache *pc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + cmd = usbd_xfer_get_priv(xfer); + DPRINTF(sc, ZYD_DEBUG_CMD, "command %p transferred\n", cmd); + STAILQ_FOREACH(rqp, &sc->sc_rqh, rq) { + /* Ensure the cached rq pointer is still valid */ + if (rqp == cmd && + (rqp->flags & ZYD_CMD_FLAG_READ) == 0) + wakeup(rqp); /* wakeup caller */ + } + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + STAILQ_FOREACH(rqp, &sc->sc_rqh, rq) { + if (rqp->flags & ZYD_CMD_FLAG_SENT) + continue; + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, rqp->cmd, rqp->ilen); + + usbd_xfer_set_frame_len(xfer, 0, rqp->ilen); + usbd_xfer_set_priv(xfer, rqp); + rqp->flags |= ZYD_CMD_FLAG_SENT; + usbd_transfer_submit(xfer); + break; + } + break; + + default: /* Error */ + DPRINTF(sc, ZYD_DEBUG_ANY, "error = %s\n", + usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static int +zyd_cmd(struct zyd_softc *sc, uint16_t code, const void *idata, int ilen, + void *odata, int olen, int flags) +{ + struct zyd_cmd cmd; + struct zyd_rq rq; + int error; + + if (ilen > sizeof(cmd.data)) + return (EINVAL); + + cmd.code = htole16(code); + memcpy(cmd.data, idata, ilen); + DPRINTF(sc, ZYD_DEBUG_CMD, "sending cmd %p = %*D\n", + &rq, ilen, idata, ":"); + + rq.cmd = &cmd; + rq.idata = idata; + rq.odata = odata; + rq.ilen = sizeof(uint16_t) + ilen; + rq.olen = olen; + rq.flags = flags; + STAILQ_INSERT_TAIL(&sc->sc_rqh, &rq, rq); + usbd_transfer_start(sc->sc_xfer[ZYD_INTR_RD]); + usbd_transfer_start(sc->sc_xfer[ZYD_INTR_WR]); + + /* wait at most one second for command reply */ + error = mtx_sleep(&rq, &sc->sc_mtx, 0 , "zydcmd", hz); + if (error) + device_printf(sc->sc_dev, "command timeout\n"); + STAILQ_REMOVE(&sc->sc_rqh, &rq, zyd_rq, rq); + DPRINTF(sc, ZYD_DEBUG_CMD, "finsihed cmd %p, error = %d \n", + &rq, error); + + return (error); +} + +static int +zyd_read16(struct zyd_softc *sc, uint16_t reg, uint16_t *val) +{ + struct zyd_pair tmp; + int error; + + reg = htole16(reg); + error = zyd_cmd(sc, ZYD_CMD_IORD, ®, sizeof(reg), &tmp, sizeof(tmp), + ZYD_CMD_FLAG_READ); + if (error == 0) + *val = le16toh(tmp.val); + return (error); +} + +static int +zyd_read32(struct zyd_softc *sc, uint16_t reg, uint32_t *val) +{ + struct zyd_pair tmp[2]; + uint16_t regs[2]; + int error; + + regs[0] = htole16(ZYD_REG32_HI(reg)); + regs[1] = htole16(ZYD_REG32_LO(reg)); + error = zyd_cmd(sc, ZYD_CMD_IORD, regs, sizeof(regs), tmp, sizeof(tmp), + ZYD_CMD_FLAG_READ); + if (error == 0) + *val = le16toh(tmp[0].val) << 16 | le16toh(tmp[1].val); + return (error); +} + +static int +zyd_write16(struct zyd_softc *sc, uint16_t reg, uint16_t val) +{ + struct zyd_pair pair; + + pair.reg = htole16(reg); + pair.val = htole16(val); + + return zyd_cmd(sc, ZYD_CMD_IOWR, &pair, sizeof(pair), NULL, 0, 0); +} + +static int +zyd_write32(struct zyd_softc *sc, uint16_t reg, uint32_t val) +{ + struct zyd_pair pair[2]; + + pair[0].reg = htole16(ZYD_REG32_HI(reg)); + pair[0].val = htole16(val >> 16); + pair[1].reg = htole16(ZYD_REG32_LO(reg)); + pair[1].val = htole16(val & 0xffff); + + return zyd_cmd(sc, ZYD_CMD_IOWR, pair, sizeof(pair), NULL, 0, 0); +} + +static int +zyd_rfwrite(struct zyd_softc *sc, uint32_t val) +{ + struct zyd_rf *rf = &sc->sc_rf; + struct zyd_rfwrite_cmd req; + uint16_t cr203; + int error, i; + + zyd_read16_m(sc, ZYD_CR203, &cr203); + cr203 &= ~(ZYD_RF_IF_LE | ZYD_RF_CLK | ZYD_RF_DATA); + + req.code = htole16(2); + req.width = htole16(rf->width); + for (i = 0; i < rf->width; i++) { + req.bit[i] = htole16(cr203); + if (val & (1 << (rf->width - 1 - i))) + req.bit[i] |= htole16(ZYD_RF_DATA); + } + error = zyd_cmd(sc, ZYD_CMD_RFCFG, &req, 4 + 2 * rf->width, NULL, 0, 0); +fail: + return (error); +} + +static int +zyd_rfwrite_cr(struct zyd_softc *sc, uint32_t val) +{ + int error; + + zyd_write16_m(sc, ZYD_CR244, (val >> 16) & 0xff); + zyd_write16_m(sc, ZYD_CR243, (val >> 8) & 0xff); + zyd_write16_m(sc, ZYD_CR242, (val >> 0) & 0xff); +fail: + return (error); +} + +static int +zyd_lock_phy(struct zyd_softc *sc) +{ + int error; + uint32_t tmp; + + zyd_read32_m(sc, ZYD_MAC_MISC, &tmp); + tmp &= ~ZYD_UNLOCK_PHY_REGS; + zyd_write32_m(sc, ZYD_MAC_MISC, tmp); +fail: + return (error); +} + +static int +zyd_unlock_phy(struct zyd_softc *sc) +{ + int error; + uint32_t tmp; + + zyd_read32_m(sc, ZYD_MAC_MISC, &tmp); + tmp |= ZYD_UNLOCK_PHY_REGS; + zyd_write32_m(sc, ZYD_MAC_MISC, tmp); +fail: + return (error); +} + +/* + * RFMD RF methods. + */ +static int +zyd_rfmd_init(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_RFMD_PHY; + static const uint32_t rfini[] = ZYD_RFMD_RF; + int i, error; + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) { + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + } + + /* init RFMD radio */ + for (i = 0; i < N(rfini); i++) { + if ((error = zyd_rfwrite(sc, rfini[i])) != 0) + return (error); + } +fail: + return (error); +#undef N +} + +static int +zyd_rfmd_switch_radio(struct zyd_rf *rf, int on) +{ + int error; + struct zyd_softc *sc = rf->rf_sc; + + zyd_write16_m(sc, ZYD_CR10, on ? 0x89 : 0x15); + zyd_write16_m(sc, ZYD_CR11, on ? 0x00 : 0x81); +fail: + return (error); +} + +static int +zyd_rfmd_set_channel(struct zyd_rf *rf, uint8_t chan) +{ + int error; + struct zyd_softc *sc = rf->rf_sc; + static const struct { + uint32_t r1, r2; + } rfprog[] = ZYD_RFMD_CHANTABLE; + + error = zyd_rfwrite(sc, rfprog[chan - 1].r1); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, rfprog[chan - 1].r2); + if (error != 0) + goto fail; + +fail: + return (error); +} + +/* + * AL2230 RF methods. + */ +static int +zyd_al2230_init(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_AL2230_PHY; + static const struct zyd_phy_pair phy2230s[] = ZYD_AL2230S_PHY_INIT; + static const struct zyd_phy_pair phypll[] = { + { ZYD_CR251, 0x2f }, { ZYD_CR251, 0x3f }, + { ZYD_CR138, 0x28 }, { ZYD_CR203, 0x06 } + }; + static const uint32_t rfini1[] = ZYD_AL2230_RF_PART1; + static const uint32_t rfini2[] = ZYD_AL2230_RF_PART2; + static const uint32_t rfini3[] = ZYD_AL2230_RF_PART3; + int i, error; + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + if (sc->sc_rfrev == ZYD_RF_AL2230S || sc->sc_al2230s != 0) { + for (i = 0; i < N(phy2230s); i++) + zyd_write16_m(sc, phy2230s[i].reg, phy2230s[i].val); + } + + /* init AL2230 radio */ + for (i = 0; i < N(rfini1); i++) { + error = zyd_rfwrite(sc, rfini1[i]); + if (error != 0) + goto fail; + } + + if (sc->sc_rfrev == ZYD_RF_AL2230S || sc->sc_al2230s != 0) + error = zyd_rfwrite(sc, 0x000824); + else + error = zyd_rfwrite(sc, 0x0005a4); + if (error != 0) + goto fail; + + for (i = 0; i < N(rfini2); i++) { + error = zyd_rfwrite(sc, rfini2[i]); + if (error != 0) + goto fail; + } + + for (i = 0; i < N(phypll); i++) + zyd_write16_m(sc, phypll[i].reg, phypll[i].val); + + for (i = 0; i < N(rfini3); i++) { + error = zyd_rfwrite(sc, rfini3[i]); + if (error != 0) + goto fail; + } +fail: + return (error); +#undef N +} + +static int +zyd_al2230_fini(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int error, i; + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phy[] = ZYD_AL2230_PHY_FINI_PART1; + + for (i = 0; i < N(phy); i++) + zyd_write16_m(sc, phy[i].reg, phy[i].val); + + if (sc->sc_newphy != 0) + zyd_write16_m(sc, ZYD_CR9, 0xe1); + + zyd_write16_m(sc, ZYD_CR203, 0x6); +fail: + return (error); +#undef N +} + +static int +zyd_al2230_init_b(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phy1[] = ZYD_AL2230_PHY_PART1; + static const struct zyd_phy_pair phy2[] = ZYD_AL2230_PHY_PART2; + static const struct zyd_phy_pair phy3[] = ZYD_AL2230_PHY_PART3; + static const struct zyd_phy_pair phy2230s[] = ZYD_AL2230S_PHY_INIT; + static const struct zyd_phy_pair phyini[] = ZYD_AL2230_PHY_B; + static const uint32_t rfini_part1[] = ZYD_AL2230_RF_B_PART1; + static const uint32_t rfini_part2[] = ZYD_AL2230_RF_B_PART2; + static const uint32_t rfini_part3[] = ZYD_AL2230_RF_B_PART3; + static const uint32_t zyd_al2230_chtable[][3] = ZYD_AL2230_CHANTABLE; + int i, error; + + for (i = 0; i < N(phy1); i++) + zyd_write16_m(sc, phy1[i].reg, phy1[i].val); + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + if (sc->sc_rfrev == ZYD_RF_AL2230S || sc->sc_al2230s != 0) { + for (i = 0; i < N(phy2230s); i++) + zyd_write16_m(sc, phy2230s[i].reg, phy2230s[i].val); + } + + for (i = 0; i < 3; i++) { + error = zyd_rfwrite_cr(sc, zyd_al2230_chtable[0][i]); + if (error != 0) + return (error); + } + + for (i = 0; i < N(rfini_part1); i++) { + error = zyd_rfwrite_cr(sc, rfini_part1[i]); + if (error != 0) + return (error); + } + + if (sc->sc_rfrev == ZYD_RF_AL2230S || sc->sc_al2230s != 0) + error = zyd_rfwrite(sc, 0x241000); + else + error = zyd_rfwrite(sc, 0x25a000); + if (error != 0) + goto fail; + + for (i = 0; i < N(rfini_part2); i++) { + error = zyd_rfwrite_cr(sc, rfini_part2[i]); + if (error != 0) + return (error); + } + + for (i = 0; i < N(phy2); i++) + zyd_write16_m(sc, phy2[i].reg, phy2[i].val); + + for (i = 0; i < N(rfini_part3); i++) { + error = zyd_rfwrite_cr(sc, rfini_part3[i]); + if (error != 0) + return (error); + } + + for (i = 0; i < N(phy3); i++) + zyd_write16_m(sc, phy3[i].reg, phy3[i].val); + + error = zyd_al2230_fini(rf); +fail: + return (error); +#undef N +} + +static int +zyd_al2230_switch_radio(struct zyd_rf *rf, int on) +{ + struct zyd_softc *sc = rf->rf_sc; + int error, on251 = (sc->sc_macrev == ZYD_ZD1211) ? 0x3f : 0x7f; + + zyd_write16_m(sc, ZYD_CR11, on ? 0x00 : 0x04); + zyd_write16_m(sc, ZYD_CR251, on ? on251 : 0x2f); +fail: + return (error); +} + +static int +zyd_al2230_set_channel(struct zyd_rf *rf, uint8_t chan) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int error, i; + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phy1[] = { + { ZYD_CR138, 0x28 }, { ZYD_CR203, 0x06 }, + }; + static const struct { + uint32_t r1, r2, r3; + } rfprog[] = ZYD_AL2230_CHANTABLE; + + error = zyd_rfwrite(sc, rfprog[chan - 1].r1); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, rfprog[chan - 1].r2); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, rfprog[chan - 1].r3); + if (error != 0) + goto fail; + + for (i = 0; i < N(phy1); i++) + zyd_write16_m(sc, phy1[i].reg, phy1[i].val); +fail: + return (error); +#undef N +} + +static int +zyd_al2230_set_channel_b(struct zyd_rf *rf, uint8_t chan) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int error, i; + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phy1[] = ZYD_AL2230_PHY_PART1; + static const struct { + uint32_t r1, r2, r3; + } rfprog[] = ZYD_AL2230_CHANTABLE_B; + + for (i = 0; i < N(phy1); i++) + zyd_write16_m(sc, phy1[i].reg, phy1[i].val); + + error = zyd_rfwrite_cr(sc, rfprog[chan - 1].r1); + if (error != 0) + goto fail; + error = zyd_rfwrite_cr(sc, rfprog[chan - 1].r2); + if (error != 0) + goto fail; + error = zyd_rfwrite_cr(sc, rfprog[chan - 1].r3); + if (error != 0) + goto fail; + error = zyd_al2230_fini(rf); +fail: + return (error); +#undef N +} + +#define ZYD_AL2230_PHY_BANDEDGE6 \ +{ \ + { ZYD_CR128, 0x14 }, { ZYD_CR129, 0x12 }, { ZYD_CR130, 0x10 }, \ + { ZYD_CR47, 0x1e } \ +} + +static int +zyd_al2230_bandedge6(struct zyd_rf *rf, struct ieee80211_channel *c) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int error = 0, i; + struct zyd_softc *sc = rf->rf_sc; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct zyd_phy_pair r[] = ZYD_AL2230_PHY_BANDEDGE6; + int chan = ieee80211_chan2ieee(ic, c); + + if (chan == 1 || chan == 11) + r[0].val = 0x12; + + for (i = 0; i < N(r); i++) + zyd_write16_m(sc, r[i].reg, r[i].val); +fail: + return (error); +#undef N +} + +/* + * AL7230B RF methods. + */ +static int +zyd_al7230B_init(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini_1[] = ZYD_AL7230B_PHY_1; + static const struct zyd_phy_pair phyini_2[] = ZYD_AL7230B_PHY_2; + static const struct zyd_phy_pair phyini_3[] = ZYD_AL7230B_PHY_3; + static const uint32_t rfini_1[] = ZYD_AL7230B_RF_1; + static const uint32_t rfini_2[] = ZYD_AL7230B_RF_2; + int i, error; + + /* for AL7230B, PHY and RF need to be initialized in "phases" */ + + /* init RF-dependent PHY registers, part one */ + for (i = 0; i < N(phyini_1); i++) + zyd_write16_m(sc, phyini_1[i].reg, phyini_1[i].val); + + /* init AL7230B radio, part one */ + for (i = 0; i < N(rfini_1); i++) { + if ((error = zyd_rfwrite(sc, rfini_1[i])) != 0) + return (error); + } + /* init RF-dependent PHY registers, part two */ + for (i = 0; i < N(phyini_2); i++) + zyd_write16_m(sc, phyini_2[i].reg, phyini_2[i].val); + + /* init AL7230B radio, part two */ + for (i = 0; i < N(rfini_2); i++) { + if ((error = zyd_rfwrite(sc, rfini_2[i])) != 0) + return (error); + } + /* init RF-dependent PHY registers, part three */ + for (i = 0; i < N(phyini_3); i++) + zyd_write16_m(sc, phyini_3[i].reg, phyini_3[i].val); +fail: + return (error); +#undef N +} + +static int +zyd_al7230B_switch_radio(struct zyd_rf *rf, int on) +{ + int error; + struct zyd_softc *sc = rf->rf_sc; + + zyd_write16_m(sc, ZYD_CR11, on ? 0x00 : 0x04); + zyd_write16_m(sc, ZYD_CR251, on ? 0x3f : 0x2f); +fail: + return (error); +} + +static int +zyd_al7230B_set_channel(struct zyd_rf *rf, uint8_t chan) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct { + uint32_t r1, r2; + } rfprog[] = ZYD_AL7230B_CHANTABLE; + static const uint32_t rfsc[] = ZYD_AL7230B_RF_SETCHANNEL; + int i, error; + + zyd_write16_m(sc, ZYD_CR240, 0x57); + zyd_write16_m(sc, ZYD_CR251, 0x2f); + + for (i = 0; i < N(rfsc); i++) { + if ((error = zyd_rfwrite(sc, rfsc[i])) != 0) + return (error); + } + + zyd_write16_m(sc, ZYD_CR128, 0x14); + zyd_write16_m(sc, ZYD_CR129, 0x12); + zyd_write16_m(sc, ZYD_CR130, 0x10); + zyd_write16_m(sc, ZYD_CR38, 0x38); + zyd_write16_m(sc, ZYD_CR136, 0xdf); + + error = zyd_rfwrite(sc, rfprog[chan - 1].r1); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, rfprog[chan - 1].r2); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, 0x3c9000); + if (error != 0) + goto fail; + + zyd_write16_m(sc, ZYD_CR251, 0x3f); + zyd_write16_m(sc, ZYD_CR203, 0x06); + zyd_write16_m(sc, ZYD_CR240, 0x08); +fail: + return (error); +#undef N +} + +/* + * AL2210 RF methods. + */ +static int +zyd_al2210_init(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_AL2210_PHY; + static const uint32_t rfini[] = ZYD_AL2210_RF; + uint32_t tmp; + int i, error; + + zyd_write32_m(sc, ZYD_CR18, 2); + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + /* init AL2210 radio */ + for (i = 0; i < N(rfini); i++) { + if ((error = zyd_rfwrite(sc, rfini[i])) != 0) + return (error); + } + zyd_write16_m(sc, ZYD_CR47, 0x1e); + zyd_read32_m(sc, ZYD_CR_RADIO_PD, &tmp); + zyd_write32_m(sc, ZYD_CR_RADIO_PD, tmp & ~1); + zyd_write32_m(sc, ZYD_CR_RADIO_PD, tmp | 1); + zyd_write32_m(sc, ZYD_CR_RFCFG, 0x05); + zyd_write32_m(sc, ZYD_CR_RFCFG, 0x00); + zyd_write16_m(sc, ZYD_CR47, 0x1e); + zyd_write32_m(sc, ZYD_CR18, 3); +fail: + return (error); +#undef N +} + +static int +zyd_al2210_switch_radio(struct zyd_rf *rf, int on) +{ + /* vendor driver does nothing for this RF chip */ + + return (0); +} + +static int +zyd_al2210_set_channel(struct zyd_rf *rf, uint8_t chan) +{ + int error; + struct zyd_softc *sc = rf->rf_sc; + static const uint32_t rfprog[] = ZYD_AL2210_CHANTABLE; + uint32_t tmp; + + zyd_write32_m(sc, ZYD_CR18, 2); + zyd_write16_m(sc, ZYD_CR47, 0x1e); + zyd_read32_m(sc, ZYD_CR_RADIO_PD, &tmp); + zyd_write32_m(sc, ZYD_CR_RADIO_PD, tmp & ~1); + zyd_write32_m(sc, ZYD_CR_RADIO_PD, tmp | 1); + zyd_write32_m(sc, ZYD_CR_RFCFG, 0x05); + zyd_write32_m(sc, ZYD_CR_RFCFG, 0x00); + zyd_write16_m(sc, ZYD_CR47, 0x1e); + + /* actually set the channel */ + error = zyd_rfwrite(sc, rfprog[chan - 1]); + if (error != 0) + goto fail; + + zyd_write32_m(sc, ZYD_CR18, 3); +fail: + return (error); +} + +/* + * GCT RF methods. + */ +static int +zyd_gct_init(struct zyd_rf *rf) +{ +#define ZYD_GCT_INTR_REG 0x85c1 +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_GCT_PHY; + static const uint32_t rfini[] = ZYD_GCT_RF; + static const uint16_t vco[11][7] = ZYD_GCT_VCO; + int i, idx = -1, error; + uint16_t data; + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + /* init cgt radio */ + for (i = 0; i < N(rfini); i++) { + if ((error = zyd_rfwrite(sc, rfini[i])) != 0) + return (error); + } + + error = zyd_gct_mode(rf); + if (error != 0) + return (error); + + for (i = 0; i < N(vco) - 1; i++) { + error = zyd_gct_set_channel_synth(rf, 1, 0); + if (error != 0) + goto fail; + error = zyd_gct_write(rf, vco[i][0]); + if (error != 0) + goto fail; + zyd_write16_m(sc, ZYD_GCT_INTR_REG, 0xf); + zyd_read16_m(sc, ZYD_GCT_INTR_REG, &data); + if ((data & 0xf) == 0) { + idx = i; + break; + } + } + if (idx == -1) { + error = zyd_gct_set_channel_synth(rf, 1, 1); + if (error != 0) + goto fail; + error = zyd_gct_write(rf, 0x6662); + if (error != 0) + goto fail; + } + + rf->idx = idx; + zyd_write16_m(sc, ZYD_CR203, 0x6); +fail: + return (error); +#undef N +#undef ZYD_GCT_INTR_REG +} + +static int +zyd_gct_mode(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const uint32_t mode[] = { + 0x25f98, 0x25f9a, 0x25f94, 0x27fd4 + }; + int i, error; + + for (i = 0; i < N(mode); i++) { + if ((error = zyd_rfwrite(sc, mode[i])) != 0) + break; + } + return (error); +#undef N +} + +static int +zyd_gct_set_channel_synth(struct zyd_rf *rf, int chan, int acal) +{ + int error, idx = chan - 1; + struct zyd_softc *sc = rf->rf_sc; + static uint32_t acal_synth[] = ZYD_GCT_CHANNEL_ACAL; + static uint32_t std_synth[] = ZYD_GCT_CHANNEL_STD; + static uint32_t div_synth[] = ZYD_GCT_CHANNEL_DIV; + + error = zyd_rfwrite(sc, + (acal == 1) ? acal_synth[idx] : std_synth[idx]); + if (error != 0) + return (error); + return zyd_rfwrite(sc, div_synth[idx]); +} + +static int +zyd_gct_write(struct zyd_rf *rf, uint16_t value) +{ + struct zyd_softc *sc = rf->rf_sc; + + return zyd_rfwrite(sc, 0x300000 | 0x40000 | value); +} + +static int +zyd_gct_switch_radio(struct zyd_rf *rf, int on) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int error; + struct zyd_softc *sc = rf->rf_sc; + + error = zyd_rfwrite(sc, on ? 0x25f94 : 0x25f90); + if (error != 0) + return (error); + + zyd_write16_m(sc, ZYD_CR11, on ? 0x00 : 0x04); + zyd_write16_m(sc, ZYD_CR251, + on ? ((sc->sc_macrev == ZYD_ZD1211B) ? 0x7f : 0x3f) : 0x2f); +fail: + return (error); +} + +static int +zyd_gct_set_channel(struct zyd_rf *rf, uint8_t chan) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int error, i; + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair cmd[] = { + { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR79, 0x58 }, + { ZYD_CR12, 0xf0 }, { ZYD_CR77, 0x1b }, { ZYD_CR78, 0x58 }, + }; + static const uint16_t vco[11][7] = ZYD_GCT_VCO; + + error = zyd_gct_set_channel_synth(rf, chan, 0); + if (error != 0) + goto fail; + error = zyd_gct_write(rf, (rf->idx == -1) ? 0x6662 : + vco[rf->idx][((chan - 1) / 2)]); + if (error != 0) + goto fail; + error = zyd_gct_mode(rf); + if (error != 0) + return (error); + for (i = 0; i < N(cmd); i++) + zyd_write16_m(sc, cmd[i].reg, cmd[i].val); + error = zyd_gct_txgain(rf, chan); + if (error != 0) + return (error); + zyd_write16_m(sc, ZYD_CR203, 0x6); +fail: + return (error); +#undef N +} + +static int +zyd_gct_txgain(struct zyd_rf *rf, uint8_t chan) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static uint32_t txgain[] = ZYD_GCT_TXGAIN; + uint8_t idx = sc->sc_pwrint[chan - 1]; + + if (idx >= N(txgain)) { + device_printf(sc->sc_dev, "could not set TX gain (%d %#x)\n", + chan, idx); + return 0; + } + + return zyd_rfwrite(sc, 0x700000 | txgain[idx]); +#undef N +} + +/* + * Maxim2 RF methods. + */ +static int +zyd_maxim2_init(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_MAXIM2_PHY; + static const uint32_t rfini[] = ZYD_MAXIM2_RF; + uint16_t tmp; + int i, error; + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + zyd_read16_m(sc, ZYD_CR203, &tmp); + zyd_write16_m(sc, ZYD_CR203, tmp & ~(1 << 4)); + + /* init maxim2 radio */ + for (i = 0; i < N(rfini); i++) { + if ((error = zyd_rfwrite(sc, rfini[i])) != 0) + return (error); + } + zyd_read16_m(sc, ZYD_CR203, &tmp); + zyd_write16_m(sc, ZYD_CR203, tmp | (1 << 4)); +fail: + return (error); +#undef N +} + +static int +zyd_maxim2_switch_radio(struct zyd_rf *rf, int on) +{ + + /* vendor driver does nothing for this RF chip */ + return (0); +} + +static int +zyd_maxim2_set_channel(struct zyd_rf *rf, uint8_t chan) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_MAXIM2_PHY; + static const uint32_t rfini[] = ZYD_MAXIM2_RF; + static const struct { + uint32_t r1, r2; + } rfprog[] = ZYD_MAXIM2_CHANTABLE; + uint16_t tmp; + int i, error; + + /* + * Do the same as we do when initializing it, except for the channel + * values coming from the two channel tables. + */ + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + zyd_read16_m(sc, ZYD_CR203, &tmp); + zyd_write16_m(sc, ZYD_CR203, tmp & ~(1 << 4)); + + /* first two values taken from the chantables */ + error = zyd_rfwrite(sc, rfprog[chan - 1].r1); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, rfprog[chan - 1].r2); + if (error != 0) + goto fail; + + /* init maxim2 radio - skipping the two first values */ + for (i = 2; i < N(rfini); i++) { + if ((error = zyd_rfwrite(sc, rfini[i])) != 0) + return (error); + } + zyd_read16_m(sc, ZYD_CR203, &tmp); + zyd_write16_m(sc, ZYD_CR203, tmp | (1 << 4)); +fail: + return (error); +#undef N +} + +static int +zyd_rf_attach(struct zyd_softc *sc, uint8_t type) +{ + struct zyd_rf *rf = &sc->sc_rf; + + rf->rf_sc = sc; + rf->update_pwr = 1; + + switch (type) { + case ZYD_RF_RFMD: + rf->init = zyd_rfmd_init; + rf->switch_radio = zyd_rfmd_switch_radio; + rf->set_channel = zyd_rfmd_set_channel; + rf->width = 24; /* 24-bit RF values */ + break; + case ZYD_RF_AL2230: + case ZYD_RF_AL2230S: + if (sc->sc_macrev == ZYD_ZD1211B) { + rf->init = zyd_al2230_init_b; + rf->set_channel = zyd_al2230_set_channel_b; + } else { + rf->init = zyd_al2230_init; + rf->set_channel = zyd_al2230_set_channel; + } + rf->switch_radio = zyd_al2230_switch_radio; + rf->bandedge6 = zyd_al2230_bandedge6; + rf->width = 24; /* 24-bit RF values */ + break; + case ZYD_RF_AL7230B: + rf->init = zyd_al7230B_init; + rf->switch_radio = zyd_al7230B_switch_radio; + rf->set_channel = zyd_al7230B_set_channel; + rf->width = 24; /* 24-bit RF values */ + break; + case ZYD_RF_AL2210: + rf->init = zyd_al2210_init; + rf->switch_radio = zyd_al2210_switch_radio; + rf->set_channel = zyd_al2210_set_channel; + rf->width = 24; /* 24-bit RF values */ + break; + case ZYD_RF_MAXIM_NEW: + case ZYD_RF_GCT: + rf->init = zyd_gct_init; + rf->switch_radio = zyd_gct_switch_radio; + rf->set_channel = zyd_gct_set_channel; + rf->width = 24; /* 24-bit RF values */ + rf->update_pwr = 0; + break; + case ZYD_RF_MAXIM_NEW2: + rf->init = zyd_maxim2_init; + rf->switch_radio = zyd_maxim2_switch_radio; + rf->set_channel = zyd_maxim2_set_channel; + rf->width = 18; /* 18-bit RF values */ + break; + default: + device_printf(sc->sc_dev, + "sorry, radio \"%s\" is not supported yet\n", + zyd_rf_name(type)); + return (EINVAL); + } + return (0); +} + +static const char * +zyd_rf_name(uint8_t type) +{ + static const char * const zyd_rfs[] = { + "unknown", "unknown", "UW2451", "UCHIP", "AL2230", + "AL7230B", "THETA", "AL2210", "MAXIM_NEW", "GCT", + "AL2230S", "RALINK", "INTERSIL", "RFMD", "MAXIM_NEW2", + "PHILIPS" + }; + + return zyd_rfs[(type > 15) ? 0 : type]; +} + +static int +zyd_hw_init(struct zyd_softc *sc) +{ + int error; + const struct zyd_phy_pair *phyp; + struct zyd_rf *rf = &sc->sc_rf; + uint16_t val; + + /* specify that the plug and play is finished */ + zyd_write32_m(sc, ZYD_MAC_AFTER_PNP, 1); + zyd_read16_m(sc, ZYD_FIRMWARE_BASE_ADDR, &sc->sc_fwbase); + DPRINTF(sc, ZYD_DEBUG_FW, "firmware base address=0x%04x\n", + sc->sc_fwbase); + + /* retrieve firmware revision number */ + zyd_read16_m(sc, sc->sc_fwbase + ZYD_FW_FIRMWARE_REV, &sc->sc_fwrev); + zyd_write32_m(sc, ZYD_CR_GPI_EN, 0); + zyd_write32_m(sc, ZYD_MAC_CONT_WIN_LIMIT, 0x7f043f); + /* set mandatory rates - XXX assumes 802.11b/g */ + zyd_write32_m(sc, ZYD_MAC_MAN_RATE, 0x150f); + + /* disable interrupts */ + zyd_write32_m(sc, ZYD_CR_INTERRUPT, 0); + + if ((error = zyd_read_pod(sc)) != 0) { + device_printf(sc->sc_dev, "could not read EEPROM\n"); + goto fail; + } + + /* PHY init (resetting) */ + error = zyd_lock_phy(sc); + if (error != 0) + goto fail; + phyp = (sc->sc_macrev == ZYD_ZD1211B) ? zyd_def_phyB : zyd_def_phy; + for (; phyp->reg != 0; phyp++) + zyd_write16_m(sc, phyp->reg, phyp->val); + if (sc->sc_macrev == ZYD_ZD1211 && sc->sc_fix_cr157 != 0) { + zyd_read16_m(sc, ZYD_EEPROM_PHY_REG, &val); + zyd_write32_m(sc, ZYD_CR157, val >> 8); + } + error = zyd_unlock_phy(sc); + if (error != 0) + goto fail; + + /* HMAC init */ + zyd_write32_m(sc, ZYD_MAC_ACK_EXT, 0x00000020); + zyd_write32_m(sc, ZYD_CR_ADDA_MBIAS_WT, 0x30000808); + zyd_write32_m(sc, ZYD_MAC_SNIFFER, 0x00000000); + zyd_write32_m(sc, ZYD_MAC_RXFILTER, 0x00000000); + zyd_write32_m(sc, ZYD_MAC_GHTBL, 0x00000000); + zyd_write32_m(sc, ZYD_MAC_GHTBH, 0x80000000); + zyd_write32_m(sc, ZYD_MAC_MISC, 0x000000a4); + zyd_write32_m(sc, ZYD_CR_ADDA_PWR_DWN, 0x0000007f); + zyd_write32_m(sc, ZYD_MAC_BCNCFG, 0x00f00401); + zyd_write32_m(sc, ZYD_MAC_PHY_DELAY2, 0x00000000); + zyd_write32_m(sc, ZYD_MAC_ACK_EXT, 0x00000080); + zyd_write32_m(sc, ZYD_CR_ADDA_PWR_DWN, 0x00000000); + zyd_write32_m(sc, ZYD_MAC_SIFS_ACK_TIME, 0x00000100); + zyd_write32_m(sc, ZYD_CR_RX_PE_DELAY, 0x00000070); + zyd_write32_m(sc, ZYD_CR_PS_CTRL, 0x10000000); + zyd_write32_m(sc, ZYD_MAC_RTSCTSRATE, 0x02030203); + zyd_write32_m(sc, ZYD_MAC_AFTER_PNP, 1); + zyd_write32_m(sc, ZYD_MAC_BACKOFF_PROTECT, 0x00000114); + zyd_write32_m(sc, ZYD_MAC_DIFS_EIFS_SIFS, 0x0a47c032); + zyd_write32_m(sc, ZYD_MAC_CAM_MODE, 0x3); + + if (sc->sc_macrev == ZYD_ZD1211) { + zyd_write32_m(sc, ZYD_MAC_RETRY, 0x00000002); + zyd_write32_m(sc, ZYD_MAC_RX_THRESHOLD, 0x000c0640); + } else { + zyd_write32_m(sc, ZYD_MACB_MAX_RETRY, 0x02020202); + zyd_write32_m(sc, ZYD_MACB_TXPWR_CTL4, 0x007f003f); + zyd_write32_m(sc, ZYD_MACB_TXPWR_CTL3, 0x007f003f); + zyd_write32_m(sc, ZYD_MACB_TXPWR_CTL2, 0x003f001f); + zyd_write32_m(sc, ZYD_MACB_TXPWR_CTL1, 0x001f000f); + zyd_write32_m(sc, ZYD_MACB_AIFS_CTL1, 0x00280028); + zyd_write32_m(sc, ZYD_MACB_AIFS_CTL2, 0x008C003C); + zyd_write32_m(sc, ZYD_MACB_TXOP, 0x01800824); + zyd_write32_m(sc, ZYD_MAC_RX_THRESHOLD, 0x000c0eff); + } + + /* init beacon interval to 100ms */ + if ((error = zyd_set_beacon_interval(sc, 100)) != 0) + goto fail; + + if ((error = zyd_rf_attach(sc, sc->sc_rfrev)) != 0) { + device_printf(sc->sc_dev, "could not attach RF, rev 0x%x\n", + sc->sc_rfrev); + goto fail; + } + + /* RF chip init */ + error = zyd_lock_phy(sc); + if (error != 0) + goto fail; + error = (*rf->init)(rf); + if (error != 0) { + device_printf(sc->sc_dev, + "radio initialization failed, error %d\n", error); + goto fail; + } + error = zyd_unlock_phy(sc); + if (error != 0) + goto fail; + + if ((error = zyd_read_eeprom(sc)) != 0) { + device_printf(sc->sc_dev, "could not read EEPROM\n"); + goto fail; + } + +fail: return (error); +} + +static int +zyd_read_pod(struct zyd_softc *sc) +{ + int error; + uint32_t tmp; + + zyd_read32_m(sc, ZYD_EEPROM_POD, &tmp); + sc->sc_rfrev = tmp & 0x0f; + sc->sc_ledtype = (tmp >> 4) & 0x01; + sc->sc_al2230s = (tmp >> 7) & 0x01; + sc->sc_cckgain = (tmp >> 8) & 0x01; + sc->sc_fix_cr157 = (tmp >> 13) & 0x01; + sc->sc_parev = (tmp >> 16) & 0x0f; + sc->sc_bandedge6 = (tmp >> 21) & 0x01; + sc->sc_newphy = (tmp >> 31) & 0x01; + sc->sc_txled = ((tmp & (1 << 24)) && (tmp & (1 << 29))) ? 0 : 1; +fail: + return (error); +} + +static int +zyd_read_eeprom(struct zyd_softc *sc) +{ + uint16_t val; + int error, i; + + /* read Tx power calibration tables */ + for (i = 0; i < 7; i++) { + zyd_read16_m(sc, ZYD_EEPROM_PWR_CAL + i, &val); + sc->sc_pwrcal[i * 2] = val >> 8; + sc->sc_pwrcal[i * 2 + 1] = val & 0xff; + zyd_read16_m(sc, ZYD_EEPROM_PWR_INT + i, &val); + sc->sc_pwrint[i * 2] = val >> 8; + sc->sc_pwrint[i * 2 + 1] = val & 0xff; + zyd_read16_m(sc, ZYD_EEPROM_36M_CAL + i, &val); + sc->sc_ofdm36_cal[i * 2] = val >> 8; + sc->sc_ofdm36_cal[i * 2 + 1] = val & 0xff; + zyd_read16_m(sc, ZYD_EEPROM_48M_CAL + i, &val); + sc->sc_ofdm48_cal[i * 2] = val >> 8; + sc->sc_ofdm48_cal[i * 2 + 1] = val & 0xff; + zyd_read16_m(sc, ZYD_EEPROM_54M_CAL + i, &val); + sc->sc_ofdm54_cal[i * 2] = val >> 8; + sc->sc_ofdm54_cal[i * 2 + 1] = val & 0xff; + } +fail: + return (error); +} + +static int +zyd_get_macaddr(struct zyd_softc *sc) +{ + struct usb_device_request req; + usb_error_t error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = ZYD_READFWDATAREQ; + USETW(req.wValue, ZYD_EEPROM_MAC_ADDR_P1); + USETW(req.wIndex, 0); + USETW(req.wLength, IEEE80211_ADDR_LEN); + + error = zyd_do_request(sc, &req, sc->sc_bssid); + if (error != 0) { + device_printf(sc->sc_dev, "could not read EEPROM: %s\n", + usbd_errstr(error)); + } + + return (error); +} + +static int +zyd_set_macaddr(struct zyd_softc *sc, const uint8_t *addr) +{ + int error; + uint32_t tmp; + + tmp = addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]; + zyd_write32_m(sc, ZYD_MAC_MACADRL, tmp); + tmp = addr[5] << 8 | addr[4]; + zyd_write32_m(sc, ZYD_MAC_MACADRH, tmp); +fail: + return (error); +} + +static int +zyd_set_bssid(struct zyd_softc *sc, const uint8_t *addr) +{ + int error; + uint32_t tmp; + + tmp = addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]; + zyd_write32_m(sc, ZYD_MAC_BSSADRL, tmp); + tmp = addr[5] << 8 | addr[4]; + zyd_write32_m(sc, ZYD_MAC_BSSADRH, tmp); +fail: + return (error); +} + +static int +zyd_switch_radio(struct zyd_softc *sc, int on) +{ + struct zyd_rf *rf = &sc->sc_rf; + int error; + + error = zyd_lock_phy(sc); + if (error != 0) + goto fail; + error = (*rf->switch_radio)(rf, on); + if (error != 0) + goto fail; + error = zyd_unlock_phy(sc); +fail: + return (error); +} + +static int +zyd_set_led(struct zyd_softc *sc, int which, int on) +{ + int error; + uint32_t tmp; + + zyd_read32_m(sc, ZYD_MAC_TX_PE_CONTROL, &tmp); + tmp &= ~which; + if (on) + tmp |= which; + zyd_write32_m(sc, ZYD_MAC_TX_PE_CONTROL, tmp); +fail: + return (error); +} + +static void +zyd_set_multi(struct zyd_softc *sc) +{ + int error; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ifmultiaddr *ifma; + uint32_t low, high; + uint8_t v; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + low = 0x00000000; + high = 0x80000000; + + if (ic->ic_opmode == IEEE80211_M_MONITOR || + (ifp->if_flags & (IFF_ALLMULTI | IFF_PROMISC))) { + low = 0xffffffff; + high = 0xffffffff; + } else { + if_maddr_rlock(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + v = ((uint8_t *)LLADDR((struct sockaddr_dl *) + ifma->ifma_addr))[5] >> 2; + if (v < 32) + low |= 1 << v; + else + high |= 1 << (v - 32); + } + if_maddr_runlock(ifp); + } + + /* reprogram multicast global hash table */ + zyd_write32_m(sc, ZYD_MAC_GHTBL, low); + zyd_write32_m(sc, ZYD_MAC_GHTBH, high); +fail: + if (error != 0) + device_printf(sc->sc_dev, + "could not set multicast hash table\n"); +} + +static void +zyd_update_mcast(struct ifnet *ifp) +{ + struct zyd_softc *sc = ifp->if_softc; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + ZYD_LOCK(sc); + zyd_set_multi(sc); + ZYD_UNLOCK(sc); +} + +static int +zyd_set_rxfilter(struct zyd_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t rxfilter; + + switch (ic->ic_opmode) { + case IEEE80211_M_STA: + rxfilter = ZYD_FILTER_BSS; + break; + case IEEE80211_M_IBSS: + case IEEE80211_M_HOSTAP: + rxfilter = ZYD_FILTER_HOSTAP; + break; + case IEEE80211_M_MONITOR: + rxfilter = ZYD_FILTER_MONITOR; + break; + default: + /* should not get there */ + return (EINVAL); + } + return zyd_write32(sc, ZYD_MAC_RXFILTER, rxfilter); +} + +static void +zyd_set_chan(struct zyd_softc *sc, struct ieee80211_channel *c) +{ + int error; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct zyd_rf *rf = &sc->sc_rf; + uint32_t tmp; + int chan; + + chan = ieee80211_chan2ieee(ic, c); + if (chan == 0 || chan == IEEE80211_CHAN_ANY) { + /* XXX should NEVER happen */ + device_printf(sc->sc_dev, + "%s: invalid channel %x\n", __func__, chan); + return; + } + + error = zyd_lock_phy(sc); + if (error != 0) + goto fail; + + error = (*rf->set_channel)(rf, chan); + if (error != 0) + goto fail; + + if (rf->update_pwr) { + /* update Tx power */ + zyd_write16_m(sc, ZYD_CR31, sc->sc_pwrint[chan - 1]); + + if (sc->sc_macrev == ZYD_ZD1211B) { + zyd_write16_m(sc, ZYD_CR67, + sc->sc_ofdm36_cal[chan - 1]); + zyd_write16_m(sc, ZYD_CR66, + sc->sc_ofdm48_cal[chan - 1]); + zyd_write16_m(sc, ZYD_CR65, + sc->sc_ofdm54_cal[chan - 1]); + zyd_write16_m(sc, ZYD_CR68, sc->sc_pwrcal[chan - 1]); + zyd_write16_m(sc, ZYD_CR69, 0x28); + zyd_write16_m(sc, ZYD_CR69, 0x2a); + } + } + if (sc->sc_cckgain) { + /* set CCK baseband gain from EEPROM */ + if (zyd_read32(sc, ZYD_EEPROM_PHY_REG, &tmp) == 0) + zyd_write16_m(sc, ZYD_CR47, tmp & 0xff); + } + if (sc->sc_bandedge6 && rf->bandedge6 != NULL) { + error = (*rf->bandedge6)(rf, c); + if (error != 0) + goto fail; + } + zyd_write32_m(sc, ZYD_CR_CONFIG_PHILIPS, 0); + + error = zyd_unlock_phy(sc); + if (error != 0) + goto fail; + + sc->sc_rxtap.wr_chan_freq = sc->sc_txtap.wt_chan_freq = + htole16(c->ic_freq); + sc->sc_rxtap.wr_chan_flags = sc->sc_txtap.wt_chan_flags = + htole16(c->ic_flags); +fail: + return; +} + +static int +zyd_set_beacon_interval(struct zyd_softc *sc, int bintval) +{ + int error; + uint32_t val; + + zyd_read32_m(sc, ZYD_CR_ATIM_WND_PERIOD, &val); + sc->sc_atim_wnd = val; + zyd_read32_m(sc, ZYD_CR_PRE_TBTT, &val); + sc->sc_pre_tbtt = val; + sc->sc_bcn_int = bintval; + + if (sc->sc_bcn_int <= 5) + sc->sc_bcn_int = 5; + if (sc->sc_pre_tbtt < 4 || sc->sc_pre_tbtt >= sc->sc_bcn_int) + sc->sc_pre_tbtt = sc->sc_bcn_int - 1; + if (sc->sc_atim_wnd >= sc->sc_pre_tbtt) + sc->sc_atim_wnd = sc->sc_pre_tbtt - 1; + + zyd_write32_m(sc, ZYD_CR_ATIM_WND_PERIOD, sc->sc_atim_wnd); + zyd_write32_m(sc, ZYD_CR_PRE_TBTT, sc->sc_pre_tbtt); + zyd_write32_m(sc, ZYD_CR_BCN_INTERVAL, sc->sc_bcn_int); +fail: + return (error); +} + +static void +zyd_rx_data(struct usb_xfer *xfer, int offset, uint16_t len) +{ + struct zyd_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct zyd_plcphdr plcp; + struct zyd_rx_stat stat; + struct usb_page_cache *pc; + struct mbuf *m; + int rlen, rssi; + + if (len < ZYD_MIN_FRAGSZ) { + DPRINTF(sc, ZYD_DEBUG_RECV, "%s: frame too short (length=%d)\n", + device_get_nameunit(sc->sc_dev), len); + ifp->if_ierrors++; + return; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, offset, &plcp, sizeof(plcp)); + usbd_copy_out(pc, offset + len - sizeof(stat), &stat, sizeof(stat)); + + if (stat.flags & ZYD_RX_ERROR) { + DPRINTF(sc, ZYD_DEBUG_RECV, + "%s: RX status indicated error (%x)\n", + device_get_nameunit(sc->sc_dev), stat.flags); + ifp->if_ierrors++; + return; + } + + /* compute actual frame length */ + rlen = len - sizeof(struct zyd_plcphdr) - + sizeof(struct zyd_rx_stat) - IEEE80211_CRC_LEN; + + /* allocate a mbuf to store the frame */ + if (rlen > MCLBYTES) { + DPRINTF(sc, ZYD_DEBUG_RECV, "%s: frame too long (length=%d)\n", + device_get_nameunit(sc->sc_dev), rlen); + ifp->if_ierrors++; + return; + } else if (rlen > MHLEN) + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + else + m = m_gethdr(M_DONTWAIT, MT_DATA); + if (m == NULL) { + DPRINTF(sc, ZYD_DEBUG_RECV, "%s: could not allocate rx mbuf\n", + device_get_nameunit(sc->sc_dev)); + ifp->if_ierrors++; + return; + } + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = rlen; + usbd_copy_out(pc, offset + sizeof(plcp), mtod(m, uint8_t *), rlen); + + if (ieee80211_radiotap_active(ic)) { + struct zyd_rx_radiotap_header *tap = &sc->sc_rxtap; + + tap->wr_flags = 0; + if (stat.flags & (ZYD_RX_BADCRC16 | ZYD_RX_BADCRC32)) + tap->wr_flags |= IEEE80211_RADIOTAP_F_BADFCS; + /* XXX toss, no way to express errors */ + if (stat.flags & ZYD_RX_DECRYPTERR) + tap->wr_flags |= IEEE80211_RADIOTAP_F_BADFCS; + tap->wr_rate = ieee80211_plcp2rate(plcp.signal, + (stat.flags & ZYD_RX_OFDM) ? + IEEE80211_T_OFDM : IEEE80211_T_CCK); + tap->wr_antsignal = stat.rssi + -95; + tap->wr_antnoise = -95; /* XXX */ + } + rssi = (stat.rssi > 63) ? 127 : 2 * stat.rssi; + + sc->sc_rx_data[sc->sc_rx_count].rssi = rssi; + sc->sc_rx_data[sc->sc_rx_count].m = m; + sc->sc_rx_count++; +} + +static void +zyd_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct zyd_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211_node *ni; + struct zyd_rx_desc desc; + struct mbuf *m; + struct usb_page_cache *pc; + uint32_t offset; + uint8_t rssi; + int8_t nf; + int i; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + sc->sc_rx_count = 0; + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, actlen - sizeof(desc), &desc, sizeof(desc)); + + offset = 0; + if (UGETW(desc.tag) == ZYD_TAG_MULTIFRAME) { + DPRINTF(sc, ZYD_DEBUG_RECV, + "%s: received multi-frame transfer\n", __func__); + + for (i = 0; i < ZYD_MAX_RXFRAMECNT; i++) { + uint16_t len16 = UGETW(desc.len[i]); + + if (len16 == 0 || len16 > actlen) + break; + + zyd_rx_data(xfer, offset, len16); + + /* next frame is aligned on a 32-bit boundary */ + len16 = (len16 + 3) & ~3; + offset += len16; + if (len16 > actlen) + break; + actlen -= len16; + } + } else { + DPRINTF(sc, ZYD_DEBUG_RECV, + "%s: received single-frame transfer\n", __func__); + + zyd_rx_data(xfer, 0, actlen); + } + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + + /* + * At the end of a USB callback it is always safe to unlock + * the private mutex of a device! That is why we do the + * "ieee80211_input" here, and not some lines up! + */ + ZYD_UNLOCK(sc); + for (i = 0; i < sc->sc_rx_count; i++) { + rssi = sc->sc_rx_data[i].rssi; + m = sc->sc_rx_data[i].m; + sc->sc_rx_data[i].m = NULL; + + nf = -95; /* XXX */ + + ni = ieee80211_find_rxnode(ic, + mtod(m, struct ieee80211_frame_min *)); + if (ni != NULL) { + (void)ieee80211_input(ni, m, rssi, nf); + ieee80211_free_node(ni); + } else + (void)ieee80211_input_all(ic, m, rssi, nf); + } + if ((ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0 && + !IFQ_IS_EMPTY(&ifp->if_snd)) + zyd_start(ifp); + ZYD_LOCK(sc); + break; + + default: /* Error */ + DPRINTF(sc, ZYD_DEBUG_ANY, "frame error: %s\n", usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static uint8_t +zyd_plcp_signal(struct zyd_softc *sc, int rate) +{ + switch (rate) { + /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ + case 12: + return (0xb); + case 18: + return (0xf); + case 24: + return (0xa); + case 36: + return (0xe); + case 48: + return (0x9); + case 72: + return (0xd); + case 96: + return (0x8); + case 108: + return (0xc); + /* CCK rates (NB: not IEEE std, device-specific) */ + case 2: + return (0x0); + case 4: + return (0x1); + case 11: + return (0x2); + case 22: + return (0x3); + } + + device_printf(sc->sc_dev, "unsupported rate %d\n", rate); + return (0x0); +} + +static void +zyd_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct zyd_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211vap *vap; + struct zyd_tx_data *data; + struct mbuf *m; + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTF(sc, ZYD_DEBUG_ANY, "transfer complete, %u bytes\n", + actlen); + + /* free resources */ + data = usbd_xfer_get_priv(xfer); + zyd_tx_free(data, 0); + usbd_xfer_set_priv(xfer, NULL); + + ifp->if_opackets++; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + data = STAILQ_FIRST(&sc->tx_q); + if (data) { + STAILQ_REMOVE_HEAD(&sc->tx_q, next); + m = data->m; + + if (m->m_pkthdr.len > ZYD_MAX_TXBUFSZ) { + DPRINTF(sc, ZYD_DEBUG_ANY, "data overflow, %u bytes\n", + m->m_pkthdr.len); + m->m_pkthdr.len = ZYD_MAX_TXBUFSZ; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &data->desc, ZYD_TX_DESC_SIZE); + usbd_m_copy_in(pc, ZYD_TX_DESC_SIZE, m, 0, + m->m_pkthdr.len); + + vap = data->ni->ni_vap; + if (ieee80211_radiotap_active_vap(vap)) { + struct zyd_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = data->rate; + + ieee80211_radiotap_tx(vap, m); + } + + usbd_xfer_set_frame_len(xfer, 0, ZYD_TX_DESC_SIZE + m->m_pkthdr.len); + usbd_xfer_set_priv(xfer, data); + usbd_transfer_submit(xfer); + } + ZYD_UNLOCK(sc); + zyd_start(ifp); + ZYD_LOCK(sc); + break; + + default: /* Error */ + DPRINTF(sc, ZYD_DEBUG_ANY, "transfer error, %s\n", + usbd_errstr(error)); + + ifp->if_oerrors++; + data = usbd_xfer_get_priv(xfer); + usbd_xfer_set_priv(xfer, NULL); + if (data != NULL) + zyd_tx_free(data, error); + + if (error != USB_ERR_CANCELLED) { + if (error == USB_ERR_TIMEOUT) + device_printf(sc->sc_dev, "device timeout\n"); + + /* + * Try to clear stall first, also if other + * errors occur, hence clearing stall + * introduces a 50 ms delay: + */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static int +zyd_tx_start(struct zyd_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + struct zyd_tx_desc *desc; + struct zyd_tx_data *data; + struct ieee80211_frame *wh; + const struct ieee80211_txparam *tp; + struct ieee80211_key *k; + int rate, totlen; + static uint8_t ratediv[] = ZYD_TX_RATEDIV; + uint8_t phy; + uint16_t pktlen; + uint32_t bits; + + wh = mtod(m0, struct ieee80211_frame *); + data = STAILQ_FIRST(&sc->tx_free); + STAILQ_REMOVE_HEAD(&sc->tx_free, next); + sc->tx_nfree--; + + if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_MGT || + (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_CTL) { + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + rate = tp->mgmtrate; + } else { + tp = &vap->iv_txparms[ieee80211_chan2mode(ni->ni_chan)]; + /* for data frames */ + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) + rate = tp->mcastrate; + else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) + rate = tp->ucastrate; + else { + (void) ieee80211_ratectl_rate(ni, NULL, 0); + rate = ni->ni_txrate; + } + } + + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + m_freem(m0); + return (ENOBUFS); + } + /* packet header may have moved, reset our local pointer */ + wh = mtod(m0, struct ieee80211_frame *); + } + + data->ni = ni; + data->m = m0; + data->rate = rate; + + /* fill Tx descriptor */ + desc = &data->desc; + phy = zyd_plcp_signal(sc, rate); + desc->phy = phy; + if (ZYD_RATE_IS_OFDM(rate)) { + desc->phy |= ZYD_TX_PHY_OFDM; + if (IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan)) + desc->phy |= ZYD_TX_PHY_5GHZ; + } else if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) + desc->phy |= ZYD_TX_PHY_SHPREAMBLE; + + totlen = m0->m_pkthdr.len + IEEE80211_CRC_LEN; + desc->len = htole16(totlen); + + desc->flags = ZYD_TX_FLAG_BACKOFF; + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + /* multicast frames are not sent at OFDM rates in 802.11b/g */ + if (totlen > vap->iv_rtsthreshold) { + desc->flags |= ZYD_TX_FLAG_RTS; + } else if (ZYD_RATE_IS_OFDM(rate) && + (ic->ic_flags & IEEE80211_F_USEPROT)) { + if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) + desc->flags |= ZYD_TX_FLAG_CTS_TO_SELF; + else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS) + desc->flags |= ZYD_TX_FLAG_RTS; + } + } else + desc->flags |= ZYD_TX_FLAG_MULTICAST; + if ((wh->i_fc[0] & + (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) == + (IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_PS_POLL)) + desc->flags |= ZYD_TX_FLAG_TYPE(ZYD_TX_TYPE_PS_POLL); + + /* actual transmit length (XXX why +10?) */ + pktlen = ZYD_TX_DESC_SIZE + 10; + if (sc->sc_macrev == ZYD_ZD1211) + pktlen += totlen; + desc->pktlen = htole16(pktlen); + + bits = (rate == 11) ? (totlen * 16) + 10 : + ((rate == 22) ? (totlen * 8) + 10 : (totlen * 8)); + desc->plcp_length = htole16(bits / ratediv[phy]); + desc->plcp_service = 0; + if (rate == 22 && (bits % 11) > 0 && (bits % 11) <= 3) + desc->plcp_service |= ZYD_PLCP_LENGEXT; + desc->nextlen = 0; + + if (ieee80211_radiotap_active_vap(vap)) { + struct zyd_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = rate; + + ieee80211_radiotap_tx(vap, m0); + } + + DPRINTF(sc, ZYD_DEBUG_XMIT, + "%s: sending data frame len=%zu rate=%u\n", + device_get_nameunit(sc->sc_dev), (size_t)m0->m_pkthdr.len, + rate); + + STAILQ_INSERT_TAIL(&sc->tx_q, data, next); + usbd_transfer_start(sc->sc_xfer[ZYD_BULK_WR]); + + return (0); +} + +static void +zyd_start(struct ifnet *ifp) +{ + struct zyd_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + struct mbuf *m; + + ZYD_LOCK(sc); + for (;;) { + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + if (sc->tx_nfree == 0) { + IFQ_DRV_PREPEND(&ifp->if_snd, m); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + break; + } + ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; + if (zyd_tx_start(sc, m, ni) != 0) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + break; + } + } + ZYD_UNLOCK(sc); +} + +static int +zyd_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct zyd_softc *sc = ifp->if_softc; + + ZYD_LOCK(sc); + /* prevent management frames from being sent if we're not ready */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + ZYD_UNLOCK(sc); + m_freem(m); + ieee80211_free_node(ni); + return (ENETDOWN); + } + if (sc->tx_nfree == 0) { + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + ZYD_UNLOCK(sc); + m_freem(m); + ieee80211_free_node(ni); + return (ENOBUFS); /* XXX */ + } + + /* + * Legacy path; interpret frame contents to decide + * precisely how to send the frame. + * XXX raw path + */ + if (zyd_tx_start(sc, m, ni) != 0) { + ZYD_UNLOCK(sc); + ifp->if_oerrors++; + ieee80211_free_node(ni); + return (EIO); + } + ZYD_UNLOCK(sc); + return (0); +} + +static int +zyd_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct zyd_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *) data; + int error = 0, startall = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + ZYD_LOCK(sc); + if (ifp->if_flags & IFF_UP) { + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + zyd_init_locked(sc); + startall = 1; + } else + zyd_set_multi(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + zyd_stop(sc); + } + ZYD_UNLOCK(sc); + if (startall) + ieee80211_start_all(ic); + break; + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + case SIOCGIFADDR: + error = ether_ioctl(ifp, cmd, data); + break; + default: + error = EINVAL; + break; + } + return (error); +} + +static void +zyd_init_locked(struct zyd_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct usb_config_descriptor *cd; + int error; + uint32_t val; + + ZYD_LOCK_ASSERT(sc, MA_OWNED); + + if (!(sc->sc_flags & ZYD_FLAG_INITONCE)) { + error = zyd_loadfirmware(sc); + if (error != 0) { + device_printf(sc->sc_dev, + "could not load firmware (error=%d)\n", error); + goto fail; + } + + /* reset device */ + cd = usbd_get_config_descriptor(sc->sc_udev); + error = usbd_req_set_config(sc->sc_udev, &sc->sc_mtx, + cd->bConfigurationValue); + if (error) + device_printf(sc->sc_dev, "reset failed, continuing\n"); + + error = zyd_hw_init(sc); + if (error) { + device_printf(sc->sc_dev, + "hardware initialization failed\n"); + goto fail; + } + + device_printf(sc->sc_dev, + "HMAC ZD1211%s, FW %02x.%02x, RF %s S%x, PA%x LED %x " + "BE%x NP%x Gain%x F%x\n", + (sc->sc_macrev == ZYD_ZD1211) ? "": "B", + sc->sc_fwrev >> 8, sc->sc_fwrev & 0xff, + zyd_rf_name(sc->sc_rfrev), sc->sc_al2230s, sc->sc_parev, + sc->sc_ledtype, sc->sc_bandedge6, sc->sc_newphy, + sc->sc_cckgain, sc->sc_fix_cr157); + + /* read regulatory domain (currently unused) */ + zyd_read32_m(sc, ZYD_EEPROM_SUBID, &val); + sc->sc_regdomain = val >> 16; + DPRINTF(sc, ZYD_DEBUG_INIT, "regulatory domain %x\n", + sc->sc_regdomain); + + /* we'll do software WEP decryption for now */ + DPRINTF(sc, ZYD_DEBUG_INIT, "%s: setting encryption type\n", + __func__); + zyd_write32_m(sc, ZYD_MAC_ENCRYPTION_TYPE, ZYD_ENC_SNIFFER); + + sc->sc_flags |= ZYD_FLAG_INITONCE; + } + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + zyd_stop(sc); + + DPRINTF(sc, ZYD_DEBUG_INIT, "setting MAC address to %6D\n", + IF_LLADDR(ifp), ":"); + error = zyd_set_macaddr(sc, IF_LLADDR(ifp)); + if (error != 0) + return; + + /* set basic rates */ + if (ic->ic_curmode == IEEE80211_MODE_11B) + zyd_write32_m(sc, ZYD_MAC_BAS_RATE, 0x0003); + else if (ic->ic_curmode == IEEE80211_MODE_11A) + zyd_write32_m(sc, ZYD_MAC_BAS_RATE, 0x1500); + else /* assumes 802.11b/g */ + zyd_write32_m(sc, ZYD_MAC_BAS_RATE, 0xff0f); + + /* promiscuous mode */ + zyd_write32_m(sc, ZYD_MAC_SNIFFER, 0); + /* multicast setup */ + zyd_set_multi(sc); + /* set RX filter */ + error = zyd_set_rxfilter(sc); + if (error != 0) + goto fail; + + /* switch radio transmitter ON */ + error = zyd_switch_radio(sc, 1); + if (error != 0) + goto fail; + /* set default BSS channel */ + zyd_set_chan(sc, ic->ic_curchan); + + /* + * Allocate Tx and Rx xfer queues. + */ + zyd_setup_tx_list(sc); + + /* enable interrupts */ + zyd_write32_m(sc, ZYD_CR_INTERRUPT, ZYD_HWINT_MASK); + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + usbd_xfer_set_stall(sc->sc_xfer[ZYD_BULK_WR]); + usbd_transfer_start(sc->sc_xfer[ZYD_BULK_RD]); + usbd_transfer_start(sc->sc_xfer[ZYD_INTR_RD]); + + return; + +fail: zyd_stop(sc); + return; +} + +static void +zyd_init(void *priv) +{ + struct zyd_softc *sc = priv; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + ZYD_LOCK(sc); + zyd_init_locked(sc); + ZYD_UNLOCK(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ieee80211_start_all(ic); /* start all vap's */ +} + +static void +zyd_stop(struct zyd_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + int error; + + ZYD_LOCK_ASSERT(sc, MA_OWNED); + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + /* + * Drain all the transfers, if not already drained: + */ + ZYD_UNLOCK(sc); + usbd_transfer_drain(sc->sc_xfer[ZYD_BULK_WR]); + usbd_transfer_drain(sc->sc_xfer[ZYD_BULK_RD]); + ZYD_LOCK(sc); + + zyd_unsetup_tx_list(sc); + + /* Stop now if the device was never set up */ + if (!(sc->sc_flags & ZYD_FLAG_INITONCE)) + return; + + /* switch radio transmitter OFF */ + error = zyd_switch_radio(sc, 0); + if (error != 0) + goto fail; + /* disable Rx */ + zyd_write32_m(sc, ZYD_MAC_RXFILTER, 0); + /* disable interrupts */ + zyd_write32_m(sc, ZYD_CR_INTERRUPT, 0); + +fail: + return; +} + +static int +zyd_loadfirmware(struct zyd_softc *sc) +{ + struct usb_device_request req; + size_t size; + u_char *fw; + uint8_t stat; + uint16_t addr; + + if (sc->sc_flags & ZYD_FLAG_FWLOADED) + return (0); + + if (sc->sc_macrev == ZYD_ZD1211) { + fw = (u_char *)zd1211_firmware; + size = sizeof(zd1211_firmware); + } else { + fw = (u_char *)zd1211b_firmware; + size = sizeof(zd1211b_firmware); + } + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = ZYD_DOWNLOADREQ; + USETW(req.wIndex, 0); + + addr = ZYD_FIRMWARE_START_ADDR; + while (size > 0) { + /* + * When the transfer size is 4096 bytes, it is not + * likely to be able to transfer it. + * The cause is port or machine or chip? + */ + const int mlen = min(size, 64); + + DPRINTF(sc, ZYD_DEBUG_FW, + "loading firmware block: len=%d, addr=0x%x\n", mlen, addr); + + USETW(req.wValue, addr); + USETW(req.wLength, mlen); + if (zyd_do_request(sc, &req, fw) != 0) + return (EIO); + + addr += mlen / 2; + fw += mlen; + size -= mlen; + } + + /* check whether the upload succeeded */ + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = ZYD_DOWNLOADSTS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(stat)); + if (zyd_do_request(sc, &req, &stat) != 0) + return (EIO); + + sc->sc_flags |= ZYD_FLAG_FWLOADED; + + return (stat & 0x80) ? (EIO) : (0); +} + +static void +zyd_scan_start(struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct zyd_softc *sc = ifp->if_softc; + + ZYD_LOCK(sc); + /* want broadcast address while scanning */ + zyd_set_bssid(sc, ifp->if_broadcastaddr); + ZYD_UNLOCK(sc); +} + +static void +zyd_scan_end(struct ieee80211com *ic) +{ + struct zyd_softc *sc = ic->ic_ifp->if_softc; + + ZYD_LOCK(sc); + /* restore previous bssid */ + zyd_set_bssid(sc, sc->sc_bssid); + ZYD_UNLOCK(sc); +} + +static void +zyd_set_channel(struct ieee80211com *ic) +{ + struct zyd_softc *sc = ic->ic_ifp->if_softc; + + ZYD_LOCK(sc); + zyd_set_chan(sc, ic->ic_curchan); + ZYD_UNLOCK(sc); +} + +static device_method_t zyd_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, zyd_match), + DEVMETHOD(device_attach, zyd_attach), + DEVMETHOD(device_detach, zyd_detach), + + { 0, 0 } +}; + +static driver_t zyd_driver = { + "zyd", + zyd_methods, + sizeof(struct zyd_softc) +}; + +static devclass_t zyd_devclass; + +DRIVER_MODULE(zyd, uhub, zyd_driver, zyd_devclass, NULL, 0); +MODULE_DEPEND(zyd, usb, 1, 1, 1); +MODULE_DEPEND(zyd, wlan, 1, 1, 1); +MODULE_VERSION(zyd, 1); diff --git a/sys/bus/u4b/wlan/if_zydfw.h b/sys/bus/u4b/wlan/if_zydfw.h new file mode 100644 index 0000000000..46f5c2aba7 --- /dev/null +++ b/sys/bus/u4b/wlan/if_zydfw.h @@ -0,0 +1,1144 @@ +/* + * Copyright (C) 2001, 2002, 2003,2004 ZyDAS Technology Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted provided + * that the following conditions are met: + * 1. Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* $FreeBSD$ */ + +uint8_t zd1211_firmware[] = { + 0x08, 0x91, 0xFF, 0xED, 0x09, 0x93, 0x1E, 0xEE, + 0xD1, 0x94, 0x11, 0xEE, 0x88, 0xD4, 0xD1, 0x96, + 0xD1, 0x98, 0x5C, 0x99, 0x5C, 0x99, 0x4C, 0x99, + 0x04, 0x9D, 0xD1, 0x98, 0xD1, 0x9A, 0x03, 0xEE, + 0xF4, 0x94, 0xD3, 0xD4, 0x41, 0x2A, 0x40, 0x4A, + 0x45, 0xBE, 0x88, 0x92, 0x41, 0x24, 0x40, 0x44, + 0x53, 0xBE, 0x40, 0xF0, 0x93, 0xEE, 0x41, 0xEE, + 0x98, 0x9A, 0xD4, 0xF7, 0x02, 0x00, 0x1F, 0xEC, + 0x00, 0x00, 0xB2, 0xF8, 0x4D, 0x00, 0xA1, 0xEC, + 0x00, 0x00, 0xA6, 0xF7, 0x21, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xD8, + 0xA0, 0x90, 0x98, 0x9A, 0x98, 0x9A, 0xA0, 0xD8, + 0x40, 0xF0, 0xB4, 0xF0, 0xA0, 0x90, 0x98, 0x9A, + 0xA0, 0xD8, 0x40, 0xF0, 0x64, 0xEF, 0xA0, 0x90, + 0x98, 0x9A, 0xA0, 0xD8, 0x40, 0xF0, 0xF6, 0xF0, + 0xA0, 0x90, 0x98, 0x9A, 0xA0, 0xD8, 0x40, 0xF0, + 0xF7, 0xF6, 0xA0, 0x90, 0x98, 0x9A, 0xA0, 0xD8, + 0x40, 0xF0, 0xF8, 0xF5, 0xA0, 0x90, 0x98, 0x9A, + 0xA0, 0xD8, 0x40, 0xF0, 0xF1, 0xF0, 0xA0, 0x90, + 0x98, 0x9A, 0x98, 0x9A, 0xA0, 0xD8, 0x40, 0xF0, + 0x97, 0xF7, 0xA0, 0x90, 0x98, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x0D, 0x03, 0x03, 0x00, + 0x09, 0x05, 0x01, 0x00, 0xC2, 0x94, 0x42, 0x02, + 0xC1, 0x92, 0x03, 0x96, 0x1B, 0xD7, 0x2A, 0x86, + 0x1A, 0xD5, 0x2B, 0x86, 0x09, 0xA3, 0x00, 0x80, + 0x19, 0xD3, 0x2C, 0x86, 0x00, 0xEE, 0x0A, 0x65, + 0xC0, 0x7A, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, + 0xFE, 0xFF, 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x42, 0x20, 0x08, 0x0B, 0x01, 0x00, + 0x0D, 0x03, 0x05, 0x00, 0x05, 0x94, 0xC5, 0xD4, + 0x09, 0x05, 0x01, 0x00, 0xC2, 0x94, 0x01, 0xD4, + 0x42, 0x02, 0xC1, 0x96, 0x0A, 0x65, 0xC0, 0x7A, + 0x02, 0x99, 0xC4, 0x92, 0x41, 0xA2, 0xC4, 0xD2, + 0xC5, 0x98, 0x1C, 0xD9, 0x2A, 0x86, 0x01, 0x98, + 0x1C, 0xD9, 0x2B, 0x86, 0x1B, 0xD7, 0x2C, 0x86, + 0x00, 0xEE, 0x09, 0xB3, 0xFE, 0xFF, 0xC2, 0xD2, + 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x41, 0x20, 0x08, 0x0B, 0x01, 0x00, 0x40, 0xF0, + 0xE5, 0xEE, 0x11, 0x93, 0xD8, 0xF7, 0x41, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0xAE, 0xEE, 0x40, 0xF1, + 0x40, 0x92, 0x19, 0xD3, 0xD8, 0xF7, 0xC5, 0x92, + 0x41, 0x92, 0x19, 0xD3, 0x00, 0x83, 0x40, 0x92, + 0x19, 0xD3, 0x00, 0x83, 0x0F, 0x9F, 0x95, 0xF8, + 0x0F, 0x9F, 0x99, 0xEE, 0x42, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0x99, 0xEE, 0x40, 0x92, 0x19, 0xD3, + 0xD8, 0xF7, 0x09, 0x93, 0xC7, 0xF7, 0x19, 0xD3, + 0x91, 0xEC, 0x40, 0xF0, 0x5F, 0xF2, 0x09, 0x63, + 0x00, 0x80, 0x19, 0xD3, 0xF2, 0xBD, 0x0F, 0x9F, + 0x99, 0xEE, 0x41, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x40, 0x92, + 0x19, 0xD3, 0x12, 0x95, 0x19, 0xD3, 0x10, 0x95, + 0x19, 0xD3, 0x02, 0x80, 0x19, 0xD3, 0x03, 0x82, + 0x09, 0x93, 0xC7, 0xF7, 0x19, 0xD3, 0x91, 0xEC, + 0x40, 0xF0, 0x5F, 0xF2, 0x40, 0xF0, 0xDE, 0xF3, + 0x11, 0x93, 0x04, 0xEC, 0x42, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0xE3, 0xEE, 0x40, 0x92, 0x19, 0xD3, + 0x04, 0xEC, 0x40, 0xF0, 0x38, 0xF2, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, + 0x11, 0x93, 0x44, 0x96, 0x09, 0xB3, 0xFF, 0xFD, + 0x19, 0xD3, 0x44, 0x96, 0x40, 0xF0, 0x90, 0xF7, + 0x6E, 0x92, 0x19, 0xD3, 0x05, 0x84, 0x40, 0xF0, + 0xC4, 0xEE, 0x4B, 0x62, 0x0A, 0x95, 0x2E, 0xEE, + 0xD1, 0xD4, 0x0B, 0x97, 0x2B, 0xEE, 0xD1, 0xD6, + 0x0A, 0x95, 0x00, 0xEE, 0xD1, 0xD4, 0x0B, 0x97, + 0x2F, 0xEE, 0xD1, 0xD6, 0x0A, 0x95, 0x34, 0xEE, + 0xD1, 0xD4, 0x0B, 0x97, 0x39, 0xEE, 0xD1, 0xD6, + 0x0A, 0x95, 0x3E, 0xEE, 0xD1, 0xD4, 0x0B, 0x97, + 0x43, 0xEE, 0xD1, 0xD6, 0x0A, 0x95, 0x48, 0xEE, + 0xD1, 0xD4, 0x0B, 0x97, 0x4D, 0xEE, 0xD1, 0xD6, + 0x0A, 0x95, 0x4E, 0xEE, 0xC1, 0xD4, 0x0A, 0x65, + 0x00, 0x44, 0x02, 0x97, 0xC3, 0x92, 0x44, 0xA2, + 0xC2, 0xD2, 0x43, 0xF1, 0x09, 0x93, 0x01, 0x3F, + 0x19, 0xD3, 0xC0, 0x85, 0x11, 0x93, 0x44, 0x96, + 0x09, 0xB3, 0xFF, 0xFC, 0x19, 0xD3, 0x44, 0x96, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x0D, 0x03, 0x03, 0x00, 0x03, 0x96, + 0x41, 0x02, 0x03, 0x99, 0xC4, 0x94, 0x42, 0x04, + 0xC1, 0x04, 0xC2, 0x94, 0xC3, 0xD4, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, + 0x40, 0x92, 0x19, 0xD3, 0x94, 0xEC, 0x13, 0x97, + 0x95, 0xEC, 0x1B, 0xD7, 0x02, 0x80, 0x11, 0x93, + 0x99, 0xEC, 0x19, 0xD3, 0x7C, 0x96, 0x0B, 0x97, + 0xA0, 0x00, 0x1B, 0xD7, 0x6E, 0xEC, 0x0A, 0x65, + 0x0E, 0x42, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, + 0xFF, 0xBF, 0x11, 0xA3, 0x9A, 0xEC, 0xC2, 0xD2, + 0x0A, 0x65, 0xEB, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xA3, 0xC0, 0x00, 0xC2, 0xD2, 0x0A, 0x65, + 0xE9, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, + 0xBF, 0xFF, 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x47, 0x20, 0x08, 0x0B, 0x01, 0x00, + 0x14, 0x99, 0x03, 0x80, 0x0C, 0xB3, 0x00, 0x10, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x97, 0xF0, + 0x11, 0x93, 0x9F, 0xEC, 0x41, 0x02, 0x19, 0xD3, + 0x9F, 0xEC, 0x11, 0x93, 0xD6, 0xF7, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x84, 0xEF, 0x0A, 0x65, + 0xFE, 0x7F, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0x00, 0x04, 0xC2, 0xD2, 0x0F, 0x9F, 0xB1, 0xF0, + 0x11, 0x93, 0x94, 0xEC, 0x02, 0xD2, 0x40, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0xD0, 0xEF, 0x41, 0x92, + 0x19, 0xD3, 0x94, 0xEC, 0x19, 0xD3, 0x9F, 0xEC, + 0x12, 0x95, 0x02, 0x80, 0x1A, 0xD5, 0x95, 0xEC, + 0x13, 0x97, 0x7C, 0x96, 0x1B, 0xD7, 0x99, 0xEC, + 0x0A, 0x65, 0x0E, 0x42, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0x00, 0x40, 0x19, 0xD3, 0x9A, 0xEC, + 0x09, 0x63, 0x00, 0x40, 0xC2, 0xD2, 0x02, 0x94, + 0x1A, 0xD5, 0x7C, 0x96, 0x0C, 0xB3, 0x00, 0x08, + 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0xB0, 0xEF, + 0x0C, 0xB3, 0xFF, 0x07, 0x0F, 0x9F, 0xB4, 0xEF, + 0x11, 0x93, 0x06, 0x80, 0x09, 0xB3, 0xFF, 0x07, + 0x09, 0x03, 0x00, 0xA0, 0x19, 0xD3, 0x97, 0xEC, + 0x40, 0x98, 0x0B, 0x97, 0x9C, 0xEC, 0x04, 0x95, + 0x03, 0x05, 0x14, 0x03, 0x97, 0xEC, 0x46, 0x02, + 0xC1, 0x92, 0xC2, 0xD2, 0x41, 0x08, 0x42, 0x48, + 0x02, 0x9E, 0x0F, 0x9F, 0xBB, 0xEF, 0x11, 0x93, + 0x97, 0xEC, 0xC1, 0x92, 0xC5, 0xD2, 0x5F, 0xB2, + 0x19, 0xD3, 0x9B, 0xEC, 0x0F, 0x9F, 0xD3, 0xEF, + 0x13, 0x97, 0x98, 0xEC, 0xC5, 0xD6, 0x11, 0x93, + 0x03, 0x80, 0x09, 0xB3, 0x00, 0x08, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xE9, 0xEF, 0x11, 0x93, + 0xDC, 0xF7, 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, + 0x11, 0x93, 0xDB, 0xF7, 0x09, 0xA3, 0x00, 0x10, + 0x19, 0xD3, 0xDB, 0xF7, 0x40, 0x98, 0x1C, 0xD9, + 0x9B, 0xEC, 0x12, 0x95, 0x9B, 0xEC, 0x40, 0x44, + 0x02, 0x4E, 0x0F, 0x9F, 0x86, 0xF0, 0x0A, 0xB3, + 0x08, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x07, 0xF0, 0x0A, 0xB3, 0x07, 0x00, 0x09, 0x05, + 0xA9, 0xEC, 0xC2, 0x94, 0x01, 0xD4, 0x09, 0x03, + 0xA1, 0xEC, 0xC1, 0x92, 0x19, 0xD3, 0x9B, 0xEC, + 0xC5, 0x94, 0x0A, 0xB5, 0x00, 0xFF, 0x01, 0xA5, + 0xC5, 0xD4, 0x0F, 0x9F, 0x13, 0xF0, 0x0A, 0x05, + 0xFF, 0xFF, 0x0A, 0x03, 0xB1, 0xEC, 0xC1, 0x92, + 0x01, 0xD2, 0x1A, 0xD5, 0x9B, 0xEC, 0xC5, 0x96, + 0x0B, 0x07, 0xFF, 0xFF, 0xC5, 0xD6, 0x11, 0x93, + 0x97, 0xEC, 0xC5, 0x98, 0xC1, 0xD8, 0x11, 0x93, + 0x97, 0xEC, 0x09, 0x05, 0x0B, 0x00, 0x03, 0xD4, + 0xC2, 0x96, 0x06, 0xD6, 0x7B, 0x95, 0x7A, 0x95, + 0x4C, 0x02, 0xC1, 0x92, 0x59, 0x93, 0x59, 0x93, + 0x01, 0xA5, 0x01, 0x98, 0x0C, 0xF5, 0x7B, 0x93, + 0x09, 0x09, 0x01, 0x00, 0x06, 0x92, 0x09, 0xB3, + 0xFF, 0x00, 0x04, 0xD2, 0x5C, 0x93, 0x59, 0x93, + 0x04, 0x94, 0x01, 0xA5, 0x03, 0x96, 0xC3, 0xD4, + 0x11, 0x93, 0x97, 0xEC, 0x4C, 0x02, 0x05, 0xD2, + 0xC1, 0x92, 0x09, 0xB3, 0x00, 0xFF, 0x7C, 0x95, + 0x7A, 0x95, 0x02, 0xA3, 0x05, 0x98, 0xC4, 0xD2, + 0x12, 0x95, 0x97, 0xEC, 0x45, 0x04, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xA3, 0x00, 0x01, 0xC2, 0xD2, + 0x12, 0x95, 0x9B, 0xEC, 0x0A, 0xB3, 0x08, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x5B, 0xF0, + 0x12, 0x95, 0x97, 0xEC, 0x4A, 0x04, 0x02, 0x99, + 0xC4, 0x92, 0x01, 0x98, 0x0C, 0xF3, 0x7B, 0x93, + 0x41, 0x02, 0x0F, 0x9F, 0x7C, 0xF0, 0x43, 0x44, + 0x02, 0x8E, 0x0F, 0x9F, 0x7D, 0xF0, 0x11, 0x93, + 0x97, 0xEC, 0x42, 0x02, 0x0A, 0x05, 0xFF, 0xFF, + 0xC1, 0xD4, 0x11, 0x93, 0x97, 0xEC, 0x4A, 0x02, + 0x12, 0x95, 0x60, 0x96, 0xC1, 0xD4, 0x12, 0x95, + 0x97, 0xEC, 0x4B, 0x04, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0x1F, 0xFF, 0xC2, 0xD2, 0x12, 0x95, + 0x97, 0xEC, 0x4B, 0x04, 0x11, 0x93, 0x62, 0x96, + 0x41, 0x93, 0x59, 0x93, 0x02, 0x99, 0xC4, 0xA2, + 0xC2, 0xD2, 0xC5, 0x92, 0x19, 0xD3, 0x98, 0xEC, + 0x0A, 0x95, 0x0C, 0x02, 0x1A, 0xD5, 0x02, 0x80, + 0x0F, 0x9F, 0xB1, 0xF0, 0x09, 0x63, 0xFE, 0x7F, + 0x01, 0x97, 0xC3, 0x94, 0x0A, 0xA5, 0x00, 0x04, + 0xC1, 0xD4, 0x11, 0x93, 0x9F, 0xEC, 0x09, 0xA3, + 0x00, 0x01, 0x19, 0xD3, 0x9F, 0xEC, 0x40, 0xF0, + 0x39, 0xEF, 0x0F, 0x9F, 0xB1, 0xF0, 0x11, 0x93, + 0x94, 0xEC, 0x41, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0xA6, 0xF0, 0x40, 0xF0, 0x39, 0xEF, 0x11, 0x93, + 0x95, 0xEC, 0x44, 0xB2, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xB1, 0xF0, 0x48, 0x98, 0x1C, 0xD9, + 0x02, 0x80, 0x11, 0x93, 0x91, 0xEC, 0x41, 0x22, + 0x0A, 0x95, 0xB1, 0xF0, 0x88, 0xD4, 0x88, 0xDC, + 0x91, 0x9A, 0x47, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x11, 0x93, + 0x04, 0x82, 0x48, 0xB2, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xC8, 0xF0, 0x0A, 0x65, 0xFD, 0x7D, + 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, 0xFF, 0xFE, + 0xC2, 0xD2, 0x41, 0x92, 0x19, 0xD3, 0xBF, 0xEC, + 0x11, 0x93, 0x04, 0x82, 0x43, 0xB2, 0x12, 0x95, + 0x03, 0x82, 0x02, 0xB3, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xEF, 0xF0, 0x0A, 0xB3, 0x00, 0xFF, + 0x48, 0xA2, 0x19, 0xD3, 0x03, 0x82, 0x40, 0xF0, + 0xEB, 0xF3, 0x11, 0x93, 0xBF, 0xEC, 0x41, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0xEF, 0xF0, 0x11, 0x93, + 0x07, 0x82, 0x11, 0x43, 0x03, 0xEC, 0x02, 0x0E, + 0x0F, 0x9F, 0xEF, 0xF0, 0x11, 0x93, 0x03, 0x82, + 0x09, 0xA3, 0x00, 0x01, 0x19, 0xD3, 0x03, 0x82, + 0x40, 0x96, 0x1B, 0xD7, 0xBF, 0xEC, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x11, 0x93, 0x20, 0xBC, 0xC8, 0xD2, + 0x40, 0xF0, 0x48, 0xF1, 0x41, 0x00, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x42, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x0D, 0x03, 0x05, 0x00, 0x05, 0x94, + 0x41, 0x02, 0xC1, 0x92, 0x01, 0x97, 0xC3, 0x96, + 0xC2, 0xD6, 0x0A, 0x45, 0x00, 0x95, 0x02, 0x5E, + 0x0F, 0x9F, 0x45, 0xF1, 0xC1, 0x92, 0x41, 0xB2, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x45, 0xF1, + 0x11, 0x93, 0xC0, 0xEC, 0x40, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0x45, 0xF1, 0x41, 0x98, 0x1C, 0xD9, + 0xC0, 0xEC, 0x12, 0x95, 0x02, 0x80, 0x01, 0xD4, + 0x40, 0xF0, 0x56, 0xF2, 0x0B, 0x67, 0xFD, 0x7D, + 0x03, 0x99, 0xC4, 0x92, 0x0C, 0x99, 0x96, 0x03, + 0x1C, 0xD9, 0x06, 0x82, 0x41, 0x98, 0x1C, 0xD9, + 0x02, 0x82, 0x42, 0x98, 0x1C, 0xD9, 0x05, 0x82, + 0x0C, 0x69, 0x80, 0x7F, 0x1C, 0xD9, 0x00, 0xB0, + 0x09, 0xA3, 0x00, 0x01, 0xC3, 0xD2, 0x01, 0x94, + 0x0A, 0xB3, 0x04, 0x00, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0x43, 0xF1, 0x42, 0xA4, 0x1A, 0xD5, + 0x02, 0x80, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x42, 0x20, 0x08, 0x0B, 0x01, 0x00, + 0x05, 0x92, 0xC5, 0xD2, 0x60, 0xB2, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x55, 0xF1, 0x40, 0xF0, + 0x35, 0xF7, 0xC5, 0x94, 0x0A, 0xB3, 0x10, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x5E, 0xF1, + 0x40, 0xF0, 0x23, 0xF6, 0xC5, 0x96, 0x0B, 0xB3, + 0x40, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x67, 0xF1, 0x40, 0xF0, 0x5D, 0xF5, 0xC5, 0x94, + 0x0A, 0xB3, 0x01, 0x00, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xC8, 0xF1, 0x13, 0x97, 0x21, 0xBC, + 0x01, 0xD6, 0x0B, 0xB3, 0x02, 0x00, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x79, 0xF1, 0x40, 0xF0, + 0x62, 0xFB, 0x01, 0x94, 0x0A, 0xB3, 0x04, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x82, 0xF1, + 0x40, 0xF0, 0x6C, 0xFB, 0x01, 0x96, 0x0B, 0xB3, + 0x01, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xA2, 0xF1, 0x40, 0xF0, 0xB0, 0xFA, 0x41, 0x92, + 0x19, 0xD3, 0xD5, 0xF7, 0x11, 0x93, 0x03, 0xEC, + 0x09, 0x43, 0x40, 0x00, 0x02, 0x5E, 0x0F, 0x9F, + 0x98, 0xF1, 0x40, 0x94, 0x1A, 0xD5, 0xD5, 0xF7, + 0x11, 0x93, 0x00, 0xEC, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xAB, 0xF1, 0x40, 0xF0, 0x38, 0xF2, + 0x0F, 0x9F, 0xAB, 0xF1, 0x01, 0x96, 0x0B, 0xB3, + 0x08, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xAB, 0xF1, 0x40, 0xF0, 0x7C, 0xFB, 0x01, 0x94, + 0x0A, 0xB3, 0x10, 0x00, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xB4, 0xF1, 0x40, 0xF0, 0x87, 0xFB, + 0x11, 0x93, 0x10, 0xEC, 0x42, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0xBF, 0xF1, 0x44, 0x96, 0x1B, 0xD7, + 0x0B, 0xBC, 0x0F, 0x9F, 0xC5, 0xF1, 0x41, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0xC5, 0xF1, 0x19, 0xD3, + 0x0B, 0xBC, 0x40, 0x92, 0x19, 0xD3, 0x10, 0xEC, + 0xC5, 0x94, 0x0A, 0xB3, 0x80, 0x00, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x12, 0xF2, 0x13, 0x97, + 0x28, 0xBC, 0x01, 0xD6, 0x0B, 0xB3, 0x40, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0xDA, 0xF1, + 0x40, 0xF0, 0x18, 0xF7, 0x01, 0x94, 0x0A, 0xB3, + 0x02, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xED, 0xF1, 0x40, 0xF0, 0xC4, 0xEE, 0x40, 0xF0, + 0x8F, 0xFB, 0x40, 0xF0, 0x1B, 0xF2, 0x40, 0x96, + 0x1B, 0xD7, 0x00, 0xEC, 0x41, 0x92, 0x19, 0xD3, + 0xD8, 0xF7, 0x01, 0x94, 0x0A, 0xB3, 0x04, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x09, 0xF2, + 0x40, 0xF0, 0x9E, 0xFB, 0x09, 0x63, 0x00, 0x44, + 0x01, 0x97, 0xC3, 0x94, 0x48, 0xA4, 0xC1, 0xD4, + 0x00, 0xEE, 0x40, 0x92, 0x19, 0xD3, 0x12, 0x95, + 0x19, 0xD3, 0x10, 0x95, 0x19, 0xD3, 0x02, 0x80, + 0x19, 0xD3, 0x03, 0x82, 0x41, 0x92, 0x19, 0xD3, + 0xD8, 0xF7, 0x01, 0x94, 0x0A, 0xB3, 0x08, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x12, 0xF2, + 0x40, 0xF0, 0xAE, 0xFB, 0x0A, 0x65, 0x00, 0x44, + 0x02, 0x97, 0xC3, 0x92, 0x44, 0xA2, 0xC2, 0xD2, + 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x09, 0x63, 0x00, 0x40, + 0x19, 0xD3, 0xF2, 0xBD, 0x0A, 0x65, 0xEA, 0x43, + 0x02, 0x97, 0xC3, 0x92, 0x44, 0xA2, 0xC2, 0xD2, + 0x0A, 0x65, 0xE9, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xA3, 0x40, 0x00, 0xC2, 0xD2, 0x0A, 0x65, + 0xEB, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0xC0, 0x00, 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x09, 0x63, + 0x00, 0x80, 0x19, 0xD3, 0xF2, 0xBD, 0x0A, 0x65, + 0xE8, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0xC0, 0x00, 0xC2, 0xD2, 0x0A, 0x65, 0xEB, 0x43, + 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, 0xBF, 0xFF, + 0xC2, 0xD2, 0x0A, 0x65, 0xEA, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xB3, 0xFB, 0xFF, 0xC2, 0xD2, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x09, 0x93, 0x00, 0x01, 0x19, 0xD3, + 0x02, 0x80, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x09, 0x93, 0x00, 0x09, + 0x19, 0xD3, 0x02, 0x80, 0x40, 0xF0, 0x56, 0xF2, + 0x40, 0x92, 0x19, 0xD3, 0x94, 0xEC, 0xC8, 0xD2, + 0x09, 0x93, 0x91, 0xEC, 0xC8, 0xD2, 0x40, 0xF0, + 0x2A, 0xEF, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x40, 0xF0, + 0x3B, 0xF5, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x85, 0xF2, 0x0A, 0x65, 0xFE, 0x7F, 0x02, 0x97, + 0xC3, 0x92, 0x44, 0xA2, 0xC2, 0xD2, 0x0F, 0x9F, + 0x92, 0xF2, 0x40, 0xF0, 0x94, 0xF2, 0x40, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0x92, 0xF2, 0xC8, 0xD2, + 0x09, 0x93, 0x91, 0xEC, 0xC8, 0xD2, 0x40, 0xF0, + 0x2A, 0xEF, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x11, 0x93, + 0xF1, 0xBD, 0x19, 0xD3, 0xB6, 0xEC, 0x11, 0x93, + 0xB4, 0xEC, 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0xAC, 0xF2, 0x09, 0x63, 0x00, 0x80, 0x01, 0x97, + 0xC3, 0x94, 0x0A, 0x07, 0x07, 0x00, 0xC1, 0xD6, + 0x0A, 0x05, 0x00, 0xA0, 0x1A, 0xD5, 0x96, 0xEC, + 0x11, 0x93, 0xB6, 0xEC, 0x19, 0xD3, 0x01, 0x80, + 0x0A, 0x65, 0xFE, 0x7F, 0x02, 0x97, 0xC3, 0x92, + 0x41, 0xA2, 0xC2, 0xD2, 0x40, 0x92, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x41, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x13, 0x97, 0xB4, 0xEC, 0x40, 0x46, + 0x02, 0x5E, 0x0F, 0x9F, 0x2C, 0xF3, 0x12, 0x95, + 0x96, 0xEC, 0x0A, 0x03, 0x07, 0x00, 0xC1, 0x92, + 0xC2, 0xD2, 0x11, 0x93, 0x96, 0xEC, 0x09, 0x05, + 0x01, 0x00, 0x48, 0x02, 0xC1, 0x92, 0xC2, 0xD2, + 0x11, 0x93, 0x96, 0xEC, 0x4E, 0x02, 0xC1, 0x94, + 0xC5, 0xD6, 0xC5, 0x92, 0x11, 0x07, 0x96, 0xEC, + 0x0B, 0x03, 0x0F, 0x00, 0xC1, 0x98, 0x46, 0x06, + 0x7A, 0x93, 0x79, 0x93, 0x5C, 0x95, 0x5A, 0x95, + 0x02, 0xA3, 0xC3, 0xD2, 0x04, 0x95, 0xC5, 0x96, + 0x41, 0x06, 0xC5, 0xD6, 0x42, 0x46, 0x02, 0x9E, + 0x0F, 0x9F, 0xD5, 0xF2, 0x11, 0x93, 0x96, 0xEC, + 0x09, 0x05, 0x05, 0x00, 0x41, 0x02, 0xC1, 0x92, + 0xC2, 0xD2, 0x11, 0x93, 0x96, 0xEC, 0xC1, 0x92, + 0x09, 0xB5, 0x1F, 0x00, 0x43, 0x44, 0x02, 0x8E, + 0x0F, 0x9F, 0x02, 0xF3, 0x40, 0x44, 0x02, 0x4E, + 0x0F, 0x9F, 0x03, 0xF3, 0x0A, 0x05, 0xFF, 0xFF, + 0x0F, 0x9F, 0x03, 0xF3, 0x43, 0x94, 0x11, 0x93, + 0x96, 0xEC, 0x42, 0x02, 0xC1, 0xD4, 0x11, 0x93, + 0x96, 0xEC, 0x49, 0x02, 0xC1, 0x92, 0x19, 0xD3, + 0xB4, 0xEC, 0x09, 0x05, 0xF2, 0xFF, 0x1A, 0xD5, + 0x92, 0xEC, 0x09, 0x43, 0xD0, 0x07, 0x02, 0x9E, + 0x0F, 0x9F, 0x2C, 0xF3, 0x11, 0x93, 0xDC, 0xF7, + 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, 0x11, 0x93, + 0xDB, 0xF7, 0x09, 0xA3, 0x40, 0x00, 0x19, 0xD3, + 0xDB, 0xF7, 0x09, 0x63, 0x00, 0x80, 0x01, 0x95, + 0xC2, 0x94, 0x1A, 0xD5, 0xB5, 0xEC, 0x40, 0x96, + 0x1B, 0xD7, 0xB4, 0xEC, 0x0F, 0x9F, 0x92, 0xF3, + 0x11, 0x93, 0x92, 0xEC, 0x12, 0x95, 0xB6, 0xEC, + 0x02, 0x43, 0x02, 0x8E, 0x0F, 0x9F, 0x7A, 0xF3, + 0x02, 0x0E, 0x0F, 0x9F, 0x4D, 0xF3, 0x11, 0x93, + 0xDC, 0xF7, 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, + 0x11, 0x93, 0xDB, 0xF7, 0x09, 0xA3, 0x80, 0x00, + 0x19, 0xD3, 0xDB, 0xF7, 0x09, 0x63, 0x00, 0x80, + 0x01, 0x95, 0xC2, 0x94, 0x1A, 0xD5, 0xB5, 0xEC, + 0x40, 0x96, 0x1B, 0xD7, 0xB4, 0xEC, 0x0F, 0x9F, + 0x92, 0xF3, 0x11, 0x93, 0x03, 0x80, 0x09, 0xB3, + 0x00, 0x40, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x5F, 0xF3, 0x11, 0x93, 0xC0, 0xEC, 0x40, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0x5F, 0xF3, 0x40, 0xF0, + 0xA6, 0xF3, 0x0F, 0x9F, 0x94, 0xF3, 0x41, 0x92, + 0xC8, 0xD2, 0x0A, 0x95, 0x91, 0xEC, 0xC8, 0xD4, + 0x40, 0xF0, 0x2A, 0xEF, 0x42, 0x00, 0x11, 0x93, + 0xC0, 0xEC, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x72, 0xF3, 0x42, 0x96, 0x1B, 0xD7, 0xC0, 0xEC, + 0x0F, 0x9F, 0x94, 0xF3, 0x0A, 0x65, 0xFE, 0x7F, + 0x02, 0x97, 0xC3, 0x92, 0x42, 0xA2, 0xC2, 0xD2, + 0x0F, 0x9F, 0x94, 0xF3, 0x12, 0x45, 0x03, 0xEC, + 0x02, 0x4E, 0x0F, 0x9F, 0x8C, 0xF3, 0x11, 0x93, + 0xDC, 0xF7, 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, + 0x11, 0x93, 0xDB, 0xF7, 0x09, 0xA3, 0x00, 0x08, + 0x19, 0xD3, 0xDB, 0xF7, 0x1A, 0xD5, 0x92, 0xEC, + 0x11, 0x93, 0x92, 0xEC, 0x19, 0x25, 0x92, 0xEC, + 0x09, 0x63, 0x00, 0x80, 0x19, 0xD3, 0xF2, 0xBD, + 0x41, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x40, 0xF0, 0xA6, 0xF3, + 0x40, 0x92, 0xC8, 0xD2, 0x09, 0x93, 0x91, 0xEC, + 0xC8, 0xD2, 0x40, 0xF0, 0x2A, 0xEF, 0x42, 0x00, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x11, 0x93, 0xD7, 0xF7, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xB6, 0xF3, 0x0A, 0x65, + 0xBC, 0x69, 0x02, 0x97, 0xC3, 0x92, 0x09, 0x83, + 0x00, 0x02, 0xC2, 0xD2, 0x11, 0x93, 0x03, 0x80, + 0x09, 0xB3, 0x00, 0x40, 0x40, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0xC9, 0xF3, 0x11, 0x93, 0xDC, 0xF7, + 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, 0x11, 0x93, + 0xDB, 0xF7, 0x09, 0xA3, 0x00, 0x20, 0x19, 0xD3, + 0xDB, 0xF7, 0x11, 0x93, 0xB5, 0xEC, 0x19, 0xD3, + 0x04, 0x80, 0x12, 0x95, 0xB4, 0xEC, 0x1A, 0xD5, + 0x05, 0x80, 0x09, 0x63, 0x00, 0x80, 0x01, 0x97, + 0xC3, 0x96, 0x1B, 0xD7, 0xB5, 0xEC, 0x40, 0x94, + 0x1A, 0xD5, 0xB4, 0xEC, 0x19, 0xD3, 0xF2, 0xBD, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x09, 0x93, 0x96, 0x03, 0x19, 0xD3, + 0x06, 0x82, 0x09, 0x93, 0x00, 0x01, 0x19, 0xD3, + 0x03, 0x82, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x47, 0x20, 0x08, 0x0B, 0x01, 0x00, 0x11, 0x93, + 0x01, 0x82, 0xC5, 0xD2, 0x40, 0x94, 0x01, 0xD4, + 0x13, 0x97, 0xB8, 0xEC, 0x02, 0xD6, 0x03, 0x95, + 0x0C, 0x99, 0xBB, 0xEC, 0x04, 0x05, 0x13, 0x97, + 0x03, 0xEC, 0x01, 0x27, 0x02, 0x99, 0xC4, 0x92, + 0x03, 0x03, 0xC2, 0xD2, 0x14, 0x99, 0xBA, 0xEC, + 0x03, 0x09, 0x1C, 0xD9, 0xBA, 0xEC, 0x12, 0x95, + 0x04, 0x82, 0x0A, 0xB3, 0x02, 0x00, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x29, 0xF5, 0x01, 0x92, + 0x03, 0xD2, 0x0A, 0xA3, 0x02, 0x00, 0x19, 0xD3, + 0x04, 0x82, 0x02, 0x96, 0x0B, 0x05, 0x01, 0x00, + 0x1A, 0xD5, 0xB8, 0xEC, 0xC5, 0x92, 0x43, 0x42, + 0x02, 0x9E, 0x0F, 0x9F, 0x37, 0xF4, 0x42, 0x44, + 0x02, 0x8E, 0x0F, 0x9F, 0x37, 0xF4, 0x11, 0x93, + 0xBF, 0xEC, 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0x37, 0xF4, 0x0C, 0x49, 0xD3, 0x08, 0x02, 0x8E, + 0x0F, 0x9F, 0x37, 0xF4, 0x11, 0x63, 0x07, 0x82, + 0x11, 0xA3, 0x07, 0x82, 0x71, 0x93, 0x79, 0x93, + 0x79, 0x93, 0x79, 0x93, 0x03, 0xD2, 0xC5, 0x94, + 0x0A, 0xB5, 0xFC, 0xFF, 0x04, 0xD4, 0x03, 0x96, + 0x40, 0x46, 0x02, 0x5E, 0x0F, 0x9F, 0x46, 0xF4, + 0x11, 0x93, 0xB8, 0xEC, 0x41, 0x42, 0x02, 0x8E, + 0x0F, 0x9F, 0x4D, 0xF4, 0xC5, 0x98, 0x0C, 0x03, + 0xFF, 0xFF, 0x42, 0x42, 0x02, 0x8E, 0x0F, 0x9F, + 0x74, 0xF4, 0x0A, 0x95, 0xBB, 0xEC, 0x42, 0x92, + 0x19, 0xD3, 0xB9, 0xEC, 0xC5, 0x96, 0x43, 0x46, + 0x02, 0x9E, 0x0F, 0x9F, 0x66, 0xF4, 0x0B, 0x07, + 0xFC, 0xFF, 0xC5, 0xD6, 0xD2, 0x98, 0x1C, 0xD9, + 0xC8, 0xBC, 0xD2, 0x96, 0x1B, 0xD7, 0xCA, 0xBC, + 0x09, 0x03, 0xFF, 0xFF, 0x40, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0x52, 0xF4, 0x19, 0xD3, 0xB9, 0xEC, + 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0x72, 0xF4, + 0x0A, 0x05, 0xFE, 0xFF, 0xCA, 0xD2, 0xC2, 0xD2, + 0x0F, 0x9F, 0x74, 0xF4, 0x1A, 0xD5, 0x93, 0xEC, + 0x03, 0x98, 0x40, 0x48, 0x02, 0x5E, 0x0F, 0x9F, + 0xA1, 0xF4, 0x11, 0x93, 0xB8, 0xEC, 0x41, 0x42, + 0x02, 0x9E, 0x0F, 0x9F, 0x84, 0xF4, 0x04, 0x94, + 0x48, 0x44, 0x02, 0x4E, 0x0F, 0x9F, 0x8F, 0xF4, + 0x41, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0xA1, 0xF4, + 0x11, 0x93, 0x04, 0x82, 0x41, 0xB2, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xA1, 0xF4, 0x41, 0x96, + 0x01, 0xD6, 0x0A, 0x65, 0xBD, 0x43, 0x02, 0x99, + 0xC4, 0x92, 0x09, 0xA3, 0x80, 0x00, 0xC2, 0xD2, + 0x0A, 0x65, 0xE8, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0xBF, 0xFF, 0xC2, 0xD2, 0x0F, 0x9F, + 0xFA, 0xF4, 0xC5, 0x98, 0x43, 0x48, 0x02, 0x9E, + 0x0F, 0x9F, 0xFA, 0xF4, 0x4F, 0x96, 0x0C, 0xB3, + 0x01, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xAE, 0xF4, 0x47, 0x96, 0x11, 0x93, 0xB7, 0xEC, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0xD6, 0xF4, + 0x11, 0x93, 0xB8, 0xEC, 0x41, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0xD6, 0xF4, 0x12, 0x95, 0x00, 0x82, + 0x0A, 0x05, 0xFF, 0xAF, 0x05, 0xD4, 0xC8, 0xD6, + 0xC8, 0xD2, 0x40, 0xF0, 0x7B, 0xF7, 0x42, 0x00, + 0x05, 0x96, 0xC3, 0x94, 0x01, 0xB5, 0x40, 0x44, + 0x02, 0x4E, 0x0F, 0x9F, 0xD6, 0xF4, 0x06, 0x98, + 0x50, 0x98, 0x1C, 0xD9, 0xA2, 0xBC, 0x40, 0x98, + 0x1C, 0xD9, 0xA2, 0xBC, 0x40, 0x92, 0x03, 0xD2, + 0x0F, 0x9F, 0xFF, 0xF4, 0x03, 0x94, 0x40, 0x44, + 0x02, 0x5E, 0x0F, 0x9F, 0xE3, 0xF4, 0x0A, 0x65, + 0x5E, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x48, 0xA2, + 0xC2, 0xD2, 0x0F, 0x9F, 0xFF, 0xF4, 0x11, 0x93, + 0xB8, 0xEC, 0x0C, 0x99, 0xBB, 0xEC, 0x04, 0x03, + 0x04, 0x96, 0x13, 0x25, 0x03, 0xEC, 0xC1, 0xD4, + 0x11, 0x93, 0xBA, 0xEC, 0x19, 0x05, 0xBA, 0xEC, + 0x1B, 0xD7, 0x01, 0x82, 0x0A, 0x65, 0xFD, 0x7D, + 0x02, 0x99, 0xC4, 0x92, 0x43, 0xA2, 0xC2, 0xD2, + 0x41, 0x92, 0x01, 0xD2, 0x03, 0x94, 0x40, 0x44, + 0x02, 0x5E, 0x0F, 0x9F, 0x13, 0xF5, 0x11, 0x93, + 0xB9, 0xEC, 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0x0B, 0xF5, 0x19, 0xD3, 0xB8, 0xEC, 0x19, 0xD3, + 0xBA, 0xEC, 0x19, 0xD3, 0xBB, 0xEC, 0x03, 0x96, + 0x40, 0x46, 0x02, 0x5E, 0x0F, 0x9F, 0x13, 0xF5, + 0x41, 0x98, 0x1C, 0xD9, 0xB7, 0xEC, 0x11, 0x93, + 0xBF, 0xEC, 0x41, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0x24, 0xF5, 0x11, 0x93, 0x00, 0x82, 0x19, 0xD3, + 0x02, 0x82, 0x0A, 0x65, 0xFD, 0x7D, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xA3, 0x00, 0x01, 0xC2, 0xD2, + 0x40, 0x98, 0x1C, 0xD9, 0xBF, 0xEC, 0x0F, 0x9F, + 0x2C, 0xF5, 0x01, 0x92, 0x19, 0xD3, 0xB7, 0xEC, + 0x01, 0x94, 0x40, 0x44, 0x02, 0x5E, 0x0F, 0x9F, + 0x38, 0xF5, 0x0A, 0x65, 0xEA, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xB3, 0xFB, 0xFF, 0xC2, 0xD2, + 0x47, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x12, 0x95, 0x03, 0x80, + 0x0A, 0xB3, 0x00, 0x40, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0x57, 0xF5, 0x0A, 0xB7, 0x00, 0x08, + 0x40, 0x46, 0x02, 0x5E, 0x0F, 0x9F, 0x5A, 0xF5, + 0x11, 0x93, 0x03, 0xEC, 0x41, 0x02, 0x09, 0xB3, + 0xFE, 0xFF, 0x12, 0x95, 0x07, 0x80, 0x01, 0x45, + 0x02, 0x8E, 0x0F, 0x9F, 0x5A, 0xF5, 0x41, 0x92, + 0x0F, 0x9F, 0x5B, 0xF5, 0x40, 0x92, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x41, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x0A, 0x65, 0xE9, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xA3, 0x40, 0x00, 0xC2, 0xD2, + 0x13, 0x97, 0x6E, 0xEC, 0x0B, 0x47, 0xA0, 0x00, + 0x02, 0x5E, 0x0F, 0x9F, 0x86, 0xF5, 0x09, 0x63, + 0x08, 0x43, 0x0A, 0x65, 0xFF, 0x5F, 0x01, 0x99, + 0xC4, 0xD4, 0x0A, 0x95, 0x9B, 0xEC, 0xD2, 0x96, + 0x1B, 0xD7, 0xFA, 0xBC, 0xD2, 0x96, 0xC4, 0xD6, + 0xD2, 0x98, 0x1C, 0xD9, 0xFA, 0xBC, 0xD2, 0x96, + 0xC1, 0xD6, 0xC2, 0x94, 0x1A, 0xD5, 0xFA, 0xBC, + 0x0F, 0x9F, 0xC4, 0xF5, 0x0C, 0x69, 0xFF, 0x6F, + 0x1C, 0xD9, 0xF8, 0xBC, 0x0B, 0x47, 0x10, 0x95, + 0x02, 0x5E, 0x0F, 0x9F, 0x9E, 0xF5, 0x0A, 0x95, + 0x6F, 0xEC, 0x09, 0x63, 0x06, 0x43, 0x01, 0x99, + 0xC4, 0xD6, 0xD2, 0x96, 0x1B, 0xD7, 0xF8, 0xBC, + 0x0C, 0x69, 0xEE, 0x6A, 0xC1, 0xD8, 0xC2, 0x94, + 0x1A, 0xD5, 0xF8, 0xBC, 0x40, 0x92, 0xC5, 0xD2, + 0x11, 0x43, 0xC1, 0xEC, 0x02, 0x0E, 0x0F, 0x9F, + 0xC1, 0xF5, 0xC5, 0x94, 0x0A, 0x03, 0x71, 0xEC, + 0xC1, 0x94, 0x1A, 0xD5, 0xFA, 0xBC, 0x11, 0x93, + 0xC0, 0xEC, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xB3, 0xF5, 0x0A, 0x95, 0x6F, 0xEC, 0xC8, 0xD4, + 0x40, 0xF0, 0x9C, 0xF7, 0x19, 0xD3, 0xF8, 0xBC, + 0x41, 0x00, 0xC5, 0x96, 0x41, 0x06, 0xC5, 0xD6, + 0x13, 0x47, 0xC1, 0xEC, 0x02, 0x1E, 0x0F, 0x9F, + 0xA5, 0xF5, 0x40, 0x98, 0x1C, 0xD9, 0xFA, 0xBC, + 0x40, 0x92, 0x19, 0xD3, 0x6E, 0xEC, 0x19, 0xD3, + 0xC1, 0xEC, 0x0A, 0x65, 0x52, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x48, 0xA2, 0xC2, 0xD2, 0x0A, 0x65, + 0xEB, 0x43, 0x02, 0x99, 0xC4, 0x92, 0x09, 0xB3, + 0xBF, 0xFF, 0xC2, 0xD2, 0x41, 0x00, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x43, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x06, 0x92, 0x01, 0xD2, 0x0A, 0x65, + 0xF0, 0x6A, 0x0B, 0x97, 0x6F, 0xEC, 0x02, 0x99, + 0xC4, 0x98, 0xD3, 0xD8, 0x02, 0xD6, 0x0A, 0x03, + 0x02, 0x00, 0x01, 0x97, 0xC3, 0x98, 0x02, 0x96, + 0xC3, 0xD8, 0x01, 0x96, 0xC1, 0xD6, 0x1A, 0xD5, + 0x6E, 0xEC, 0xC5, 0x98, 0x14, 0x99, 0x6F, 0xEC, + 0xC2, 0xD8, 0x43, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x40, 0x92, + 0xC8, 0xD2, 0x40, 0xF0, 0xD9, 0xF5, 0x41, 0x00, + 0x11, 0x93, 0xC0, 0xEC, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0x13, 0xF6, 0x42, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0x10, 0xF6, 0x0A, 0x65, 0xFE, 0x7F, + 0x02, 0x97, 0xC3, 0x92, 0x42, 0xA2, 0xC2, 0xD2, + 0x40, 0x92, 0x19, 0xD3, 0xC0, 0xEC, 0x0A, 0x65, + 0xEB, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0xC0, 0x00, 0xC2, 0xD2, 0x0A, 0x65, 0xE9, 0x43, + 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, 0xBF, 0xFF, + 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x63, 0x20, 0x08, 0x0B, 0x01, 0x00, 0x11, 0x93, + 0xAF, 0xBC, 0x47, 0xB2, 0x59, 0x95, 0x5A, 0x95, + 0x12, 0xA5, 0xBF, 0xBC, 0x0A, 0xB3, 0x01, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x35, 0xF6, + 0x41, 0x04, 0x05, 0x93, 0x40, 0x96, 0x20, 0xD6, + 0x62, 0x97, 0x0F, 0x9F, 0x44, 0xF6, 0x14, 0x99, + 0xFC, 0xBC, 0xD1, 0xD8, 0x14, 0x99, 0xFE, 0xBC, + 0xD1, 0xD8, 0x20, 0x98, 0x42, 0x08, 0x20, 0xD8, + 0x20, 0x98, 0x03, 0x49, 0x02, 0x1E, 0x0F, 0x9F, + 0x3B, 0xF6, 0xC5, 0x92, 0x62, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0x5D, 0xF6, 0x02, 0x8E, 0x0F, 0x9F, + 0x57, 0xF6, 0x61, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x81, 0xF6, 0x0F, 0x9F, 0xAE, 0xF6, 0x63, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xA4, 0xF6, 0x0F, 0x9F, + 0xAE, 0xF6, 0x0D, 0x03, 0x01, 0x00, 0x0C, 0x99, + 0x71, 0xEC, 0x0B, 0x05, 0xFF, 0xFF, 0x40, 0x96, + 0x0F, 0x9F, 0x6A, 0xF6, 0xD1, 0x96, 0xD4, 0xD6, + 0x20, 0x96, 0x41, 0x06, 0x20, 0xD6, 0x02, 0x47, + 0x02, 0x1E, 0x0F, 0x9F, 0x66, 0xF6, 0x1A, 0xD5, + 0xC1, 0xEC, 0x0A, 0x65, 0xEB, 0x43, 0x02, 0x99, + 0xC4, 0x92, 0x09, 0xA3, 0xC0, 0x00, 0xC2, 0xD2, + 0x0A, 0x65, 0xE9, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0xBF, 0xFF, 0xC2, 0xD2, 0x0F, 0x9F, + 0xAE, 0xF6, 0x0A, 0x03, 0xFE, 0xFF, 0x61, 0x95, + 0x40, 0x98, 0x20, 0xD8, 0x02, 0x49, 0x02, 0x0E, + 0x0F, 0x9F, 0xAE, 0xF6, 0x0D, 0x03, 0x01, 0x00, + 0x21, 0xD2, 0x20, 0x92, 0x05, 0x03, 0x42, 0x02, + 0xC8, 0xD2, 0x21, 0x96, 0xC3, 0x92, 0x42, 0x06, + 0x21, 0xD6, 0xC8, 0xD2, 0x22, 0xD4, 0x40, 0xF0, + 0x01, 0xF1, 0x42, 0x00, 0x20, 0x98, 0x42, 0x08, + 0x20, 0xD8, 0x22, 0x94, 0x02, 0x49, 0x02, 0x1E, + 0x0F, 0x9F, 0x8D, 0xF6, 0x0F, 0x9F, 0xAE, 0xF6, + 0x0D, 0x03, 0x03, 0x00, 0xC8, 0xD2, 0x02, 0x92, + 0xC8, 0xD2, 0x01, 0x96, 0xC8, 0xD6, 0x40, 0xF0, + 0xB1, 0xF6, 0x43, 0x00, 0x63, 0x00, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x45, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x0D, 0x03, 0x08, 0x00, 0x08, 0x94, + 0xC5, 0xD4, 0x09, 0x05, 0x01, 0x00, 0xC2, 0x94, + 0x03, 0xD4, 0x42, 0x02, 0xC1, 0x92, 0x01, 0xD2, + 0x02, 0x97, 0xC5, 0x94, 0x0A, 0x83, 0xFF, 0xFF, + 0x11, 0xB3, 0x2C, 0x93, 0x09, 0xB3, 0xFB, 0xFF, + 0x19, 0xD3, 0x2C, 0x93, 0x03, 0x92, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xE4, 0xF6, 0x01, 0x94, + 0xD2, 0x92, 0x19, 0xD3, 0x2C, 0x93, 0x01, 0xD4, + 0x02, 0x94, 0x12, 0x95, 0x2C, 0x93, 0x44, 0xA4, + 0x1A, 0xD5, 0x2C, 0x93, 0x0A, 0xB5, 0xFB, 0xFF, + 0x1A, 0xD5, 0x2C, 0x93, 0x0B, 0x07, 0xFF, 0xFF, + 0x40, 0x46, 0x02, 0x5E, 0x0F, 0x9F, 0xCF, 0xF6, + 0x09, 0x63, 0xD4, 0x6C, 0x01, 0x95, 0xC2, 0x96, + 0xC5, 0x94, 0x02, 0xA7, 0xC1, 0xD6, 0x03, 0x92, + 0x54, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0xF4, 0xF6, + 0x0A, 0x83, 0xFF, 0xFF, 0x1B, 0xB3, 0x2C, 0x93, + 0x45, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x09, 0x63, 0x00, 0x40, + 0x19, 0xD3, 0xF2, 0xBD, 0x40, 0xF0, 0x3B, 0xF5, + 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0x08, 0xF7, + 0x40, 0xF0, 0x94, 0xF2, 0x0F, 0x9F, 0x16, 0xF7, + 0x40, 0x96, 0xC8, 0xD6, 0x09, 0x93, 0x91, 0xEC, + 0xC8, 0xD2, 0x40, 0xF0, 0x2A, 0xEF, 0x0A, 0x65, + 0xFE, 0x7F, 0x02, 0x97, 0xC3, 0x92, 0x44, 0xA2, + 0xC2, 0xD2, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x0A, 0x65, + 0xE8, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0x40, 0x00, 0xC2, 0xD2, 0x0A, 0x65, 0xEA, 0x43, + 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, 0xFB, 0xFF, + 0xC2, 0xD2, 0x40, 0x92, 0x19, 0xD3, 0x2D, 0xBC, + 0x0A, 0x65, 0xD8, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0xBF, 0xFF, 0xC2, 0xD2, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, + 0x09, 0x63, 0xEA, 0x43, 0x01, 0x97, 0xC3, 0x94, + 0x44, 0xA4, 0xC1, 0xD4, 0x11, 0x93, 0xB9, 0xEC, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x6F, 0xF7, + 0x12, 0x95, 0x93, 0xEC, 0x0B, 0x67, 0x36, 0x43, + 0xD2, 0x98, 0x1C, 0xD9, 0xC8, 0xBC, 0xD2, 0x98, + 0x03, 0x93, 0xC1, 0xD8, 0x11, 0x93, 0xB9, 0xEC, + 0x09, 0x03, 0xFF, 0xFF, 0x19, 0xD3, 0xB9, 0xEC, + 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0x48, 0xF7, + 0x19, 0xD3, 0xB8, 0xEC, 0x19, 0xD3, 0xBA, 0xEC, + 0x0A, 0x05, 0xFE, 0xFF, 0xCA, 0xD2, 0xCA, 0xD2, + 0xC2, 0xD2, 0x0A, 0x65, 0x5E, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x48, 0xA2, 0xC2, 0xD2, 0x0A, 0x65, + 0xEA, 0x43, 0x02, 0x99, 0xC4, 0x92, 0x09, 0xB3, + 0xFB, 0xFF, 0x0F, 0x9F, 0x78, 0xF7, 0x11, 0x93, + 0x03, 0xEC, 0x19, 0xD3, 0x01, 0x82, 0x0A, 0x65, + 0xFD, 0x7D, 0x02, 0x97, 0xC3, 0x92, 0x43, 0xA2, + 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x03, 0x92, 0x04, 0x96, + 0x0D, 0x5E, 0x50, 0x46, 0x02, 0x0E, 0x40, 0x92, + 0x09, 0xEE, 0x44, 0x46, 0x04, 0x0E, 0x59, 0x93, + 0x44, 0x26, 0x04, 0x5E, 0x46, 0xEE, 0x41, 0x93, + 0x41, 0x26, 0x43, 0x4E, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x40, 0xF0, + 0xB1, 0xFE, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x03, 0x94, + 0x1A, 0xD5, 0xA3, 0xF7, 0x11, 0x93, 0x00, 0x90, + 0x88, 0x98, 0x90, 0x9A, 0x1D, 0x00, 0x1A, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x18, 0x00, 0x19, 0x00, + 0x1A, 0x00, 0x1B, 0x00, 0x16, 0x00, 0x21, 0x00, + 0x12, 0x00, 0x09, 0x00, 0x13, 0x00, 0x19, 0x00, + 0x19, 0x00, 0x19, 0x00, 0x21, 0x00, 0x2D, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x69, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5F, 0xF2, 0xCD, 0xF7, 0x00, 0x00, 0x74, 0xF2, + 0xCD, 0xF7, 0x00, 0x00, 0xB9, 0xF2, 0xCA, 0xF7, + 0xD1, 0xF7, 0x00, 0x00, 0x97, 0xF3, 0xCD, 0xF7, + 0x05, 0x46, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* + * current zd1211b firmware version. + */ +#define ZD1211B_FIRMWARE_VER 4705 + +uint8_t zd1211b_firmware[] = { + 0x08, 0x91, 0xff, 0xed, 0x09, 0x93, 0x1e, 0xee, 0xd1, 0x94, 0x11, + 0xee, 0x88, 0xd4, 0xd1, 0x96, 0xd1, 0x98, 0x5c, 0x99, 0x5c, 0x99, + 0x4c, 0x99, 0x04, 0x9d, 0xd1, 0x98, 0xd1, 0x9a, 0x03, 0xee, 0xf4, + 0x94, 0xd3, 0xd4, 0x41, 0x2a, 0x40, 0x4a, 0x45, 0xbe, 0x88, 0x92, + 0x41, 0x24, 0x40, 0x44, 0x53, 0xbe, 0x40, 0xf0, 0x4e, 0xee, 0x41, + 0xee, 0x98, 0x9a, 0x72, 0xf7, 0x02, 0x00, 0x1f, 0xec, 0x00, 0x00, + 0xb2, 0xf8, 0x4d, 0x00, 0xa1, 0xec, 0x00, 0x00, 0x43, 0xf7, 0x22, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xd8, + 0xa0, 0x90, 0x98, 0x9a, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x5a, + 0xf0, 0xa0, 0x90, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x0a, 0xef, + 0xa0, 0x90, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x97, 0xf0, 0xa0, + 0x90, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x94, 0xf6, 0xa0, 0x90, + 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x95, 0xf5, 0xa0, 0x90, 0x98, + 0x9a, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x34, 0xf7, 0xa0, 0x90, + 0x98, 0x9a, 0x88, 0xda, 0x41, 0x20, 0x08, 0x0b, 0x01, 0x00, 0x40, + 0xf0, 0x8e, 0xee, 0x40, 0x96, 0x0a, 0x65, 0x00, 0x7d, 0x11, 0x93, + 0x76, 0xf7, 0x41, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x57, 0xee, 0x40, + 0xf1, 0x1b, 0xd7, 0x76, 0xf7, 0xc5, 0x92, 0x02, 0x99, 0x41, 0x92, + 0xc4, 0xd2, 0x40, 0x92, 0xc4, 0xd2, 0x0f, 0x9f, 0x95, 0xf8, 0x0f, + 0x9f, 0x57, 0xee, 0x41, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x40, 0x92, 0x19, 0xd3, 0x12, 0x95, 0x19, + 0xd3, 0x10, 0x95, 0x19, 0xd3, 0x02, 0x80, 0x19, 0xd3, 0x03, 0x82, + 0x09, 0x93, 0x65, 0xf7, 0x19, 0xd3, 0x91, 0xec, 0x40, 0xf0, 0x07, + 0xf2, 0x40, 0xf0, 0x75, 0xf3, 0x11, 0x93, 0x04, 0xec, 0x42, 0x42, + 0x02, 0x5e, 0x0f, 0x9f, 0x8c, 0xee, 0x40, 0x92, 0x19, 0xd3, 0x04, + 0xec, 0x40, 0xf0, 0xe0, 0xf1, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x11, 0x93, 0x44, 0x96, 0x09, 0xb3, 0xff, + 0xfd, 0x19, 0xd3, 0x44, 0x96, 0x40, 0xf0, 0x2d, 0xf7, 0x40, 0xf0, + 0x6d, 0xee, 0x4b, 0x62, 0x0a, 0x95, 0x2e, 0xee, 0xd1, 0xd4, 0x0b, + 0x97, 0x2b, 0xee, 0xd1, 0xd6, 0x0a, 0x95, 0x00, 0xee, 0xd1, 0xd4, + 0x0b, 0x97, 0x2f, 0xee, 0xd1, 0xd6, 0x0a, 0x95, 0x34, 0xee, 0xd1, + 0xd4, 0x0b, 0x97, 0x39, 0xee, 0xd1, 0xd6, 0x0a, 0x95, 0x3e, 0xee, + 0xd1, 0xd4, 0x0b, 0x97, 0x43, 0xee, 0xd1, 0xd6, 0x0a, 0x95, 0x2e, + 0xee, 0xd1, 0xd4, 0x0b, 0x97, 0x48, 0xee, 0xd1, 0xd6, 0x0a, 0x95, + 0x49, 0xee, 0xc1, 0xd4, 0x0a, 0x65, 0x00, 0x44, 0x02, 0x97, 0xc3, + 0x92, 0x44, 0xa2, 0xc2, 0xd2, 0x43, 0xf1, 0x09, 0x93, 0x01, 0x3f, + 0x19, 0xd3, 0xc0, 0x85, 0x11, 0x93, 0x44, 0x96, 0x09, 0xb3, 0xff, + 0xfc, 0x19, 0xd3, 0x44, 0x96, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x0d, 0x03, 0x03, 0x00, 0x03, 0x96, 0x41, + 0x02, 0x03, 0x99, 0xc4, 0x94, 0x42, 0x04, 0xc1, 0x04, 0xc2, 0x94, + 0xc3, 0xd4, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, + 0x00, 0x40, 0x92, 0x19, 0xd3, 0x94, 0xec, 0x13, 0x97, 0x95, 0xec, + 0x1b, 0xd7, 0x02, 0x80, 0x11, 0x93, 0x99, 0xec, 0x19, 0xd3, 0x7c, + 0x96, 0x0b, 0x97, 0xa0, 0x00, 0x1b, 0xd7, 0x6e, 0xec, 0x0a, 0x65, + 0x0e, 0x42, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xff, 0xbf, 0x11, + 0xa3, 0x9a, 0xec, 0xc2, 0xd2, 0x0a, 0x65, 0xeb, 0x43, 0x02, 0x97, + 0xc3, 0x92, 0x09, 0xa3, 0xc0, 0x00, 0xc2, 0xd2, 0x0a, 0x65, 0xe9, + 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xbf, 0xff, 0xc2, 0xd2, + 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x47, 0x20, 0x08, 0x0b, 0x01, + 0x00, 0x14, 0x99, 0x03, 0x80, 0x0c, 0xb3, 0x00, 0x10, 0x40, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0x3d, 0xf0, 0x11, 0x93, 0x9f, 0xec, 0x41, + 0x02, 0x19, 0xd3, 0x9f, 0xec, 0x11, 0x93, 0x74, 0xf7, 0x40, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0x2a, 0xef, 0x0a, 0x65, 0xfe, 0x7f, 0x02, + 0x97, 0xc3, 0x92, 0x09, 0xa3, 0x00, 0x04, 0xc2, 0xd2, 0x0f, 0x9f, + 0x57, 0xf0, 0x11, 0x93, 0x94, 0xec, 0x02, 0xd2, 0x40, 0x42, 0x02, + 0x5e, 0x0f, 0x9f, 0x76, 0xef, 0x41, 0x92, 0x19, 0xd3, 0x94, 0xec, + 0x19, 0xd3, 0x9f, 0xec, 0x12, 0x95, 0x02, 0x80, 0x1a, 0xd5, 0x95, + 0xec, 0x13, 0x97, 0x7c, 0x96, 0x1b, 0xd7, 0x99, 0xec, 0x0a, 0x65, + 0x0e, 0x42, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0x00, 0x40, 0x19, + 0xd3, 0x9a, 0xec, 0x09, 0x63, 0x00, 0x40, 0xc2, 0xd2, 0x02, 0x94, + 0x1a, 0xd5, 0x7c, 0x96, 0x0c, 0xb3, 0x00, 0x08, 0x40, 0x42, 0x02, + 0x5e, 0x0f, 0x9f, 0x56, 0xef, 0x0c, 0xb3, 0xff, 0x07, 0x0f, 0x9f, + 0x5a, 0xef, 0x11, 0x93, 0x06, 0x80, 0x09, 0xb3, 0xff, 0x07, 0x09, + 0x03, 0x00, 0xa0, 0x19, 0xd3, 0x97, 0xec, 0x40, 0x98, 0x0b, 0x97, + 0x9c, 0xec, 0x04, 0x95, 0x03, 0x05, 0x14, 0x03, 0x97, 0xec, 0x46, + 0x02, 0xc1, 0x92, 0xc2, 0xd2, 0x41, 0x08, 0x42, 0x48, 0x02, 0x9e, + 0x0f, 0x9f, 0x61, 0xef, 0x11, 0x93, 0x97, 0xec, 0xc1, 0x92, 0xc5, + 0xd2, 0x5f, 0xb2, 0x19, 0xd3, 0x9b, 0xec, 0x0f, 0x9f, 0x79, 0xef, + 0x13, 0x97, 0x98, 0xec, 0xc5, 0xd6, 0x11, 0x93, 0x03, 0x80, 0x09, + 0xb3, 0x00, 0x08, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x8f, 0xef, + 0x11, 0x93, 0x7a, 0xf7, 0x41, 0x02, 0x19, 0xd3, 0x7a, 0xf7, 0x11, + 0x93, 0x79, 0xf7, 0x09, 0xa3, 0x00, 0x10, 0x19, 0xd3, 0x79, 0xf7, + 0x40, 0x98, 0x1c, 0xd9, 0x9b, 0xec, 0x12, 0x95, 0x9b, 0xec, 0x40, + 0x44, 0x02, 0x4e, 0x0f, 0x9f, 0x2c, 0xf0, 0x0a, 0xb3, 0x08, 0x00, + 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xad, 0xef, 0x0a, 0xb3, 0x07, + 0x00, 0x09, 0x05, 0xa9, 0xec, 0xc2, 0x94, 0x01, 0xd4, 0x09, 0x03, + 0xa1, 0xec, 0xc1, 0x92, 0x19, 0xd3, 0x9b, 0xec, 0xc5, 0x94, 0x0a, + 0xb5, 0x00, 0xff, 0x01, 0xa5, 0xc5, 0xd4, 0x0f, 0x9f, 0xb9, 0xef, + 0x0a, 0x05, 0xff, 0xff, 0x0a, 0x03, 0xb1, 0xec, 0xc1, 0x92, 0x01, + 0xd2, 0x1a, 0xd5, 0x9b, 0xec, 0xc5, 0x96, 0x0b, 0x07, 0xff, 0xff, + 0xc5, 0xd6, 0x11, 0x93, 0x97, 0xec, 0xc5, 0x98, 0xc1, 0xd8, 0x11, + 0x93, 0x97, 0xec, 0x09, 0x05, 0x0b, 0x00, 0x03, 0xd4, 0xc2, 0x96, + 0x06, 0xd6, 0x7b, 0x95, 0x7a, 0x95, 0x4c, 0x02, 0xc1, 0x92, 0x59, + 0x93, 0x59, 0x93, 0x01, 0xa5, 0x01, 0x98, 0x0c, 0xf5, 0x7b, 0x93, + 0x09, 0x09, 0x01, 0x00, 0x06, 0x92, 0x09, 0xb3, 0xff, 0x00, 0x04, + 0xd2, 0x5c, 0x93, 0x59, 0x93, 0x04, 0x94, 0x01, 0xa5, 0x03, 0x96, + 0xc3, 0xd4, 0x11, 0x93, 0x97, 0xec, 0x4c, 0x02, 0x05, 0xd2, 0xc1, + 0x92, 0x09, 0xb3, 0x00, 0xff, 0x7c, 0x95, 0x7a, 0x95, 0x02, 0xa3, + 0x05, 0x98, 0xc4, 0xd2, 0x12, 0x95, 0x97, 0xec, 0x45, 0x04, 0x02, + 0x97, 0xc3, 0x92, 0x09, 0xa3, 0x00, 0x01, 0xc2, 0xd2, 0x12, 0x95, + 0x9b, 0xec, 0x0a, 0xb3, 0x08, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0x01, 0xf0, 0x12, 0x95, 0x97, 0xec, 0x4a, 0x04, 0x02, 0x99, + 0xc4, 0x92, 0x01, 0x98, 0x0c, 0xf3, 0x7b, 0x93, 0x41, 0x02, 0x0f, + 0x9f, 0x22, 0xf0, 0x43, 0x44, 0x02, 0x8e, 0x0f, 0x9f, 0x23, 0xf0, + 0x11, 0x93, 0x97, 0xec, 0x42, 0x02, 0x0a, 0x05, 0xff, 0xff, 0xc1, + 0xd4, 0x11, 0x93, 0x97, 0xec, 0x4a, 0x02, 0x12, 0x95, 0x60, 0x96, + 0xc1, 0xd4, 0x12, 0x95, 0x97, 0xec, 0x4b, 0x04, 0x02, 0x97, 0xc3, + 0x92, 0x09, 0xb3, 0x1f, 0xff, 0xc2, 0xd2, 0x12, 0x95, 0x97, 0xec, + 0x4b, 0x04, 0x11, 0x93, 0x62, 0x96, 0x41, 0x93, 0x59, 0x93, 0x02, + 0x99, 0xc4, 0xa2, 0xc2, 0xd2, 0xc5, 0x92, 0x19, 0xd3, 0x98, 0xec, + 0x0a, 0x95, 0x0c, 0x02, 0x1a, 0xd5, 0x02, 0x80, 0x0f, 0x9f, 0x57, + 0xf0, 0x09, 0x63, 0xfe, 0x7f, 0x01, 0x97, 0xc3, 0x94, 0x0a, 0xa5, + 0x00, 0x04, 0xc1, 0xd4, 0x11, 0x93, 0x9f, 0xec, 0x09, 0xa3, 0x00, + 0x01, 0x19, 0xd3, 0x9f, 0xec, 0x40, 0xf0, 0xdf, 0xee, 0x0f, 0x9f, + 0x57, 0xf0, 0x11, 0x93, 0x94, 0xec, 0x41, 0x42, 0x02, 0x5e, 0x0f, + 0x9f, 0x4c, 0xf0, 0x40, 0xf0, 0xdf, 0xee, 0x11, 0x93, 0x95, 0xec, + 0x44, 0xb2, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x57, 0xf0, 0x48, + 0x98, 0x1c, 0xd9, 0x02, 0x80, 0x11, 0x93, 0x91, 0xec, 0x41, 0x22, + 0x0a, 0x95, 0x57, 0xf0, 0x88, 0xd4, 0x88, 0xdc, 0x91, 0x9a, 0x47, + 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, + 0x11, 0x93, 0x04, 0x82, 0x48, 0xb2, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0x6e, 0xf0, 0x0a, 0x65, 0xfd, 0x7d, 0x02, 0x97, 0xc3, 0x92, + 0x09, 0xb3, 0xff, 0xfe, 0xc2, 0xd2, 0x41, 0x92, 0x19, 0xd3, 0xbf, + 0xec, 0x11, 0x93, 0x04, 0x82, 0x43, 0xb2, 0x12, 0x95, 0x03, 0x82, + 0x02, 0xb3, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x95, 0xf0, 0x0a, + 0xb3, 0x00, 0xff, 0x48, 0xa2, 0x19, 0xd3, 0x03, 0x82, 0x40, 0xf0, + 0x82, 0xf3, 0x11, 0x93, 0xbf, 0xec, 0x41, 0x42, 0x02, 0x5e, 0x0f, + 0x9f, 0x95, 0xf0, 0x11, 0x93, 0x07, 0x82, 0x11, 0x43, 0x03, 0xec, + 0x02, 0x0e, 0x0f, 0x9f, 0x95, 0xf0, 0x11, 0x93, 0x03, 0x82, 0x09, + 0xa3, 0x00, 0x01, 0x19, 0xd3, 0x03, 0x82, 0x40, 0x96, 0x1b, 0xd7, + 0xbf, 0xec, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, + 0x00, 0x11, 0x93, 0x20, 0xbc, 0xc8, 0xd2, 0x40, 0xf0, 0xe9, 0xf0, + 0x41, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x42, 0x20, 0x08, + 0x0b, 0x01, 0x00, 0x0d, 0x03, 0x05, 0x00, 0x05, 0x94, 0x41, 0x02, + 0xc1, 0x92, 0x01, 0x97, 0xc3, 0x96, 0xc2, 0xd6, 0x0a, 0x45, 0x00, + 0x95, 0x02, 0x5e, 0x0f, 0x9f, 0xe6, 0xf0, 0xc1, 0x92, 0x41, 0xb2, + 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xe6, 0xf0, 0x11, 0x93, 0xc0, + 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xe6, 0xf0, 0x41, 0x98, + 0x1c, 0xd9, 0xc0, 0xec, 0x12, 0x95, 0x02, 0x80, 0x01, 0xd4, 0x40, + 0xf0, 0xfe, 0xf1, 0x0b, 0x67, 0xfd, 0x7d, 0x03, 0x99, 0xc4, 0x92, + 0x0c, 0x99, 0x96, 0x03, 0x1c, 0xd9, 0x06, 0x82, 0x41, 0x98, 0x1c, + 0xd9, 0x02, 0x82, 0x42, 0x98, 0x1c, 0xd9, 0x05, 0x82, 0x0c, 0x69, + 0x80, 0x7f, 0x1c, 0xd9, 0x00, 0xb0, 0x09, 0xa3, 0x00, 0x01, 0xc3, + 0xd2, 0x01, 0x94, 0x0a, 0xb3, 0x04, 0x00, 0x40, 0x42, 0x02, 0x4e, + 0x0f, 0x9f, 0xe4, 0xf0, 0x42, 0xa4, 0x1a, 0xd5, 0x02, 0x80, 0x42, + 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x42, 0x20, 0x08, 0x0b, + 0x01, 0x00, 0x05, 0x92, 0xc5, 0xd2, 0x60, 0xb2, 0x40, 0x42, 0x02, + 0x4e, 0x0f, 0x9f, 0xf6, 0xf0, 0x40, 0xf0, 0xd2, 0xf6, 0xc5, 0x94, + 0x0a, 0xb3, 0x10, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xff, + 0xf0, 0x40, 0xf0, 0xc0, 0xf5, 0xc5, 0x96, 0x0b, 0xb3, 0x40, 0x00, + 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x08, 0xf1, 0x40, 0xf0, 0xfa, + 0xf4, 0xc5, 0x94, 0x0a, 0xb3, 0x01, 0x00, 0x40, 0x42, 0x02, 0x4e, + 0x0f, 0x9f, 0x70, 0xf1, 0x13, 0x97, 0x21, 0xbc, 0x01, 0xd6, 0x0b, + 0xb3, 0x02, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x1a, 0xf1, + 0x40, 0xf0, 0x62, 0xfb, 0x01, 0x94, 0x0a, 0xb3, 0x04, 0x00, 0x40, + 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x23, 0xf1, 0x40, 0xf0, 0x6c, 0xfb, + 0x01, 0x96, 0x0b, 0xb3, 0x01, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0x4c, 0xf1, 0x40, 0xf0, 0xb0, 0xfa, 0x41, 0x92, 0x19, 0xd3, + 0x73, 0xf7, 0x11, 0x93, 0x03, 0xec, 0x09, 0x43, 0x40, 0x00, 0x02, + 0x5e, 0x0f, 0x9f, 0x39, 0xf1, 0x40, 0x94, 0x1a, 0xd5, 0x73, 0xf7, + 0x11, 0x93, 0x00, 0xec, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x55, + 0xf1, 0x11, 0x93, 0xc1, 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, + 0x55, 0xf1, 0x40, 0xf0, 0xe0, 0xf1, 0x41, 0x96, 0x1b, 0xd7, 0xc1, + 0xec, 0x0f, 0x9f, 0x55, 0xf1, 0x01, 0x94, 0x0a, 0xb3, 0x08, 0x00, + 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x55, 0xf1, 0x40, 0xf0, 0x7c, + 0xfb, 0x01, 0x96, 0x0b, 0xb3, 0x10, 0x00, 0x40, 0x42, 0x02, 0x4e, + 0x0f, 0x9f, 0x5e, 0xf1, 0x40, 0xf0, 0x87, 0xfb, 0x11, 0x93, 0x10, + 0xec, 0x42, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x67, 0xf1, 0x44, 0x92, + 0x0f, 0x9f, 0x6b, 0xf1, 0x41, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x6d, + 0xf1, 0x19, 0xd3, 0x0b, 0xbc, 0x40, 0x94, 0x1a, 0xd5, 0x10, 0xec, + 0xc5, 0x96, 0x0b, 0xb3, 0x80, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0xba, 0xf1, 0x11, 0x93, 0x28, 0xbc, 0x01, 0xd2, 0x09, 0xb3, + 0x40, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x82, 0xf1, 0x40, + 0xf0, 0xb5, 0xf6, 0x01, 0x94, 0x0a, 0xb3, 0x02, 0x00, 0x40, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0x95, 0xf1, 0x40, 0xf0, 0x6d, 0xee, 0x40, + 0xf0, 0x8f, 0xfb, 0x40, 0xf0, 0xc3, 0xf1, 0x40, 0x96, 0x1b, 0xd7, + 0x00, 0xec, 0x41, 0x92, 0x19, 0xd3, 0x76, 0xf7, 0x01, 0x94, 0x0a, + 0xb3, 0x04, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xb1, 0xf1, + 0x40, 0xf0, 0x9e, 0xfb, 0x09, 0x63, 0x00, 0x44, 0x01, 0x97, 0xc3, + 0x94, 0x48, 0xa4, 0xc1, 0xd4, 0x00, 0xee, 0x40, 0x92, 0x19, 0xd3, + 0x12, 0x95, 0x19, 0xd3, 0x10, 0x95, 0x19, 0xd3, 0x02, 0x80, 0x19, + 0xd3, 0x03, 0x82, 0x41, 0x92, 0x19, 0xd3, 0x76, 0xf7, 0x01, 0x94, + 0x0a, 0xb3, 0x08, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xba, + 0xf1, 0x40, 0xf0, 0xae, 0xfb, 0x0a, 0x65, 0x00, 0x44, 0x02, 0x97, + 0xc3, 0x92, 0x44, 0xa2, 0xc2, 0xd2, 0x42, 0x00, 0x88, 0x98, 0x90, + 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x09, 0x63, 0x00, 0x40, + 0x19, 0xd3, 0xf2, 0xbd, 0x0a, 0x65, 0xea, 0x43, 0x02, 0x97, 0xc3, + 0x92, 0x44, 0xa2, 0xc2, 0xd2, 0x0a, 0x65, 0xe9, 0x43, 0x02, 0x97, + 0xc3, 0x92, 0x09, 0xa3, 0x40, 0x00, 0xc2, 0xd2, 0x0a, 0x65, 0xeb, + 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, 0xc0, 0x00, 0xc2, 0xd2, + 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x09, + 0x63, 0x00, 0x80, 0x19, 0xd3, 0xf2, 0xbd, 0x0a, 0x65, 0xe8, 0x43, + 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, 0xc0, 0x00, 0xc2, 0xd2, 0x0a, + 0x65, 0xeb, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xbf, 0xff, + 0xc2, 0xd2, 0x0a, 0x65, 0xea, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, + 0xb3, 0xfb, 0xff, 0xc2, 0xd2, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x09, 0x93, 0x00, 0x01, 0x19, 0xd3, 0x02, + 0x80, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, + 0x09, 0x93, 0x00, 0x09, 0x19, 0xd3, 0x02, 0x80, 0x40, 0xf0, 0xfe, + 0xf1, 0x40, 0x92, 0x19, 0xd3, 0x94, 0xec, 0xc8, 0xd2, 0x09, 0x93, + 0x91, 0xec, 0xc8, 0xd2, 0x40, 0xf0, 0xd0, 0xee, 0x42, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x40, 0xf0, + 0xd8, 0xf4, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x2d, 0xf2, 0x0a, + 0x65, 0xfe, 0x7f, 0x02, 0x97, 0xc3, 0x92, 0x44, 0xa2, 0xc2, 0xd2, + 0x0f, 0x9f, 0x3a, 0xf2, 0x40, 0xf0, 0x3c, 0xf2, 0x40, 0x42, 0x02, + 0x5e, 0x0f, 0x9f, 0x3a, 0xf2, 0xc8, 0xd2, 0x09, 0x93, 0x91, 0xec, + 0xc8, 0xd2, 0x40, 0xf0, 0xd0, 0xee, 0x42, 0x00, 0x88, 0x98, 0x90, + 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x11, 0x93, 0xf1, 0xbd, + 0x19, 0xd3, 0xb6, 0xec, 0x11, 0x93, 0xb4, 0xec, 0x40, 0x42, 0x02, + 0x5e, 0x0f, 0x9f, 0x54, 0xf2, 0x09, 0x63, 0x00, 0x80, 0x01, 0x97, + 0xc3, 0x94, 0x0a, 0x07, 0x07, 0x00, 0xc1, 0xd6, 0x0a, 0x05, 0x00, + 0xa0, 0x1a, 0xd5, 0x96, 0xec, 0x11, 0x93, 0xb6, 0xec, 0x19, 0xd3, + 0x01, 0x80, 0x0a, 0x65, 0xfe, 0x7f, 0x02, 0x97, 0xc3, 0x92, 0x41, + 0xa2, 0xc2, 0xd2, 0x40, 0x92, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x41, 0x20, 0x08, 0x0b, 0x01, 0x00, 0x13, 0x97, 0xb4, 0xec, 0x40, + 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0xc3, 0xf2, 0x12, 0x95, 0x96, 0xec, + 0x0a, 0x03, 0x07, 0x00, 0xc1, 0x92, 0xc2, 0xd2, 0x11, 0x93, 0x96, + 0xec, 0x09, 0x05, 0x01, 0x00, 0x48, 0x02, 0xc1, 0x92, 0xc2, 0xd2, + 0x11, 0x93, 0x96, 0xec, 0x4e, 0x02, 0xc1, 0x94, 0xc5, 0xd6, 0xc5, + 0x92, 0x11, 0x07, 0x96, 0xec, 0x0b, 0x03, 0x0f, 0x00, 0xc1, 0x98, + 0x46, 0x06, 0x7a, 0x93, 0x79, 0x93, 0x5c, 0x95, 0x5a, 0x95, 0x02, + 0xa3, 0xc3, 0xd2, 0x04, 0x95, 0xc5, 0x96, 0x41, 0x06, 0xc5, 0xd6, + 0x42, 0x46, 0x02, 0x9e, 0x0f, 0x9f, 0x7d, 0xf2, 0x11, 0x93, 0x96, + 0xec, 0x09, 0x05, 0x05, 0x00, 0x41, 0x02, 0xc1, 0x92, 0xc2, 0xd2, + 0x11, 0x93, 0x96, 0xec, 0xc1, 0x92, 0x09, 0xb5, 0x1f, 0x00, 0x43, + 0x44, 0x02, 0x8e, 0x0f, 0x9f, 0xaa, 0xf2, 0x40, 0x44, 0x02, 0x4e, + 0x0f, 0x9f, 0xab, 0xf2, 0x0a, 0x05, 0xff, 0xff, 0x0f, 0x9f, 0xab, + 0xf2, 0x43, 0x94, 0x11, 0x93, 0x96, 0xec, 0x42, 0x02, 0xc1, 0xd4, + 0x13, 0x97, 0x96, 0xec, 0x03, 0x93, 0xd1, 0x94, 0x7a, 0x95, 0x7a, + 0x95, 0xc1, 0x92, 0x59, 0x93, 0x59, 0x93, 0x01, 0x05, 0x49, 0x06, + 0xc3, 0x92, 0x7f, 0xb2, 0x01, 0x05, 0x1a, 0xd5, 0xb4, 0xec, 0x0a, + 0x05, 0xf2, 0xff, 0x1a, 0xd5, 0x92, 0xec, 0x11, 0x93, 0x92, 0xec, + 0x12, 0x95, 0xb6, 0xec, 0x02, 0x43, 0x02, 0x8e, 0x0f, 0x9f, 0x11, + 0xf3, 0x02, 0x0e, 0x0f, 0x9f, 0xe4, 0xf2, 0x11, 0x93, 0x7a, 0xf7, + 0x41, 0x02, 0x19, 0xd3, 0x7a, 0xf7, 0x11, 0x93, 0x79, 0xf7, 0x09, + 0xa3, 0x80, 0x00, 0x19, 0xd3, 0x79, 0xf7, 0x09, 0x63, 0x00, 0x80, + 0x01, 0x95, 0xc2, 0x94, 0x1a, 0xd5, 0xb5, 0xec, 0x40, 0x96, 0x1b, + 0xd7, 0xb4, 0xec, 0x0f, 0x9f, 0x29, 0xf3, 0x11, 0x93, 0x03, 0x80, + 0x09, 0xb3, 0x00, 0x40, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xf6, + 0xf2, 0x11, 0x93, 0xc0, 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, + 0xf6, 0xf2, 0x40, 0xf0, 0x3d, 0xf3, 0x0f, 0x9f, 0x2b, 0xf3, 0x41, + 0x92, 0xc8, 0xd2, 0x0a, 0x95, 0x91, 0xec, 0xc8, 0xd4, 0x40, 0xf0, + 0xd0, 0xee, 0x42, 0x00, 0x11, 0x93, 0xc0, 0xec, 0x40, 0x42, 0x02, + 0x4e, 0x0f, 0x9f, 0x09, 0xf3, 0x42, 0x96, 0x1b, 0xd7, 0xc0, 0xec, + 0x0f, 0x9f, 0x2b, 0xf3, 0x0a, 0x65, 0xfe, 0x7f, 0x02, 0x97, 0xc3, + 0x92, 0x42, 0xa2, 0xc2, 0xd2, 0x0f, 0x9f, 0x2b, 0xf3, 0x12, 0x45, + 0x03, 0xec, 0x02, 0x4e, 0x0f, 0x9f, 0x23, 0xf3, 0x11, 0x93, 0x7a, + 0xf7, 0x41, 0x02, 0x19, 0xd3, 0x7a, 0xf7, 0x11, 0x93, 0x79, 0xf7, + 0x09, 0xa3, 0x00, 0x08, 0x19, 0xd3, 0x79, 0xf7, 0x1a, 0xd5, 0x92, + 0xec, 0x11, 0x93, 0x92, 0xec, 0x19, 0x25, 0x92, 0xec, 0x09, 0x63, + 0x00, 0x80, 0x19, 0xd3, 0xf2, 0xbd, 0x41, 0x00, 0x88, 0x98, 0x90, + 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x40, 0xf0, 0x3d, 0xf3, + 0x40, 0x92, 0xc8, 0xd2, 0x09, 0x93, 0x91, 0xec, 0xc8, 0xd2, 0x40, + 0xf0, 0xd0, 0xee, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x11, 0x93, 0x75, 0xf7, 0x40, 0x42, 0x02, + 0x4e, 0x0f, 0x9f, 0x4d, 0xf3, 0x0a, 0x65, 0xbc, 0x69, 0x02, 0x97, + 0xc3, 0x92, 0x09, 0x83, 0x00, 0x02, 0xc2, 0xd2, 0x11, 0x93, 0x03, + 0x80, 0x09, 0xb3, 0x00, 0x40, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, + 0x60, 0xf3, 0x11, 0x93, 0x7a, 0xf7, 0x41, 0x02, 0x19, 0xd3, 0x7a, + 0xf7, 0x11, 0x93, 0x79, 0xf7, 0x09, 0xa3, 0x00, 0x20, 0x19, 0xd3, + 0x79, 0xf7, 0x11, 0x93, 0xb5, 0xec, 0x19, 0xd3, 0x04, 0x80, 0x12, + 0x95, 0xb4, 0xec, 0x1a, 0xd5, 0x05, 0x80, 0x09, 0x63, 0x00, 0x80, + 0x01, 0x97, 0xc3, 0x96, 0x1b, 0xd7, 0xb5, 0xec, 0x40, 0x94, 0x1a, + 0xd5, 0xb4, 0xec, 0x19, 0xd3, 0xf2, 0xbd, 0x88, 0x98, 0x90, 0x9a, + 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x09, 0x93, 0x96, 0x03, 0x19, + 0xd3, 0x06, 0x82, 0x09, 0x93, 0x00, 0x01, 0x19, 0xd3, 0x03, 0x82, + 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x47, 0x20, 0x08, 0x0b, 0x01, + 0x00, 0x11, 0x93, 0x01, 0x82, 0xc5, 0xd2, 0x40, 0x94, 0x01, 0xd4, + 0x13, 0x97, 0xb8, 0xec, 0x02, 0xd6, 0x03, 0x95, 0x0c, 0x99, 0xbb, + 0xec, 0x04, 0x05, 0x13, 0x97, 0x03, 0xec, 0x01, 0x27, 0x02, 0x99, + 0xc4, 0x92, 0x03, 0x03, 0xc2, 0xd2, 0x14, 0x99, 0xba, 0xec, 0x03, + 0x09, 0x1c, 0xd9, 0xba, 0xec, 0x12, 0x95, 0x04, 0x82, 0x0a, 0xb3, + 0x02, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xc6, 0xf4, 0x01, + 0x92, 0x03, 0xd2, 0x0a, 0xa3, 0x02, 0x00, 0x19, 0xd3, 0x04, 0x82, + 0x02, 0x96, 0x0b, 0x05, 0x01, 0x00, 0x1a, 0xd5, 0xb8, 0xec, 0xc5, + 0x92, 0x43, 0x42, 0x02, 0x9e, 0x0f, 0x9f, 0xce, 0xf3, 0x42, 0x44, + 0x02, 0x8e, 0x0f, 0x9f, 0xce, 0xf3, 0x11, 0x93, 0xbf, 0xec, 0x40, + 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xce, 0xf3, 0x0c, 0x49, 0xd3, 0x08, + 0x02, 0x8e, 0x0f, 0x9f, 0xce, 0xf3, 0x11, 0x63, 0x07, 0x82, 0x11, + 0xa3, 0x07, 0x82, 0x71, 0x93, 0x79, 0x93, 0x79, 0x93, 0x79, 0x93, + 0x03, 0xd2, 0xc5, 0x94, 0x0a, 0xb5, 0xfc, 0xff, 0x04, 0xd4, 0x03, + 0x96, 0x40, 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0xdd, 0xf3, 0x11, 0x93, + 0xb8, 0xec, 0x41, 0x42, 0x02, 0x8e, 0x0f, 0x9f, 0xe4, 0xf3, 0xc5, + 0x98, 0x0c, 0x03, 0xff, 0xff, 0x42, 0x42, 0x02, 0x8e, 0x0f, 0x9f, + 0x0b, 0xf4, 0x0a, 0x95, 0xbb, 0xec, 0x42, 0x92, 0x19, 0xd3, 0xb9, + 0xec, 0xc5, 0x96, 0x43, 0x46, 0x02, 0x9e, 0x0f, 0x9f, 0xfd, 0xf3, + 0x0b, 0x07, 0xfc, 0xff, 0xc5, 0xd6, 0xd2, 0x98, 0x1c, 0xd9, 0xc8, + 0xbc, 0xd2, 0x96, 0x1b, 0xd7, 0xca, 0xbc, 0x09, 0x03, 0xff, 0xff, + 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xe9, 0xf3, 0x19, 0xd3, 0xb9, + 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x09, 0xf4, 0x0a, 0x05, + 0xfe, 0xff, 0xca, 0xd2, 0xc2, 0xd2, 0x0f, 0x9f, 0x0b, 0xf4, 0x1a, + 0xd5, 0x93, 0xec, 0x03, 0x98, 0x40, 0x48, 0x02, 0x5e, 0x0f, 0x9f, + 0x38, 0xf4, 0x11, 0x93, 0xb8, 0xec, 0x41, 0x42, 0x02, 0x9e, 0x0f, + 0x9f, 0x1b, 0xf4, 0x04, 0x94, 0x48, 0x44, 0x02, 0x4e, 0x0f, 0x9f, + 0x26, 0xf4, 0x41, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x38, 0xf4, 0x11, + 0x93, 0x04, 0x82, 0x41, 0xb2, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, + 0x38, 0xf4, 0x41, 0x96, 0x01, 0xd6, 0x0a, 0x65, 0xbd, 0x43, 0x02, + 0x99, 0xc4, 0x92, 0x09, 0xa3, 0x80, 0x00, 0xc2, 0xd2, 0x0a, 0x65, + 0xe8, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xbf, 0xff, 0xc2, + 0xd2, 0x0f, 0x9f, 0x97, 0xf4, 0xc5, 0x98, 0x43, 0x48, 0x02, 0x9e, + 0x0f, 0x9f, 0x97, 0xf4, 0x4f, 0x96, 0x0c, 0xb3, 0x01, 0x00, 0x40, + 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x45, 0xf4, 0x47, 0x96, 0x11, 0x93, + 0xb7, 0xec, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x73, 0xf4, 0x11, + 0x93, 0xb8, 0xec, 0x41, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x73, 0xf4, + 0x12, 0x95, 0x00, 0x82, 0x0a, 0x05, 0xff, 0xaf, 0x05, 0xd4, 0xc8, + 0xd6, 0xc8, 0xd2, 0x40, 0xf0, 0x18, 0xf7, 0x42, 0x00, 0x05, 0x96, + 0xc3, 0x94, 0x01, 0xb5, 0x40, 0x44, 0x02, 0x5e, 0x0f, 0x9f, 0x68, + 0xf4, 0x11, 0x93, 0xba, 0xec, 0x4d, 0x42, 0x02, 0x8e, 0x0f, 0x9f, + 0x73, 0xf4, 0x06, 0x98, 0x50, 0x98, 0x1c, 0xd9, 0xa2, 0xbc, 0x40, + 0x98, 0x1c, 0xd9, 0xa2, 0xbc, 0x40, 0x92, 0x03, 0xd2, 0x0f, 0x9f, + 0x9c, 0xf4, 0x03, 0x94, 0x40, 0x44, 0x02, 0x5e, 0x0f, 0x9f, 0x80, + 0xf4, 0x0a, 0x65, 0x5e, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x48, 0xa2, + 0xc2, 0xd2, 0x0f, 0x9f, 0x9c, 0xf4, 0x11, 0x93, 0xb8, 0xec, 0x0c, + 0x99, 0xbb, 0xec, 0x04, 0x03, 0x04, 0x96, 0x13, 0x25, 0x03, 0xec, + 0xc1, 0xd4, 0x11, 0x93, 0xba, 0xec, 0x19, 0x05, 0xba, 0xec, 0x1b, + 0xd7, 0x01, 0x82, 0x0a, 0x65, 0xfd, 0x7d, 0x02, 0x99, 0xc4, 0x92, + 0x43, 0xa2, 0xc2, 0xd2, 0x41, 0x92, 0x01, 0xd2, 0x03, 0x94, 0x40, + 0x44, 0x02, 0x5e, 0x0f, 0x9f, 0xb0, 0xf4, 0x11, 0x93, 0xb9, 0xec, + 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xa8, 0xf4, 0x19, 0xd3, 0xb8, + 0xec, 0x19, 0xd3, 0xba, 0xec, 0x19, 0xd3, 0xbb, 0xec, 0x03, 0x96, + 0x40, 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0xb0, 0xf4, 0x41, 0x98, 0x1c, + 0xd9, 0xb7, 0xec, 0x11, 0x93, 0xbf, 0xec, 0x41, 0x42, 0x02, 0x5e, + 0x0f, 0x9f, 0xc1, 0xf4, 0x11, 0x93, 0x00, 0x82, 0x19, 0xd3, 0x02, + 0x82, 0x0a, 0x65, 0xfd, 0x7d, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, + 0x00, 0x01, 0xc2, 0xd2, 0x40, 0x98, 0x1c, 0xd9, 0xbf, 0xec, 0x0f, + 0x9f, 0xc9, 0xf4, 0x01, 0x92, 0x19, 0xd3, 0xb7, 0xec, 0x01, 0x94, + 0x40, 0x44, 0x02, 0x5e, 0x0f, 0x9f, 0xd5, 0xf4, 0x0a, 0x65, 0xea, + 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xfb, 0xff, 0xc2, 0xd2, + 0x47, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, + 0x00, 0x12, 0x95, 0x03, 0x80, 0x0a, 0xb3, 0x00, 0x40, 0x40, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0xf4, 0xf4, 0x0a, 0xb7, 0x00, 0x08, 0x40, + 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0xf7, 0xf4, 0x11, 0x93, 0x03, 0xec, + 0x41, 0x02, 0x09, 0xb3, 0xfe, 0xff, 0x12, 0x95, 0x07, 0x80, 0x01, + 0x45, 0x02, 0x8e, 0x0f, 0x9f, 0xf7, 0xf4, 0x41, 0x92, 0x0f, 0x9f, + 0xf8, 0xf4, 0x40, 0x92, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x41, + 0x20, 0x08, 0x0b, 0x01, 0x00, 0x0a, 0x65, 0xe9, 0x43, 0x02, 0x97, + 0xc3, 0x92, 0x09, 0xa3, 0x40, 0x00, 0xc2, 0xd2, 0x13, 0x97, 0x6e, + 0xec, 0x0b, 0x47, 0xa0, 0x00, 0x02, 0x5e, 0x0f, 0x9f, 0x23, 0xf5, + 0x09, 0x63, 0x08, 0x43, 0x0a, 0x65, 0xff, 0x5f, 0x01, 0x99, 0xc4, + 0xd4, 0x0a, 0x95, 0x9b, 0xec, 0xd2, 0x96, 0x1b, 0xd7, 0xfa, 0xbc, + 0xd2, 0x96, 0xc4, 0xd6, 0xd2, 0x98, 0x1c, 0xd9, 0xfa, 0xbc, 0xd2, + 0x96, 0xc1, 0xd6, 0xc2, 0x94, 0x1a, 0xd5, 0xfa, 0xbc, 0x0f, 0x9f, + 0x61, 0xf5, 0x0c, 0x69, 0xff, 0x6f, 0x1c, 0xd9, 0xf8, 0xbc, 0x0b, + 0x47, 0x10, 0x95, 0x02, 0x5e, 0x0f, 0x9f, 0x3b, 0xf5, 0x0a, 0x95, + 0x6f, 0xec, 0x09, 0x63, 0x06, 0x43, 0x01, 0x99, 0xc4, 0xd6, 0xd2, + 0x96, 0x1b, 0xd7, 0xf8, 0xbc, 0x0c, 0x69, 0xee, 0x6a, 0xc1, 0xd8, + 0xc2, 0x94, 0x1a, 0xd5, 0xf8, 0xbc, 0x40, 0x92, 0xc5, 0xd2, 0x11, + 0x43, 0xc2, 0xec, 0x02, 0x0e, 0x0f, 0x9f, 0x5e, 0xf5, 0xc5, 0x94, + 0x0a, 0x03, 0x71, 0xec, 0xc1, 0x94, 0x1a, 0xd5, 0xfa, 0xbc, 0x11, + 0x93, 0xc0, 0xec, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x50, 0xf5, + 0x0a, 0x95, 0x6f, 0xec, 0xc8, 0xd4, 0x40, 0xf0, 0x39, 0xf7, 0x19, + 0xd3, 0xf8, 0xbc, 0x41, 0x00, 0xc5, 0x96, 0x41, 0x06, 0xc5, 0xd6, + 0x13, 0x47, 0xc2, 0xec, 0x02, 0x1e, 0x0f, 0x9f, 0x42, 0xf5, 0x40, + 0x98, 0x1c, 0xd9, 0xfa, 0xbc, 0x40, 0x92, 0x19, 0xd3, 0x6e, 0xec, + 0x19, 0xd3, 0xc2, 0xec, 0x0a, 0x65, 0x52, 0x43, 0x02, 0x97, 0xc3, + 0x92, 0x48, 0xa2, 0xc2, 0xd2, 0x0a, 0x65, 0xeb, 0x43, 0x02, 0x99, + 0xc4, 0x92, 0x09, 0xb3, 0xbf, 0xff, 0xc2, 0xd2, 0x41, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x43, 0x20, 0x08, 0x0b, 0x01, 0x00, + 0x06, 0x92, 0x01, 0xd2, 0x0a, 0x65, 0xf0, 0x6a, 0x0b, 0x97, 0x6f, + 0xec, 0x02, 0x99, 0xc4, 0x98, 0xd3, 0xd8, 0x02, 0xd6, 0x0a, 0x03, + 0x02, 0x00, 0x01, 0x97, 0xc3, 0x98, 0x02, 0x96, 0xc3, 0xd8, 0x01, + 0x96, 0xc1, 0xd6, 0x1a, 0xd5, 0x6e, 0xec, 0xc5, 0x98, 0x14, 0x99, + 0x6f, 0xec, 0xc2, 0xd8, 0x43, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, + 0xda, 0x08, 0x0b, 0x01, 0x00, 0x40, 0x92, 0xc8, 0xd2, 0x40, 0xf0, + 0x76, 0xf5, 0x41, 0x00, 0x11, 0x93, 0xc0, 0xec, 0x40, 0x42, 0x02, + 0x4e, 0x0f, 0x9f, 0xb0, 0xf5, 0x42, 0x42, 0x02, 0x5e, 0x0f, 0x9f, + 0xad, 0xf5, 0x0a, 0x65, 0xfe, 0x7f, 0x02, 0x97, 0xc3, 0x92, 0x42, + 0xa2, 0xc2, 0xd2, 0x40, 0x92, 0x19, 0xd3, 0xc0, 0xec, 0x0a, 0x65, + 0xeb, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, 0xc0, 0x00, 0xc2, + 0xd2, 0x0a, 0x65, 0xe9, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, + 0xbf, 0xff, 0xc2, 0xd2, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x63, + 0x20, 0x08, 0x0b, 0x01, 0x00, 0x11, 0x93, 0xaf, 0xbc, 0x47, 0xb2, + 0x59, 0x95, 0x5a, 0x95, 0x12, 0xa5, 0xbf, 0xbc, 0x0a, 0xb3, 0x01, + 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xd2, 0xf5, 0x41, 0x04, + 0x05, 0x93, 0x40, 0x96, 0x20, 0xd6, 0x62, 0x97, 0x0f, 0x9f, 0xe1, + 0xf5, 0x14, 0x99, 0xfc, 0xbc, 0xd1, 0xd8, 0x14, 0x99, 0xfe, 0xbc, + 0xd1, 0xd8, 0x20, 0x98, 0x42, 0x08, 0x20, 0xd8, 0x20, 0x98, 0x03, + 0x49, 0x02, 0x1e, 0x0f, 0x9f, 0xd8, 0xf5, 0xc5, 0x92, 0x62, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0xfa, 0xf5, 0x02, 0x8e, 0x0f, 0x9f, 0xf4, + 0xf5, 0x61, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x1e, 0xf6, 0x0f, 0x9f, + 0x4b, 0xf6, 0x63, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x41, 0xf6, 0x0f, + 0x9f, 0x4b, 0xf6, 0x0d, 0x03, 0x01, 0x00, 0x0c, 0x99, 0x71, 0xec, + 0x0b, 0x05, 0xff, 0xff, 0x40, 0x96, 0x0f, 0x9f, 0x07, 0xf6, 0xd1, + 0x96, 0xd4, 0xd6, 0x20, 0x96, 0x41, 0x06, 0x20, 0xd6, 0x02, 0x47, + 0x02, 0x1e, 0x0f, 0x9f, 0x03, 0xf6, 0x1a, 0xd5, 0xc2, 0xec, 0x0a, + 0x65, 0xeb, 0x43, 0x02, 0x99, 0xc4, 0x92, 0x09, 0xa3, 0xc0, 0x00, + 0xc2, 0xd2, 0x0a, 0x65, 0xe9, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, + 0xb3, 0xbf, 0xff, 0xc2, 0xd2, 0x0f, 0x9f, 0x4b, 0xf6, 0x0a, 0x03, + 0xfe, 0xff, 0x61, 0x95, 0x40, 0x98, 0x20, 0xd8, 0x02, 0x49, 0x02, + 0x0e, 0x0f, 0x9f, 0x4b, 0xf6, 0x0d, 0x03, 0x01, 0x00, 0x21, 0xd2, + 0x20, 0x92, 0x05, 0x03, 0x42, 0x02, 0xc8, 0xd2, 0x21, 0x96, 0xc3, + 0x92, 0x42, 0x06, 0x21, 0xd6, 0xc8, 0xd2, 0x22, 0xd4, 0x40, 0xf0, + 0xa2, 0xf0, 0x42, 0x00, 0x20, 0x98, 0x42, 0x08, 0x20, 0xd8, 0x22, + 0x94, 0x02, 0x49, 0x02, 0x1e, 0x0f, 0x9f, 0x2a, 0xf6, 0x0f, 0x9f, + 0x4b, 0xf6, 0x0d, 0x03, 0x03, 0x00, 0xc8, 0xd2, 0x02, 0x92, 0xc8, + 0xd2, 0x01, 0x96, 0xc8, 0xd6, 0x40, 0xf0, 0x4e, 0xf6, 0x43, 0x00, + 0x63, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x45, 0x20, 0x08, + 0x0b, 0x01, 0x00, 0x0d, 0x03, 0x08, 0x00, 0x08, 0x94, 0xc5, 0xd4, + 0x09, 0x05, 0x01, 0x00, 0xc2, 0x94, 0x03, 0xd4, 0x42, 0x02, 0xc1, + 0x92, 0x01, 0xd2, 0x02, 0x97, 0xc5, 0x94, 0x0a, 0x83, 0xff, 0xff, + 0x11, 0xb3, 0x2c, 0x93, 0x09, 0xb3, 0xfb, 0xff, 0x19, 0xd3, 0x2c, + 0x93, 0x03, 0x92, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x81, 0xf6, + 0x01, 0x94, 0xd2, 0x92, 0x19, 0xd3, 0x2c, 0x93, 0x01, 0xd4, 0x02, + 0x94, 0x12, 0x95, 0x2c, 0x93, 0x44, 0xa4, 0x1a, 0xd5, 0x2c, 0x93, + 0x0a, 0xb5, 0xfb, 0xff, 0x1a, 0xd5, 0x2c, 0x93, 0x0b, 0x07, 0xff, + 0xff, 0x40, 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0x6c, 0xf6, 0x09, 0x63, + 0xd4, 0x6c, 0x01, 0x95, 0xc2, 0x96, 0xc5, 0x94, 0x02, 0xa7, 0xc1, + 0xd6, 0x03, 0x92, 0x54, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x91, 0xf6, + 0x0a, 0x83, 0xff, 0xff, 0x1b, 0xb3, 0x2c, 0x93, 0x45, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x09, 0x63, + 0x00, 0x40, 0x19, 0xd3, 0xf2, 0xbd, 0x40, 0xf0, 0xd8, 0xf4, 0x40, + 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xa5, 0xf6, 0x40, 0xf0, 0x3c, 0xf2, + 0x0f, 0x9f, 0xb3, 0xf6, 0x40, 0x96, 0xc8, 0xd6, 0x09, 0x93, 0x91, + 0xec, 0xc8, 0xd2, 0x40, 0xf0, 0xd0, 0xee, 0x0a, 0x65, 0xfe, 0x7f, + 0x02, 0x97, 0xc3, 0x92, 0x44, 0xa2, 0xc2, 0xd2, 0x42, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x0a, 0x65, + 0xe8, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, 0x40, 0x00, 0xc2, + 0xd2, 0x0a, 0x65, 0xea, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, + 0xfb, 0xff, 0xc2, 0xd2, 0x40, 0x92, 0x19, 0xd3, 0x2d, 0xbc, 0x0a, + 0x65, 0xd8, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xbf, 0xff, + 0xc2, 0xd2, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, + 0x00, 0x09, 0x63, 0xea, 0x43, 0x01, 0x97, 0xc3, 0x94, 0x44, 0xa4, + 0xc1, 0xd4, 0x11, 0x93, 0xb9, 0xec, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0x0c, 0xf7, 0x12, 0x95, 0x93, 0xec, 0x0b, 0x67, 0x36, 0x43, + 0xd2, 0x98, 0x1c, 0xd9, 0xc8, 0xbc, 0xd2, 0x98, 0x03, 0x93, 0xc1, + 0xd8, 0x11, 0x93, 0xb9, 0xec, 0x09, 0x03, 0xff, 0xff, 0x19, 0xd3, + 0xb9, 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xe5, 0xf6, 0x19, + 0xd3, 0xb8, 0xec, 0x19, 0xd3, 0xba, 0xec, 0x0a, 0x05, 0xfe, 0xff, + 0xca, 0xd2, 0xca, 0xd2, 0xc2, 0xd2, 0x0a, 0x65, 0x5e, 0x43, 0x02, + 0x97, 0xc3, 0x92, 0x48, 0xa2, 0xc2, 0xd2, 0x0a, 0x65, 0xea, 0x43, + 0x02, 0x99, 0xc4, 0x92, 0x09, 0xb3, 0xfb, 0xff, 0x0f, 0x9f, 0x15, + 0xf7, 0x11, 0x93, 0x03, 0xec, 0x19, 0xd3, 0x01, 0x82, 0x0a, 0x65, + 0xfd, 0x7d, 0x02, 0x97, 0xc3, 0x92, 0x43, 0xa2, 0xc2, 0xd2, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x03, 0x92, + 0x04, 0x96, 0x0d, 0x5e, 0x50, 0x46, 0x02, 0x0e, 0x40, 0x92, 0x09, + 0xee, 0x44, 0x46, 0x04, 0x0e, 0x59, 0x93, 0x44, 0x26, 0x04, 0x5e, + 0x46, 0xee, 0x41, 0x93, 0x41, 0x26, 0x43, 0x4e, 0x88, 0x98, 0x90, + 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x40, 0xf0, 0xb1, 0xfe, + 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x03, 0x94, + 0x1a, 0xd5, 0x40, 0xf7, 0x11, 0x93, 0x00, 0x90, 0x88, 0x98, 0x90, + 0x9a, 0x1d, 0x00, 0x1a, 0x00, 0x03, 0x00, 0x03, 0x00, 0x18, 0x00, + 0x19, 0x00, 0x1a, 0x00, 0x1b, 0x00, 0x16, 0x00, 0x21, 0x00, 0x12, + 0x00, 0x09, 0x00, 0x13, 0x00, 0x19, 0x00, 0x19, 0x00, 0x19, 0x00, + 0x21, 0x00, 0x2d, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7e, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf2, 0x6b, 0xf7, 0x00, 0x00, + 0x1c, 0xf2, 0x6b, 0xf7, 0x00, 0x00, 0x61, 0xf2, 0x68, 0xf7, 0x6f, + 0xf7, 0x00, 0x00, 0x2e, 0xf3, 0x6b, 0xf7, 0x25, 0x47, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00 +}; diff --git a/sys/bus/u4b/wlan/if_zydreg.h b/sys/bus/u4b/wlan/if_zydreg.h new file mode 100644 index 0000000000..dd11632bf2 --- /dev/null +++ b/sys/bus/u4b/wlan/if_zydreg.h @@ -0,0 +1,1313 @@ +/* $OpenBSD: if_zydreg.h,v 1.19 2006/11/30 19:28:07 damien Exp $ */ +/* $NetBSD: if_zydreg.h,v 1.2 2007/06/16 11:18:45 kiyohara Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2006 by Damien Bergamini + * Copyright (c) 2006 by Florian Stoehr + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * ZyDAS ZD1211/ZD1211B USB WLAN driver. + */ + +#define ZYD_CR_GPI_EN 0x9418 +#define ZYD_CR_RADIO_PD 0x942c +#define ZYD_CR_RF2948_PD 0x942c +#define ZYD_CR_EN_PS_MANUAL_AGC 0x943c +#define ZYD_CR_CONFIG_PHILIPS 0x9440 +#define ZYD_CR_I2C_WRITE 0x9444 +#define ZYD_CR_SA2400_SER_RP 0x9448 +#define ZYD_CR_RADIO_PE 0x9458 +#define ZYD_CR_RST_BUS_MASTER 0x945c +#define ZYD_CR_RFCFG 0x9464 +#define ZYD_CR_HSTSCHG 0x946c +#define ZYD_CR_PHY_ON 0x9474 +#define ZYD_CR_RX_DELAY 0x9478 +#define ZYD_CR_RX_PE_DELAY 0x947c +#define ZYD_CR_GPIO_1 0x9490 +#define ZYD_CR_GPIO_2 0x9494 +#define ZYD_CR_EnZYD_CRyBufMux 0x94a8 +#define ZYD_CR_PS_CTRL 0x9500 +#define ZYD_CR_ADDA_PWR_DWN 0x9504 +#define ZYD_CR_ADDA_MBIAS_WT 0x9508 +#define ZYD_CR_INTERRUPT 0x9510 +#define ZYD_CR_MAC_PS_STATE 0x950c +#define ZYD_CR_ATIM_WND_PERIOD 0x951c +#define ZYD_CR_BCN_INTERVAL 0x9520 +#define ZYD_CR_PRE_TBTT 0x9524 + +/* + * MAC registers. + */ +#define ZYD_MAC_MACADRL 0x9610 /* MAC address (low) */ +#define ZYD_MAC_MACADRH 0x9614 /* MAC address (high) */ +#define ZYD_MAC_BSSADRL 0x9618 /* BSS address (low) */ +#define ZYD_MAC_BSSADRH 0x961c /* BSS address (high) */ +#define ZYD_MAC_BCNCFG 0x9620 /* BCN configuration */ +#define ZYD_MAC_GHTBL 0x9624 /* Group hash table (low) */ +#define ZYD_MAC_GHTBH 0x9628 /* Group hash table (high) */ +#define ZYD_MAC_RX_TIMEOUT 0x962c /* Rx timeout value */ +#define ZYD_MAC_BAS_RATE 0x9630 /* Basic rate setting */ +#define ZYD_MAC_MAN_RATE 0x9634 /* Mandatory rate setting */ +#define ZYD_MAC_RTSCTSRATE 0x9638 /* RTS CTS rate */ +#define ZYD_MAC_BACKOFF_PROTECT 0x963c /* Backoff protection */ +#define ZYD_MAC_RX_THRESHOLD 0x9640 /* Rx threshold */ +#define ZYD_MAC_TX_PE_CONTROL 0x9644 /* Tx_PE control */ +#define ZYD_MAC_AFTER_PNP 0x9648 /* After PnP */ +#define ZYD_MAC_RX_PE_DELAY 0x964c /* Rx_pe delay */ +#define ZYD_MAC_RX_ADDR2_L 0x9650 /* RX address2 (low) */ +#define ZYD_MAC_RX_ADDR2_H 0x9654 /* RX address2 (high) */ +#define ZYD_MAC_SIFS_ACK_TIME 0x9658 /* Dynamic SIFS ack time */ +#define ZYD_MAC_PHY_DELAY 0x9660 /* PHY delay */ +#define ZYD_MAC_PHY_DELAY2 0x966c /* PHY delay */ +#define ZYD_MAC_BCNFIFO 0x9670 /* Beacon FIFO I/O port */ +#define ZYD_MAC_SNIFFER 0x9674 /* Sniffer on/off */ +#define ZYD_MAC_ENCRYPTION_TYPE 0x9678 /* Encryption type */ +#define ZYD_MAC_RETRY 0x967c /* Retry time */ +#define ZYD_MAC_MISC 0x9680 /* Misc */ +#define ZYD_MAC_STMACHINESTAT 0x9684 /* State machine status */ +#define ZYD_MAC_TX_UNDERRUN_CNT 0x9688 /* TX underrun counter */ +#define ZYD_MAC_RXFILTER 0x968c /* Send to host settings */ +#define ZYD_MAC_ACK_EXT 0x9690 /* Acknowledge extension */ +#define ZYD_MAC_BCNFIFOST 0x9694 /* BCN FIFO set and status */ +#define ZYD_MAC_DIFS_EIFS_SIFS 0x9698 /* DIFS, EIFS & SIFS settings */ +#define ZYD_MAC_RX_TIMEOUT_CNT 0x969c /* RX timeout count */ +#define ZYD_MAC_RX_TOTAL_FRAME 0x96a0 /* RX total frame count */ +#define ZYD_MAC_RX_CRC32_CNT 0x96a4 /* RX CRC32 frame count */ +#define ZYD_MAC_RX_CRC16_CNT 0x96a8 /* RX CRC16 frame count */ +#define ZYD_MAC_RX_UDEC 0x96ac /* RX unicast decr. error count */ +#define ZYD_MAC_RX_OVERRUN_CNT 0x96b0 /* RX FIFO overrun count */ +#define ZYD_MAC_RX_MDEC 0x96bc /* RX multicast decr. err. cnt. */ +#define ZYD_MAC_NAV_TCR 0x96c4 /* NAV timer count read */ +#define ZYD_MAC_BACKOFF_ST_RD 0x96c8 /* Backoff status read */ +#define ZYD_MAC_DM_RETRY_CNT_RD 0x96cc /* DM retry count read */ +#define ZYD_MAC_RX_ACR 0x96d0 /* RX arbitration count read */ +#define ZYD_MAC_TX_CCR 0x96d4 /* Tx complete count read */ +#define ZYD_MAC_TCB_ADDR 0x96e8 /* Current PCI process TCP addr */ +#define ZYD_MAC_RCB_ADDR 0x96ec /* Next RCB address */ +#define ZYD_MAC_CONT_WIN_LIMIT 0x96f0 /* Contention window limit */ +#define ZYD_MAC_TX_PKT 0x96f4 /* Tx total packet count read */ +#define ZYD_MAC_DL_CTRL 0x96f8 /* Download control */ +#define ZYD_MAC_CAM_MODE 0x9700 /* CAM: Continuous Access Mode */ +#define ZYD_MACB_TXPWR_CTL1 0x9b00 +#define ZYD_MACB_TXPWR_CTL2 0x9b04 +#define ZYD_MACB_TXPWR_CTL3 0x9b08 +#define ZYD_MACB_TXPWR_CTL4 0x9b0c +#define ZYD_MACB_AIFS_CTL1 0x9b10 +#define ZYD_MACB_AIFS_CTL2 0x9b14 +#define ZYD_MACB_TXOP 0x9b20 +#define ZYD_MACB_MAX_RETRY 0x9b28 + +/* + * Miscellanous registers. + */ +#define ZYD_FIRMWARE_START_ADDR 0xee00 +#define ZYD_FIRMWARE_BASE_ADDR 0xee1d /* Firmware base address */ + +/* + * EEPROM registers. + */ +#define ZYD_EEPROM_START_HEAD 0xf800 /* EEPROM start */ +#define ZYD_EEPROM_SUBID 0xf817 +#define ZYD_EEPROM_POD 0xf819 +#define ZYD_EEPROM_MAC_ADDR_P1 0xf81b /* Part 1 of the MAC address */ +#define ZYD_EEPROM_MAC_ADDR_P2 0xf81d /* Part 2 of the MAC address */ +#define ZYD_EEPROM_PWR_CAL 0xf81f /* Calibration */ +#define ZYD_EEPROM_PWR_INT 0xf827 /* Calibration */ +#define ZYD_EEPROM_ALLOWEDCHAN 0xf82f /* Allowed CH mask, 1 bit each */ +#define ZYD_EEPROM_DEVICE_VER 0xf837 /* Device version */ +#define ZYD_EEPROM_PHY_REG 0xf83c /* PHY registers */ +#define ZYD_EEPROM_36M_CAL 0xf83f /* Calibration */ +#define ZYD_EEPROM_11A_INT 0xf847 /* Interpolation */ +#define ZYD_EEPROM_48M_CAL 0xf84f /* Calibration */ +#define ZYD_EEPROM_48M_INT 0xf857 /* Interpolation */ +#define ZYD_EEPROM_54M_CAL 0xf85f /* Calibration */ +#define ZYD_EEPROM_54M_INT 0xf867 /* Interpolation */ + +/* + * Firmware registers offsets (relative to fwbase). + */ +#define ZYD_FW_FIRMWARE_REV 0x0000 /* Firmware version */ +#define ZYD_FW_USB_SPEED 0x0001 /* USB speed (!=0 if highspeed) */ +#define ZYD_FW_FIX_TX_RATE 0x0002 /* Fixed TX rate */ +#define ZYD_FW_LINK_STATUS 0x0003 +#define ZYD_FW_SOFT_RESET 0x0004 +#define ZYD_FW_FLASH_CHK 0x0005 + +/* possible flags for register ZYD_FW_LINK_STATUS */ +#define ZYD_LED1 (1 << 8) +#define ZYD_LED2 (1 << 9) + +/* + * RF IDs. + */ +#define ZYD_RF_UW2451 0x2 /* not supported yet */ +#define ZYD_RF_UCHIP 0x3 /* not supported yet */ +#define ZYD_RF_AL2230 0x4 +#define ZYD_RF_AL7230B 0x5 +#define ZYD_RF_THETA 0x6 /* not supported yet */ +#define ZYD_RF_AL2210 0x7 +#define ZYD_RF_MAXIM_NEW 0x8 +#define ZYD_RF_GCT 0x9 +#define ZYD_RF_AL2230S 0xa /* not supported yet */ +#define ZYD_RF_RALINK 0xb /* not supported yet */ +#define ZYD_RF_INTERSIL 0xc /* not supported yet */ +#define ZYD_RF_RFMD 0xd +#define ZYD_RF_MAXIM_NEW2 0xe +#define ZYD_RF_PHILIPS 0xf /* not supported yet */ + +/* + * PHY registers (8 bits, not documented). + */ +#define ZYD_CR0 0x9000 +#define ZYD_CR1 0x9004 +#define ZYD_CR2 0x9008 +#define ZYD_CR3 0x900c +#define ZYD_CR5 0x9010 +#define ZYD_CR6 0x9014 +#define ZYD_CR7 0x9018 +#define ZYD_CR8 0x901c +#define ZYD_CR4 0x9020 +#define ZYD_CR9 0x9024 +#define ZYD_CR10 0x9028 +#define ZYD_CR11 0x902c +#define ZYD_CR12 0x9030 +#define ZYD_CR13 0x9034 +#define ZYD_CR14 0x9038 +#define ZYD_CR15 0x903c +#define ZYD_CR16 0x9040 +#define ZYD_CR17 0x9044 +#define ZYD_CR18 0x9048 +#define ZYD_CR19 0x904c +#define ZYD_CR20 0x9050 +#define ZYD_CR21 0x9054 +#define ZYD_CR22 0x9058 +#define ZYD_CR23 0x905c +#define ZYD_CR24 0x9060 +#define ZYD_CR25 0x9064 +#define ZYD_CR26 0x9068 +#define ZYD_CR27 0x906c +#define ZYD_CR28 0x9070 +#define ZYD_CR29 0x9074 +#define ZYD_CR30 0x9078 +#define ZYD_CR31 0x907c +#define ZYD_CR32 0x9080 +#define ZYD_CR33 0x9084 +#define ZYD_CR34 0x9088 +#define ZYD_CR35 0x908c +#define ZYD_CR36 0x9090 +#define ZYD_CR37 0x9094 +#define ZYD_CR38 0x9098 +#define ZYD_CR39 0x909c +#define ZYD_CR40 0x90a0 +#define ZYD_CR41 0x90a4 +#define ZYD_CR42 0x90a8 +#define ZYD_CR43 0x90ac +#define ZYD_CR44 0x90b0 +#define ZYD_CR45 0x90b4 +#define ZYD_CR46 0x90b8 +#define ZYD_CR47 0x90bc +#define ZYD_CR48 0x90c0 +#define ZYD_CR49 0x90c4 +#define ZYD_CR50 0x90c8 +#define ZYD_CR51 0x90cc +#define ZYD_CR52 0x90d0 +#define ZYD_CR53 0x90d4 +#define ZYD_CR54 0x90d8 +#define ZYD_CR55 0x90dc +#define ZYD_CR56 0x90e0 +#define ZYD_CR57 0x90e4 +#define ZYD_CR58 0x90e8 +#define ZYD_CR59 0x90ec +#define ZYD_CR60 0x90f0 +#define ZYD_CR61 0x90f4 +#define ZYD_CR62 0x90f8 +#define ZYD_CR63 0x90fc +#define ZYD_CR64 0x9100 +#define ZYD_CR65 0x9104 +#define ZYD_CR66 0x9108 +#define ZYD_CR67 0x910c +#define ZYD_CR68 0x9110 +#define ZYD_CR69 0x9114 +#define ZYD_CR70 0x9118 +#define ZYD_CR71 0x911c +#define ZYD_CR72 0x9120 +#define ZYD_CR73 0x9124 +#define ZYD_CR74 0x9128 +#define ZYD_CR75 0x912c +#define ZYD_CR76 0x9130 +#define ZYD_CR77 0x9134 +#define ZYD_CR78 0x9138 +#define ZYD_CR79 0x913c +#define ZYD_CR80 0x9140 +#define ZYD_CR81 0x9144 +#define ZYD_CR82 0x9148 +#define ZYD_CR83 0x914c +#define ZYD_CR84 0x9150 +#define ZYD_CR85 0x9154 +#define ZYD_CR86 0x9158 +#define ZYD_CR87 0x915c +#define ZYD_CR88 0x9160 +#define ZYD_CR89 0x9164 +#define ZYD_CR90 0x9168 +#define ZYD_CR91 0x916c +#define ZYD_CR92 0x9170 +#define ZYD_CR93 0x9174 +#define ZYD_CR94 0x9178 +#define ZYD_CR95 0x917c +#define ZYD_CR96 0x9180 +#define ZYD_CR97 0x9184 +#define ZYD_CR98 0x9188 +#define ZYD_CR99 0x918c +#define ZYD_CR100 0x9190 +#define ZYD_CR101 0x9194 +#define ZYD_CR102 0x9198 +#define ZYD_CR103 0x919c +#define ZYD_CR104 0x91a0 +#define ZYD_CR105 0x91a4 +#define ZYD_CR106 0x91a8 +#define ZYD_CR107 0x91ac +#define ZYD_CR108 0x91b0 +#define ZYD_CR109 0x91b4 +#define ZYD_CR110 0x91b8 +#define ZYD_CR111 0x91bc +#define ZYD_CR112 0x91c0 +#define ZYD_CR113 0x91c4 +#define ZYD_CR114 0x91c8 +#define ZYD_CR115 0x91cc +#define ZYD_CR116 0x91d0 +#define ZYD_CR117 0x91d4 +#define ZYD_CR118 0x91d8 +#define ZYD_CR119 0x91dc +#define ZYD_CR120 0x91e0 +#define ZYD_CR121 0x91e4 +#define ZYD_CR122 0x91e8 +#define ZYD_CR123 0x91ec +#define ZYD_CR124 0x91f0 +#define ZYD_CR125 0x91f4 +#define ZYD_CR126 0x91f8 +#define ZYD_CR127 0x91fc +#define ZYD_CR128 0x9200 +#define ZYD_CR129 0x9204 +#define ZYD_CR130 0x9208 +#define ZYD_CR131 0x920c +#define ZYD_CR132 0x9210 +#define ZYD_CR133 0x9214 +#define ZYD_CR134 0x9218 +#define ZYD_CR135 0x921c +#define ZYD_CR136 0x9220 +#define ZYD_CR137 0x9224 +#define ZYD_CR138 0x9228 +#define ZYD_CR139 0x922c +#define ZYD_CR140 0x9230 +#define ZYD_CR141 0x9234 +#define ZYD_CR142 0x9238 +#define ZYD_CR143 0x923c +#define ZYD_CR144 0x9240 +#define ZYD_CR145 0x9244 +#define ZYD_CR146 0x9248 +#define ZYD_CR147 0x924c +#define ZYD_CR148 0x9250 +#define ZYD_CR149 0x9254 +#define ZYD_CR150 0x9258 +#define ZYD_CR151 0x925c +#define ZYD_CR152 0x9260 +#define ZYD_CR153 0x9264 +#define ZYD_CR154 0x9268 +#define ZYD_CR155 0x926c +#define ZYD_CR156 0x9270 +#define ZYD_CR157 0x9274 +#define ZYD_CR158 0x9278 +#define ZYD_CR159 0x927c +#define ZYD_CR160 0x9280 +#define ZYD_CR161 0x9284 +#define ZYD_CR162 0x9288 +#define ZYD_CR163 0x928c +#define ZYD_CR164 0x9290 +#define ZYD_CR165 0x9294 +#define ZYD_CR166 0x9298 +#define ZYD_CR167 0x929c +#define ZYD_CR168 0x92a0 +#define ZYD_CR169 0x92a4 +#define ZYD_CR170 0x92a8 +#define ZYD_CR171 0x92ac +#define ZYD_CR172 0x92b0 +#define ZYD_CR173 0x92b4 +#define ZYD_CR174 0x92b8 +#define ZYD_CR175 0x92bc +#define ZYD_CR176 0x92c0 +#define ZYD_CR177 0x92c4 +#define ZYD_CR178 0x92c8 +#define ZYD_CR179 0x92cc +#define ZYD_CR180 0x92d0 +#define ZYD_CR181 0x92d4 +#define ZYD_CR182 0x92d8 +#define ZYD_CR183 0x92dc +#define ZYD_CR184 0x92e0 +#define ZYD_CR185 0x92e4 +#define ZYD_CR186 0x92e8 +#define ZYD_CR187 0x92ec +#define ZYD_CR188 0x92f0 +#define ZYD_CR189 0x92f4 +#define ZYD_CR190 0x92f8 +#define ZYD_CR191 0x92fc +#define ZYD_CR192 0x9300 +#define ZYD_CR193 0x9304 +#define ZYD_CR194 0x9308 +#define ZYD_CR195 0x930c +#define ZYD_CR196 0x9310 +#define ZYD_CR197 0x9314 +#define ZYD_CR198 0x9318 +#define ZYD_CR199 0x931c +#define ZYD_CR200 0x9320 +#define ZYD_CR201 0x9324 +#define ZYD_CR202 0x9328 +#define ZYD_CR203 0x932c +#define ZYD_CR204 0x9330 +#define ZYD_CR205 0x9334 +#define ZYD_CR206 0x9338 +#define ZYD_CR207 0x933c +#define ZYD_CR208 0x9340 +#define ZYD_CR209 0x9344 +#define ZYD_CR210 0x9348 +#define ZYD_CR211 0x934c +#define ZYD_CR212 0x9350 +#define ZYD_CR213 0x9354 +#define ZYD_CR214 0x9358 +#define ZYD_CR215 0x935c +#define ZYD_CR216 0x9360 +#define ZYD_CR217 0x9364 +#define ZYD_CR218 0x9368 +#define ZYD_CR219 0x936c +#define ZYD_CR220 0x9370 +#define ZYD_CR221 0x9374 +#define ZYD_CR222 0x9378 +#define ZYD_CR223 0x937c +#define ZYD_CR224 0x9380 +#define ZYD_CR225 0x9384 +#define ZYD_CR226 0x9388 +#define ZYD_CR227 0x938c +#define ZYD_CR228 0x9390 +#define ZYD_CR229 0x9394 +#define ZYD_CR230 0x9398 +#define ZYD_CR231 0x939c +#define ZYD_CR232 0x93a0 +#define ZYD_CR233 0x93a4 +#define ZYD_CR234 0x93a8 +#define ZYD_CR235 0x93ac +#define ZYD_CR236 0x93b0 +#define ZYD_CR240 0x93c0 +#define ZYD_CR241 0x93c4 +#define ZYD_CR242 0x93c8 +#define ZYD_CR243 0x93cc +#define ZYD_CR244 0x93d0 +#define ZYD_CR245 0x93d4 +#define ZYD_CR251 0x93ec +#define ZYD_CR252 0x93f0 +#define ZYD_CR253 0x93f4 +#define ZYD_CR254 0x93f8 +#define ZYD_CR255 0x93fc + +/* copied nearly verbatim from the Linux driver rewrite */ +#define ZYD_DEF_PHY \ +{ \ + { ZYD_CR0, 0x0a }, { ZYD_CR1, 0x06 }, { ZYD_CR2, 0x26 }, \ + { ZYD_CR3, 0x38 }, { ZYD_CR4, 0x80 }, { ZYD_CR9, 0xa0 }, \ + { ZYD_CR10, 0x81 }, { ZYD_CR11, 0x00 }, { ZYD_CR12, 0x7f }, \ + { ZYD_CR13, 0x8c }, { ZYD_CR14, 0x80 }, { ZYD_CR15, 0x3d }, \ + { ZYD_CR16, 0x20 }, { ZYD_CR17, 0x1e }, { ZYD_CR18, 0x0a }, \ + { ZYD_CR19, 0x48 }, { ZYD_CR20, 0x0c }, { ZYD_CR21, 0x0c }, \ + { ZYD_CR22, 0x23 }, { ZYD_CR23, 0x90 }, { ZYD_CR24, 0x14 }, \ + { ZYD_CR25, 0x40 }, { ZYD_CR26, 0x10 }, { ZYD_CR27, 0x19 }, \ + { ZYD_CR28, 0x7f }, { ZYD_CR29, 0x80 }, { ZYD_CR30, 0x4b }, \ + { ZYD_CR31, 0x60 }, { ZYD_CR32, 0x43 }, { ZYD_CR33, 0x08 }, \ + { ZYD_CR34, 0x06 }, { ZYD_CR35, 0x0a }, { ZYD_CR36, 0x00 }, \ + { ZYD_CR37, 0x00 }, { ZYD_CR38, 0x38 }, { ZYD_CR39, 0x0c }, \ + { ZYD_CR40, 0x84 }, { ZYD_CR41, 0x2a }, { ZYD_CR42, 0x80 }, \ + { ZYD_CR43, 0x10 }, { ZYD_CR44, 0x12 }, { ZYD_CR46, 0xff }, \ + { ZYD_CR47, 0x1e }, { ZYD_CR48, 0x26 }, { ZYD_CR49, 0x5b }, \ + { ZYD_CR64, 0xd0 }, { ZYD_CR65, 0x04 }, { ZYD_CR66, 0x58 }, \ + { ZYD_CR67, 0xc9 }, { ZYD_CR68, 0x88 }, { ZYD_CR69, 0x41 }, \ + { ZYD_CR70, 0x23 }, { ZYD_CR71, 0x10 }, { ZYD_CR72, 0xff }, \ + { ZYD_CR73, 0x32 }, { ZYD_CR74, 0x30 }, { ZYD_CR75, 0x65 }, \ + { ZYD_CR76, 0x41 }, { ZYD_CR77, 0x1b }, { ZYD_CR78, 0x30 }, \ + { ZYD_CR79, 0x68 }, { ZYD_CR80, 0x64 }, { ZYD_CR81, 0x64 }, \ + { ZYD_CR82, 0x00 }, { ZYD_CR83, 0x00 }, { ZYD_CR84, 0x00 }, \ + { ZYD_CR85, 0x02 }, { ZYD_CR86, 0x00 }, { ZYD_CR87, 0x00 }, \ + { ZYD_CR88, 0xff }, { ZYD_CR89, 0xfc }, { ZYD_CR90, 0x00 }, \ + { ZYD_CR91, 0x00 }, { ZYD_CR92, 0x00 }, { ZYD_CR93, 0x08 }, \ + { ZYD_CR94, 0x00 }, { ZYD_CR95, 0x00 }, { ZYD_CR96, 0xff }, \ + { ZYD_CR97, 0xe7 }, { ZYD_CR98, 0x00 }, { ZYD_CR99, 0x00 }, \ + { ZYD_CR100, 0x00 }, { ZYD_CR101, 0xae }, { ZYD_CR102, 0x02 }, \ + { ZYD_CR103, 0x00 }, { ZYD_CR104, 0x03 }, { ZYD_CR105, 0x65 }, \ + { ZYD_CR106, 0x04 }, { ZYD_CR107, 0x00 }, { ZYD_CR108, 0x0a }, \ + { ZYD_CR109, 0xaa }, { ZYD_CR110, 0xaa }, { ZYD_CR111, 0x25 }, \ + { ZYD_CR112, 0x25 }, { ZYD_CR113, 0x00 }, { ZYD_CR119, 0x1e }, \ + { ZYD_CR125, 0x90 }, { ZYD_CR126, 0x00 }, { ZYD_CR127, 0x00 }, \ + { ZYD_CR5, 0x00 }, { ZYD_CR6, 0x00 }, { ZYD_CR7, 0x00 }, \ + { ZYD_CR8, 0x00 }, { ZYD_CR9, 0x20 }, { ZYD_CR12, 0xf0 }, \ + { ZYD_CR20, 0x0e }, { ZYD_CR21, 0x0e }, { ZYD_CR27, 0x10 }, \ + { ZYD_CR44, 0x33 }, { ZYD_CR47, 0x1E }, { ZYD_CR83, 0x24 }, \ + { ZYD_CR84, 0x04 }, { ZYD_CR85, 0x00 }, { ZYD_CR86, 0x0C }, \ + { ZYD_CR87, 0x12 }, { ZYD_CR88, 0x0C }, { ZYD_CR89, 0x00 }, \ + { ZYD_CR90, 0x10 }, { ZYD_CR91, 0x08 }, { ZYD_CR93, 0x00 }, \ + { ZYD_CR94, 0x01 }, { ZYD_CR95, 0x00 }, { ZYD_CR96, 0x50 }, \ + { ZYD_CR97, 0x37 }, { ZYD_CR98, 0x35 }, { ZYD_CR101, 0x13 }, \ + { ZYD_CR102, 0x27 }, { ZYD_CR103, 0x27 }, { ZYD_CR104, 0x18 }, \ + { ZYD_CR105, 0x12 }, { ZYD_CR109, 0x27 }, { ZYD_CR110, 0x27 }, \ + { ZYD_CR111, 0x27 }, { ZYD_CR112, 0x27 }, { ZYD_CR113, 0x27 }, \ + { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x26 }, { ZYD_CR116, 0x24 }, \ + { ZYD_CR117, 0xfc }, { ZYD_CR118, 0xfa }, { ZYD_CR120, 0x4f }, \ + { ZYD_CR125, 0xaa }, { ZYD_CR127, 0x03 }, { ZYD_CR128, 0x14 }, \ + { ZYD_CR129, 0x12 }, { ZYD_CR130, 0x10 }, { ZYD_CR131, 0x0C }, \ + { ZYD_CR136, 0xdf }, { ZYD_CR137, 0x40 }, { ZYD_CR138, 0xa0 }, \ + { ZYD_CR139, 0xb0 }, { ZYD_CR140, 0x99 }, { ZYD_CR141, 0x82 }, \ + { ZYD_CR142, 0x54 }, { ZYD_CR143, 0x1c }, { ZYD_CR144, 0x6c }, \ + { ZYD_CR147, 0x07 }, { ZYD_CR148, 0x4c }, { ZYD_CR149, 0x50 }, \ + { ZYD_CR150, 0x0e }, { ZYD_CR151, 0x18 }, { ZYD_CR160, 0xfe }, \ + { ZYD_CR161, 0xee }, { ZYD_CR162, 0xaa }, { ZYD_CR163, 0xfa }, \ + { ZYD_CR164, 0xfa }, { ZYD_CR165, 0xea }, { ZYD_CR166, 0xbe }, \ + { ZYD_CR167, 0xbe }, { ZYD_CR168, 0x6a }, { ZYD_CR169, 0xba }, \ + { ZYD_CR170, 0xba }, { ZYD_CR171, 0xba }, { ZYD_CR204, 0x7d }, \ + { ZYD_CR203, 0x30 }, { 0, 0} \ +} + +#define ZYD_DEF_PHYB \ +{ \ + { ZYD_CR0, 0x14 }, { ZYD_CR1, 0x06 }, { ZYD_CR2, 0x26 }, \ + { ZYD_CR3, 0x38 }, { ZYD_CR4, 0x80 }, { ZYD_CR9, 0xe0 }, \ + { ZYD_CR10, 0x81 }, { ZYD_CR11, 0x00 }, { ZYD_CR12, 0xf0 }, \ + { ZYD_CR13, 0x8c }, { ZYD_CR14, 0x80 }, { ZYD_CR15, 0x3d }, \ + { ZYD_CR16, 0x20 }, { ZYD_CR17, 0x1e }, { ZYD_CR18, 0x0a }, \ + { ZYD_CR19, 0x48 }, { ZYD_CR20, 0x10 }, { ZYD_CR21, 0x0e }, \ + { ZYD_CR22, 0x23 }, { ZYD_CR23, 0x90 }, { ZYD_CR24, 0x14 }, \ + { ZYD_CR25, 0x40 }, { ZYD_CR26, 0x10 }, { ZYD_CR27, 0x10 }, \ + { ZYD_CR28, 0x7f }, { ZYD_CR29, 0x80 }, { ZYD_CR30, 0x4b }, \ + { ZYD_CR31, 0x60 }, { ZYD_CR32, 0x43 }, { ZYD_CR33, 0x08 }, \ + { ZYD_CR34, 0x06 }, { ZYD_CR35, 0x0a }, { ZYD_CR36, 0x00 }, \ + { ZYD_CR37, 0x00 }, { ZYD_CR38, 0x38 }, { ZYD_CR39, 0x0c }, \ + { ZYD_CR40, 0x84 }, { ZYD_CR41, 0x2a }, { ZYD_CR42, 0x80 }, \ + { ZYD_CR43, 0x10 }, { ZYD_CR44, 0x33 }, { ZYD_CR46, 0xff }, \ + { ZYD_CR47, 0x1E }, { ZYD_CR48, 0x26 }, { ZYD_CR49, 0x5b }, \ + { ZYD_CR64, 0xd0 }, { ZYD_CR65, 0x04 }, { ZYD_CR66, 0x58 }, \ + { ZYD_CR67, 0xc9 }, { ZYD_CR68, 0x88 }, { ZYD_CR69, 0x41 }, \ + { ZYD_CR70, 0x23 }, { ZYD_CR71, 0x10 }, { ZYD_CR72, 0xff }, \ + { ZYD_CR73, 0x32 }, { ZYD_CR74, 0x30 }, { ZYD_CR75, 0x65 }, \ + { ZYD_CR76, 0x41 }, { ZYD_CR77, 0x1b }, { ZYD_CR78, 0x30 }, \ + { ZYD_CR79, 0xf0 }, { ZYD_CR80, 0x64 }, { ZYD_CR81, 0x64 }, \ + { ZYD_CR82, 0x00 }, { ZYD_CR83, 0x24 }, { ZYD_CR84, 0x04 }, \ + { ZYD_CR85, 0x00 }, { ZYD_CR86, 0x0c }, { ZYD_CR87, 0x12 }, \ + { ZYD_CR88, 0x0c }, { ZYD_CR89, 0x00 }, { ZYD_CR90, 0x58 }, \ + { ZYD_CR91, 0x04 }, { ZYD_CR92, 0x00 }, { ZYD_CR93, 0x00 }, \ + { ZYD_CR94, 0x01 }, { ZYD_CR95, 0x20 }, { ZYD_CR96, 0x50 }, \ + { ZYD_CR97, 0x37 }, { ZYD_CR98, 0x35 }, { ZYD_CR99, 0x00 }, \ + { ZYD_CR100, 0x01 }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR103, 0x27 }, { ZYD_CR104, 0x18 }, { ZYD_CR105, 0x12 }, \ + { ZYD_CR106, 0x04 }, { ZYD_CR107, 0x00 }, { ZYD_CR108, 0x0a }, \ + { ZYD_CR109, 0x27 }, { ZYD_CR110, 0x27 }, { ZYD_CR111, 0x27 }, \ + { ZYD_CR112, 0x27 }, { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, \ + { ZYD_CR115, 0x26 }, { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xfc }, \ + { ZYD_CR118, 0xfa }, { ZYD_CR119, 0x1e }, { ZYD_CR125, 0x90 }, \ + { ZYD_CR126, 0x00 }, { ZYD_CR127, 0x00 }, { ZYD_CR128, 0x14 }, \ + { ZYD_CR129, 0x12 }, { ZYD_CR130, 0x10 }, { ZYD_CR131, 0x0c }, \ + { ZYD_CR136, 0xdf }, { ZYD_CR137, 0xa0 }, { ZYD_CR138, 0xa8 }, \ + { ZYD_CR139, 0xb4 }, { ZYD_CR140, 0x98 }, { ZYD_CR141, 0x82 }, \ + { ZYD_CR142, 0x53 }, { ZYD_CR143, 0x1c }, { ZYD_CR144, 0x6c }, \ + { ZYD_CR147, 0x07 }, { ZYD_CR148, 0x40 }, { ZYD_CR149, 0x40 }, \ + { ZYD_CR150, 0x14 }, { ZYD_CR151, 0x18 }, { ZYD_CR159, 0x70 }, \ + { ZYD_CR160, 0xfe }, { ZYD_CR161, 0xee }, { ZYD_CR162, 0xaa }, \ + { ZYD_CR163, 0xfa }, { ZYD_CR164, 0xfa }, { ZYD_CR165, 0xea }, \ + { ZYD_CR166, 0xbe }, { ZYD_CR167, 0xbe }, { ZYD_CR168, 0x6a }, \ + { ZYD_CR169, 0xba }, { ZYD_CR170, 0xba }, { ZYD_CR171, 0xba }, \ + { ZYD_CR204, 0x7d }, { ZYD_CR203, 0x30 }, \ + { 0, 0 } \ +} + +#define ZYD_RFMD_PHY \ +{ \ + { ZYD_CR2, 0x1e }, { ZYD_CR9, 0x20 }, { ZYD_CR10, 0x89 }, \ + { ZYD_CR11, 0x00 }, { ZYD_CR15, 0xd0 }, { ZYD_CR17, 0x68 }, \ + { ZYD_CR19, 0x4a }, { ZYD_CR20, 0x0c }, { ZYD_CR21, 0x0e }, \ + { ZYD_CR23, 0x48 }, { ZYD_CR24, 0x14 }, { ZYD_CR26, 0x90 }, \ + { ZYD_CR27, 0x30 }, { ZYD_CR29, 0x20 }, { ZYD_CR31, 0xb2 }, \ + { ZYD_CR32, 0x43 }, { ZYD_CR33, 0x28 }, { ZYD_CR38, 0x30 }, \ + { ZYD_CR34, 0x0f }, { ZYD_CR35, 0xf0 }, { ZYD_CR41, 0x2a }, \ + { ZYD_CR46, 0x7f }, { ZYD_CR47, 0x1e }, { ZYD_CR51, 0xc5 }, \ + { ZYD_CR52, 0xc5 }, { ZYD_CR53, 0xc5 }, { ZYD_CR79, 0x58 }, \ + { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR82, 0x00 }, \ + { ZYD_CR83, 0x24 }, { ZYD_CR84, 0x04 }, { ZYD_CR85, 0x00 }, \ + { ZYD_CR86, 0x10 }, { ZYD_CR87, 0x2a }, { ZYD_CR88, 0x10 }, \ + { ZYD_CR89, 0x24 }, { ZYD_CR90, 0x18 }, { ZYD_CR91, 0x00 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR93, 0x00 }, { ZYD_CR94, 0x01 }, \ + { ZYD_CR95, 0x00 }, { ZYD_CR96, 0x40 }, { ZYD_CR97, 0x37 }, \ + { ZYD_CR98, 0x05 }, { ZYD_CR99, 0x28 }, { ZYD_CR100, 0x00 }, \ + { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, { ZYD_CR103, 0x27 }, \ + { ZYD_CR104, 0x18 }, { ZYD_CR105, 0x12 }, { ZYD_CR106, 0x1a }, \ + { ZYD_CR107, 0x24 }, { ZYD_CR108, 0x0a }, { ZYD_CR109, 0x13 }, \ + { ZYD_CR110, 0x2f }, { ZYD_CR111, 0x27 }, { ZYD_CR112, 0x27 }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x40 }, \ + { ZYD_CR116, 0x40 }, { ZYD_CR117, 0xf0 }, { ZYD_CR118, 0xf0 }, \ + { ZYD_CR119, 0x16 }, { ZYD_CR122, 0x00 }, { ZYD_CR127, 0x03 }, \ + { ZYD_CR131, 0x08 }, { ZYD_CR138, 0x28 }, { ZYD_CR148, 0x44 }, \ + { ZYD_CR150, 0x10 }, { ZYD_CR169, 0xbb }, { ZYD_CR170, 0xbb } \ +} + +#define ZYD_RFMD_RF \ +{ \ + 0x000007, 0x07dd43, 0x080959, 0x0e6666, 0x116a57, 0x17dd43, \ + 0x1819f9, 0x1e6666, 0x214554, 0x25e7fa, 0x27fffa, 0x294128, \ + 0x2c0000, 0x300000, 0x340000, 0x381e0f, 0x6c180f \ +} + +#define ZYD_RFMD_CHANTABLE \ +{ \ + { 0x181979, 0x1e6666 }, \ + { 0x181989, 0x1e6666 }, \ + { 0x181999, 0x1e6666 }, \ + { 0x1819a9, 0x1e6666 }, \ + { 0x1819b9, 0x1e6666 }, \ + { 0x1819c9, 0x1e6666 }, \ + { 0x1819d9, 0x1e6666 }, \ + { 0x1819e9, 0x1e6666 }, \ + { 0x1819f9, 0x1e6666 }, \ + { 0x181a09, 0x1e6666 }, \ + { 0x181a19, 0x1e6666 }, \ + { 0x181a29, 0x1e6666 }, \ + { 0x181a39, 0x1e6666 }, \ + { 0x181a60, 0x1c0000 } \ +} + +#define ZYD_AL2230_PHY \ +{ \ + { ZYD_CR15, 0x20 }, { ZYD_CR23, 0x40 }, { ZYD_CR24, 0x20 }, \ + { ZYD_CR26, 0x11 }, { ZYD_CR28, 0x3e }, { ZYD_CR29, 0x00 }, \ + { ZYD_CR44, 0x33 }, { ZYD_CR106, 0x2a }, { ZYD_CR107, 0x1a }, \ + { ZYD_CR109, 0x09 }, { ZYD_CR110, 0x27 }, { ZYD_CR111, 0x2b }, \ + { ZYD_CR112, 0x2b }, { ZYD_CR119, 0x0a }, { ZYD_CR10, 0x89 }, \ + { ZYD_CR17, 0x28 }, { ZYD_CR26, 0x93 }, { ZYD_CR34, 0x30 }, \ + { ZYD_CR35, 0x3e }, { ZYD_CR41, 0x24 }, { ZYD_CR44, 0x32 }, \ + { ZYD_CR46, 0x96 }, { ZYD_CR47, 0x1e }, { ZYD_CR79, 0x58 }, \ + { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR87, 0x0a }, \ + { ZYD_CR89, 0x04 }, { ZYD_CR92, 0x0a }, { ZYD_CR99, 0x28 }, \ + { ZYD_CR100, 0x00 }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x24 }, { ZYD_CR107, 0x2a }, { ZYD_CR109, 0x09 }, \ + { ZYD_CR110, 0x13 }, { ZYD_CR111, 0x1f }, { ZYD_CR112, 0x1f }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xf4 }, { ZYD_CR118, 0xfc }, \ + { ZYD_CR119, 0x10 }, { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x77 }, \ + { ZYD_CR122, 0xe0 }, { ZYD_CR137, 0x88 }, { ZYD_CR252, 0xff }, \ + { ZYD_CR253, 0xff }, { ZYD_CR251, 0x2f }, { ZYD_CR251, 0x3f }, \ + { ZYD_CR138, 0x28 }, { ZYD_CR203, 0x06 } \ +} + +#define ZYD_AL2230_PHY_B \ +{ \ + { ZYD_CR10, 0x89 }, { ZYD_CR15, 0x20 }, { ZYD_CR17, 0x2B }, \ + { ZYD_CR23, 0x40 }, { ZYD_CR24, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR28, 0x3e }, { ZYD_CR29, 0x00 }, { ZYD_CR33, 0x28 }, \ + { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x3e }, { ZYD_CR41, 0x24 }, \ + { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x99 }, { ZYD_CR47, 0x1e }, \ + { ZYD_CR48, 0x06 }, { ZYD_CR49, 0xf9 }, { ZYD_CR51, 0x01 }, \ + { ZYD_CR52, 0x80 }, { ZYD_CR53, 0x7e }, { ZYD_CR65, 0x00 }, \ + { ZYD_CR66, 0x00 }, { ZYD_CR67, 0x00 }, { ZYD_CR68, 0x00 }, \ + { ZYD_CR69, 0x28 }, { ZYD_CR79, 0x58 }, { ZYD_CR80, 0x30 }, \ + { ZYD_CR81, 0x30 }, { ZYD_CR87, 0x0a }, { ZYD_CR89, 0x04 }, \ + { ZYD_CR91, 0x00 }, { ZYD_CR92, 0x0a }, { ZYD_CR98, 0x8d }, \ + { ZYD_CR99, 0x00 }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x24 }, { ZYD_CR107, 0x2a }, { ZYD_CR109, 0x13 }, \ + { ZYD_CR110, 0x1f }, { ZYD_CR111, 0x1f }, { ZYD_CR112, 0x1f }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x26 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xfa }, { ZYD_CR118, 0xfa }, \ + { ZYD_CR119, 0x10 }, { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x6c }, \ + { ZYD_CR122, 0xfc }, { ZYD_CR123, 0x57 }, { ZYD_CR125, 0xad }, \ + { ZYD_CR126, 0x6c }, { ZYD_CR127, 0x03 }, { ZYD_CR137, 0x50 }, \ + { ZYD_CR138, 0xa8 }, { ZYD_CR144, 0xac }, { ZYD_CR150, 0x0d }, \ + { ZYD_CR252, 0x34 }, { ZYD_CR253, 0x34 } \ +} + +#define ZYD_AL2230_PHY_PART1 \ +{ \ + { ZYD_CR240, 0x57 }, { ZYD_CR9, 0xe0 } \ +} + +#define ZYD_AL2230_PHY_PART2 \ +{ \ + { ZYD_CR251, 0x2f }, { ZYD_CR251, 0x7f }, \ +} + +#define ZYD_AL2230_PHY_PART3 \ +{ \ + { ZYD_CR128, 0x14 }, { ZYD_CR129, 0x12 }, { ZYD_CR130, 0x10 }, \ +} + +#define ZYD_AL2230S_PHY_INIT \ +{ \ + { ZYD_CR47, 0x1e }, { ZYD_CR106, 0x22 }, { ZYD_CR107, 0x2a }, \ + { ZYD_CR109, 0x13 }, { ZYD_CR118, 0xf8 }, { ZYD_CR119, 0x12 }, \ + { ZYD_CR122, 0xe0 }, { ZYD_CR128, 0x10 }, { ZYD_CR129, 0x0e }, \ + { ZYD_CR130, 0x10 } \ +} + +#define ZYD_AL2230_PHY_FINI_PART1 \ +{ \ + { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR79, 0x58 }, \ + { ZYD_CR12, 0xf0 }, { ZYD_CR77, 0x1b }, { ZYD_CR78, 0x58 }, \ + { ZYD_CR203, 0x06 }, { ZYD_CR240, 0x80 }, \ +} + +#define ZYD_AL2230_RF_PART1 \ +{ \ + 0x03f790, 0x033331, 0x00000d, 0x0b3331, 0x03b812, 0x00fff3 \ +} + +#define ZYD_AL2230_RF_PART2 \ +{ \ + 0x000da4, 0x0f4dc5, 0x0805b6, 0x011687, 0x000688, 0x0403b9, \ + 0x00dbba, 0x00099b, 0x0bdffc, 0x00000d, 0x00500f \ +} + +#define ZYD_AL2230_RF_PART3 \ +{ \ + 0x00d00f, 0x004c0f, 0x00540f, 0x00700f, 0x00500f \ +} + +#define ZYD_AL2230_RF_B \ +{ \ + 0x03f790, 0x033331, 0x00000d, 0x0b3331, 0x03b812, 0x00fff3, \ + 0x0005a4, 0x0f4dc5, 0x0805b6, 0x0146c7, 0x000688, 0x0403b9, \ + 0x00dbba, 0x00099b, 0x0bdffc, 0x00000d, 0x00580f \ +} + +#define ZYD_AL2230_RF_B_PART1 \ +{ \ + 0x8cccd0, 0x481dc0, 0xcfff00, 0x25a000 \ +} + +#define ZYD_AL2230_RF_B_PART2 \ +{ \ + 0x25a000, 0xa3b2f0, 0x6da010, 0xe36280, 0x116000, 0x9dc020, \ + 0x5ddb00, 0xd99000, 0x3ffbd0, 0xb00000, 0xf01a00 \ +} + +#define ZYD_AL2230_RF_B_PART3 \ +{ \ + 0xf01b00, 0xf01e00, 0xf01a00 \ +} + +#define ZYD_AL2230_CHANTABLE \ +{ \ + { 0x03f790, 0x033331, 0x00000d }, \ + { 0x03f790, 0x0b3331, 0x00000d }, \ + { 0x03e790, 0x033331, 0x00000d }, \ + { 0x03e790, 0x0b3331, 0x00000d }, \ + { 0x03f7a0, 0x033331, 0x00000d }, \ + { 0x03f7a0, 0x0b3331, 0x00000d }, \ + { 0x03e7a0, 0x033331, 0x00000d }, \ + { 0x03e7a0, 0x0b3331, 0x00000d }, \ + { 0x03f7b0, 0x033331, 0x00000d }, \ + { 0x03f7b0, 0x0b3331, 0x00000d }, \ + { 0x03e7b0, 0x033331, 0x00000d }, \ + { 0x03e7b0, 0x0b3331, 0x00000d }, \ + { 0x03f7c0, 0x033331, 0x00000d }, \ + { 0x03e7c0, 0x066661, 0x00000d } \ +} + +#define ZYD_AL2230_CHANTABLE_B \ +{ \ + { 0x09efc0, 0x8cccc0, 0xb00000 }, \ + { 0x09efc0, 0x8cccd0, 0xb00000 }, \ + { 0x09e7c0, 0x8cccc0, 0xb00000 }, \ + { 0x09e7c0, 0x8cccd0, 0xb00000 }, \ + { 0x05efc0, 0x8cccc0, 0xb00000 }, \ + { 0x05efc0, 0x8cccd0, 0xb00000 }, \ + { 0x05e7c0, 0x8cccc0, 0xb00000 }, \ + { 0x05e7c0, 0x8cccd0, 0xb00000 }, \ + { 0x0defc0, 0x8cccc0, 0xb00000 }, \ + { 0x0defc0, 0x8cccd0, 0xb00000 }, \ + { 0x0de7c0, 0x8cccc0, 0xb00000 }, \ + { 0x0de7c0, 0x8cccd0, 0xb00000 }, \ + { 0x03efc0, 0x8cccc0, 0xb00000 }, \ + { 0x03e7c0, 0x866660, 0xb00000 } \ +} + +#define ZYD_AL7230B_PHY_1 \ +{ \ + { ZYD_CR240, 0x57 }, { ZYD_CR15, 0x20 }, { ZYD_CR23, 0x40 }, \ + { ZYD_CR24, 0x20 }, { ZYD_CR26, 0x11 }, { ZYD_CR28, 0x3e }, \ + { ZYD_CR29, 0x00 }, { ZYD_CR44, 0x33 }, { ZYD_CR106, 0x22 }, \ + { ZYD_CR107, 0x1a }, { ZYD_CR109, 0x09 }, { ZYD_CR110, 0x27 }, \ + { ZYD_CR111, 0x2b }, { ZYD_CR112, 0x2b }, { ZYD_CR119, 0x0a }, \ + { ZYD_CR122, 0xfc }, { ZYD_CR10, 0x89 }, { ZYD_CR17, 0x28 }, \ + { ZYD_CR26, 0x93 }, { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x3e }, \ + { ZYD_CR41, 0x24 }, { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x96 }, \ + { ZYD_CR47, 0x1e }, { ZYD_CR79, 0x58 }, { ZYD_CR80, 0x30 }, \ + { ZYD_CR81, 0x30 }, { ZYD_CR87, 0x0a }, { ZYD_CR89, 0x04 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR99, 0x28 }, { ZYD_CR100, 0x02 }, \ + { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, { ZYD_CR106, 0x22 }, \ + { ZYD_CR107, 0x3f }, { ZYD_CR109, 0x09 }, { ZYD_CR110, 0x1f }, \ + { ZYD_CR111, 0x1f }, { ZYD_CR112, 0x1f }, { ZYD_CR113, 0x27 }, \ + { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, { ZYD_CR116, 0x3f }, \ + { ZYD_CR117, 0xfa }, { ZYD_CR118, 0xfc }, { ZYD_CR119, 0x10 }, \ + { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x77 }, { ZYD_CR137, 0x88 }, \ + { ZYD_CR138, 0xa8 }, { ZYD_CR252, 0x34 }, { ZYD_CR253, 0x34 }, \ + { ZYD_CR251, 0x2f } \ +} + +#define ZYD_AL7230B_PHY_2 \ +{ \ + { ZYD_CR251, 0x3f }, { ZYD_CR128, 0x14 }, { ZYD_CR129, 0x12 }, \ + { ZYD_CR130, 0x10 }, { ZYD_CR38, 0x38 }, { ZYD_CR136, 0xdf } \ +} + +#define ZYD_AL7230B_PHY_3 \ +{ \ + { ZYD_CR203, 0x06 }, { ZYD_CR240, 0x80 } \ +} + +#define ZYD_AL7230B_RF_1 \ +{ \ + 0x09ec04, 0x8cccc8, 0x4ff821, 0xc5fbfc, 0x21ebfe, 0xafd401, \ + 0x6cf56a, 0xe04073, 0x193d76, 0x9dd844, 0x500007, 0xd8c010, \ + 0x3c9000, 0xbfffff, 0x700000, 0xf15d58 \ +} + +#define ZYD_AL7230B_RF_2 \ +{ \ + 0xf15d59, 0xf15d5c, 0xf15d58 \ +} + +#define ZYD_AL7230B_RF_SETCHANNEL \ +{ \ + 0x4ff821, 0xc5fbfc, 0x21ebfe, 0xafd401, 0x6cf56a, 0xe04073, \ + 0x193d76, 0x9dd844, 0x500007, 0xd8c010, 0x3c9000, 0xf15d58 \ +} + +#define ZYD_AL7230B_CHANTABLE \ +{ \ + { 0x09ec00, 0x8cccc8 }, \ + { 0x09ec00, 0x8cccd8 }, \ + { 0x09ec00, 0x8cccc0 }, \ + { 0x09ec00, 0x8cccd0 }, \ + { 0x05ec00, 0x8cccc8 }, \ + { 0x05ec00, 0x8cccd8 }, \ + { 0x05ec00, 0x8cccc0 }, \ + { 0x05ec00, 0x8cccd0 }, \ + { 0x0dec00, 0x8cccc8 }, \ + { 0x0dec00, 0x8cccd8 }, \ + { 0x0dec00, 0x8cccc0 }, \ + { 0x0dec00, 0x8cccd0 }, \ + { 0x03ec00, 0x8cccc8 }, \ + { 0x03ec00, 0x866660 } \ +} + +#define ZYD_AL2210_PHY \ +{ \ + { ZYD_CR9, 0xe0 }, { ZYD_CR10, 0x91 }, { ZYD_CR12, 0x90 }, \ + { ZYD_CR15, 0xd0 }, { ZYD_CR16, 0x40 }, { ZYD_CR17, 0x58 }, \ + { ZYD_CR18, 0x04 }, { ZYD_CR23, 0x66 }, { ZYD_CR24, 0x14 }, \ + { ZYD_CR26, 0x90 }, { ZYD_CR31, 0x80 }, { ZYD_CR34, 0x06 }, \ + { ZYD_CR35, 0x3e }, { ZYD_CR38, 0x38 }, { ZYD_CR46, 0x90 }, \ + { ZYD_CR47, 0x1e }, { ZYD_CR64, 0x64 }, { ZYD_CR79, 0xb5 }, \ + { ZYD_CR80, 0x38 }, { ZYD_CR81, 0x30 }, { ZYD_CR113, 0xc0 }, \ + { ZYD_CR127, 0x03 } \ +} + +#define ZYD_AL2210_RF \ +{ \ + 0x2396c0, 0x00fcb1, 0x358132, 0x0108b3, 0xc77804, 0x456415, \ + 0xff2226, 0x806667, 0x7860f8, 0xbb01c9, 0x00000a, 0x00000b \ +} + +#define ZYD_AL2210_CHANTABLE \ +{ \ + 0x0196c0, 0x019710, 0x019760, 0x0197b0, 0x019800, 0x019850, \ + 0x0198a0, 0x0198f0, 0x019940, 0x019990, 0x0199e0, 0x019a30, \ + 0x019a80, 0x019b40 \ +} + +#define ZYD_GCT_PHY \ +{ \ + { ZYD_CR10, 0x89 }, { ZYD_CR15, 0x20 }, { ZYD_CR17, 0x28 }, \ + { ZYD_CR23, 0x38 }, { ZYD_CR24, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR27, 0x15 }, { ZYD_CR28, 0x3e }, { ZYD_CR29, 0x00 }, \ + { ZYD_CR33, 0x28 }, { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x43 }, \ + { ZYD_CR41, 0x24 }, { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x92 }, \ + { ZYD_CR47, 0x1e }, { ZYD_CR48, 0x04 }, { ZYD_CR49, 0xfa }, \ + { ZYD_CR79, 0x58 }, { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, \ + { ZYD_CR87, 0x0a }, { ZYD_CR89, 0x04 }, { ZYD_CR91, 0x00 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR98, 0x8d }, { ZYD_CR99, 0x28 }, \ + { ZYD_CR100, 0x02 }, { ZYD_CR101, 0x09 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x1c }, { ZYD_CR107, 0x1c }, { ZYD_CR109, 0x13 }, \ + { ZYD_CR110, 0x1f }, { ZYD_CR111, 0x13 }, { ZYD_CR112, 0x1f }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x23 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xfa }, { ZYD_CR118, 0xf0 }, \ + { ZYD_CR119, 0x1a }, { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x1f }, \ + { ZYD_CR122, 0xf0 }, { ZYD_CR123, 0x57 }, { ZYD_CR125, 0xad }, \ + { ZYD_CR126, 0x6c }, { ZYD_CR127, 0x03 }, { ZYD_CR128, 0x14 }, \ + { ZYD_CR129, 0x12 }, { ZYD_CR130, 0x10 }, { ZYD_CR137, 0x50 }, \ + { ZYD_CR138, 0xa8 }, { ZYD_CR144, 0xac }, { ZYD_CR146, 0x20 }, \ + { ZYD_CR252, 0xff }, { ZYD_CR253, 0xff } \ +} + +#define ZYD_GCT_RF \ +{ \ + 0x40002b, 0x519e4f, 0x6f81ad, 0x73fffe, 0x25f9c, 0x100047, \ + 0x200999, 0x307602, 0x346063, \ +} + +#define ZYD_GCT_VCO \ +{ \ + { 0x664d, 0x604d, 0x6675, 0x6475, 0x6655, 0x6455, 0x6665 }, \ + { 0x666d, 0x606d, 0x664d, 0x644d, 0x6675, 0x6475, 0x6655 }, \ + { 0x665d, 0x605d, 0x666d, 0x646d, 0x664d, 0x644d, 0x6675 }, \ + { 0x667d, 0x607d, 0x665d, 0x645d, 0x666d, 0x646d, 0x664d }, \ + { 0x6643, 0x6043, 0x667d, 0x647d, 0x665d, 0x645d, 0x666d }, \ + { 0x6663, 0x6063, 0x6643, 0x6443, 0x667d, 0x647d, 0x665d }, \ + { 0x6653, 0x6053, 0x6663, 0x6463, 0x6643, 0x6443, 0x667d }, \ + { 0x6673, 0x6073, 0x6653, 0x6453, 0x6663, 0x6463, 0x6643 }, \ + { 0x664b, 0x604b, 0x6673, 0x6473, 0x6653, 0x6453, 0x6663 }, \ + { 0x666b, 0x606b, 0x664b, 0x644b, 0x6673, 0x6473, 0x6653 }, \ + { 0x665b, 0x605b, 0x666b, 0x646b, 0x664b, 0x644b, 0x6673 } \ +} + +#define ZYD_GCT_TXGAIN \ +{ \ + 0x0e313, 0x0fb13, 0x0e093, 0x0f893, 0x0ea93, 0x1f093, 0x1f493, \ + 0x1f693, 0x1f393, 0x1f35b, 0x1e6db, 0x1ff3f, 0x1ffff, 0x361d7, \ + 0x37fbf, 0x3ff8b, 0x3ff33, 0x3fb3f, 0x3ffff \ +} + +#define ZYD_GCT_CHANNEL_ACAL \ +{ \ + 0x106847, 0x106847, 0x106867, 0x106867, 0x106867, 0x106867, \ + 0x106857, 0x106857, 0x106857, 0x106857, 0x106877, 0x106877, \ + 0x106877, 0x10684f \ +} + +#define ZYD_GCT_CHANNEL_STD \ +{ \ + 0x100047, 0x100047, 0x100067, 0x100067, 0x100067, 0x100067, \ + 0x100057, 0x100057, 0x100057, 0x100057, 0x100077, 0x100077, \ + 0x100077, 0x10004f \ +} + +#define ZYD_GCT_CHANNEL_DIV \ +{ \ + 0x200999, 0x20099b, 0x200998, 0x20099a, 0x200999, 0x20099b, \ + 0x200998, 0x20099a, 0x200999, 0x20099b, 0x200998, 0x20099a, \ + 0x200999, 0x200ccc \ +} + +#define ZYD_MAXIM2_PHY \ +{ \ + { ZYD_CR23, 0x40 }, { ZYD_CR15, 0x20 }, { ZYD_CR28, 0x3e }, \ + { ZYD_CR29, 0x00 }, { ZYD_CR26, 0x11 }, { ZYD_CR44, 0x33 }, \ + { ZYD_CR106, 0x2a }, { ZYD_CR107, 0x1a }, { ZYD_CR109, 0x2b }, \ + { ZYD_CR110, 0x2b }, { ZYD_CR111, 0x2b }, { ZYD_CR112, 0x2b }, \ + { ZYD_CR10, 0x89 }, { ZYD_CR17, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x40 }, { ZYD_CR41, 0x24 }, \ + { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x90 }, { ZYD_CR89, 0x18 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x20 }, { ZYD_CR107, 0x24 }, { ZYD_CR109, 0x09 }, \ + { ZYD_CR110, 0x13 }, { ZYD_CR111, 0x13 }, { ZYD_CR112, 0x13 }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xf4 }, { ZYD_CR118, 0xfa }, \ + { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x77 }, { ZYD_CR122, 0xfe }, \ + { ZYD_CR10, 0x89 }, { ZYD_CR17, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x40 }, { ZYD_CR41, 0x24 }, \ + { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x90 }, { ZYD_CR79, 0x58 }, \ + { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR89, 0x18 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x20 }, { ZYD_CR107, 0x24 }, { ZYD_CR109, 0x09 }, \ + { ZYD_CR110, 0x13 }, { ZYD_CR111, 0x13 }, { ZYD_CR112, 0x13 }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xf4 }, { ZYD_CR118, 0x00 }, \ + { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x06 }, { ZYD_CR122, 0xfe } \ +} + +#define ZYD_MAXIM2_RF \ +{ \ + 0x33334, 0x10a03, 0x00400, 0x00ca1, 0x10072, 0x18645, 0x04006, \ + 0x000a7, 0x08258, 0x03fc9, 0x0040a, 0x0000b, 0x0026c \ +} + +#define ZYD_MAXIM2_CHANTABLE_F \ +{ \ + 0x33334, 0x08884, 0x1ddd4, 0x33334, 0x08884, 0x1ddd4, 0x33334, \ + 0x08884, 0x1ddd4, 0x33334, 0x08884, 0x1ddd4, 0x33334, 0x26664 \ +} + +#define ZYD_MAXIM2_CHANTABLE \ +{ \ + { 0x33334, 0x10a03 }, \ + { 0x08884, 0x20a13 }, \ + { 0x1ddd4, 0x30a13 }, \ + { 0x33334, 0x10a13 }, \ + { 0x08884, 0x20a23 }, \ + { 0x1ddd4, 0x30a23 }, \ + { 0x33334, 0x10a23 }, \ + { 0x08884, 0x20a33 }, \ + { 0x1ddd4, 0x30a33 }, \ + { 0x33334, 0x10a33 }, \ + { 0x08884, 0x20a43 }, \ + { 0x1ddd4, 0x30a43 }, \ + { 0x33334, 0x10a43 }, \ + { 0x26664, 0x20a53 } \ +} + +#define ZYD_TX_RATEDIV \ +{ \ + 0x1, 0x2, 0xb, 0xb, 0x0, 0x0, 0x0, 0x0, 0x30, 0x18, 0xc, 0x6, \ + 0x36, 0x24, 0x12, 0x9 \ +} + +/* + * Control pipe requests. + */ +#define ZYD_DOWNLOADREQ 0x30 +#define ZYD_DOWNLOADSTS 0x31 +#define ZYD_READFWDATAREQ 0x32 + +/* possible values for register ZYD_CR_INTERRUPT */ +#define ZYD_HWINT_MASK 0x004f0000 + +/* possible values for register ZYD_MAC_MISC */ +#define ZYD_UNLOCK_PHY_REGS 0x80 + +/* possible values for register ZYD_MAC_ENCRYPTION_TYPE */ +#define ZYD_ENC_SNIFFER 8 + +/* flags for register ZYD_MAC_RXFILTER */ +#define ZYD_FILTER_ASS_REQ (1 << 0) +#define ZYD_FILTER_ASS_RSP (1 << 1) +#define ZYD_FILTER_REASS_REQ (1 << 2) +#define ZYD_FILTER_REASS_RSP (1 << 3) +#define ZYD_FILTER_PRB_REQ (1 << 4) +#define ZYD_FILTER_PRB_RSP (1 << 5) +#define ZYD_FILTER_BCN (1 << 8) +#define ZYD_FILTER_ATIM (1 << 9) +#define ZYD_FILTER_DEASS (1 << 10) +#define ZYD_FILTER_AUTH (1 << 11) +#define ZYD_FILTER_DEAUTH (1 << 12) +#define ZYD_FILTER_PS_POLL (1 << 26) +#define ZYD_FILTER_RTS (1 << 27) +#define ZYD_FILTER_CTS (1 << 28) +#define ZYD_FILTER_ACK (1 << 29) +#define ZYD_FILTER_CFE (1 << 30) +#define ZYD_FILTER_CFE_A (1 << 31) + +/* helpers for register ZYD_MAC_RXFILTER */ +#define ZYD_FILTER_MONITOR 0xffffffff +#define ZYD_FILTER_BSS \ + (ZYD_FILTER_ASS_REQ | ZYD_FILTER_ASS_RSP | \ + ZYD_FILTER_REASS_REQ | ZYD_FILTER_REASS_RSP | \ + ZYD_FILTER_PRB_REQ | ZYD_FILTER_PRB_RSP | \ + (0x3 << 6) | \ + ZYD_FILTER_BCN | ZYD_FILTER_ATIM | ZYD_FILTER_DEASS | \ + ZYD_FILTER_AUTH | ZYD_FILTER_DEAUTH | \ + (0x7 << 13) | \ + ZYD_FILTER_PS_POLL | ZYD_FILTER_ACK) +#define ZYD_FILTER_HOSTAP \ + (ZYD_FILTER_ASS_REQ | ZYD_FILTER_REASS_REQ | \ + ZYD_FILTER_PRB_REQ | ZYD_FILTER_DEASS | ZYD_FILTER_AUTH | \ + ZYD_FILTER_DEAUTH | ZYD_FILTER_PS_POLL) + +struct zyd_tx_desc { + uint8_t phy; +#define ZYD_TX_PHY_SIGNAL(x) ((x) & 0xf) +#define ZYD_TX_PHY_OFDM (1 << 4) +#define ZYD_TX_PHY_SHPREAMBLE (1 << 5) /* CCK */ +#define ZYD_TX_PHY_5GHZ (1 << 5) /* OFDM */ + uint16_t len; + uint8_t flags; +#define ZYD_TX_FLAG_BACKOFF (1 << 0) +#define ZYD_TX_FLAG_MULTICAST (1 << 1) +#define ZYD_TX_FLAG_TYPE(x) (((x) & 0x3) << 2) +#define ZYD_TX_TYPE_DATA 0 +#define ZYD_TX_TYPE_PS_POLL 1 +#define ZYD_TX_TYPE_MGMT 2 +#define ZYD_TX_TYPE_CTL 3 +#define ZYD_TX_FLAG_WAKEUP (1 << 4) +#define ZYD_TX_FLAG_RTS (1 << 5) +#define ZYD_TX_FLAG_ENCRYPT (1 << 6) +#define ZYD_TX_FLAG_CTS_TO_SELF (1 << 7) + uint16_t pktlen; + uint16_t plcp_length; + uint8_t plcp_service; +#define ZYD_PLCP_LENGEXT 0x80 + uint16_t nextlen; +} __packed; + +struct zyd_plcphdr { + uint8_t signal; + uint8_t reserved[2]; + uint16_t service; /* unaligned! */ +} __packed; + +struct zyd_rx_stat { + uint8_t signal_cck; + uint8_t rssi; + uint8_t signal_ofdm; + uint8_t cipher; +#define ZYD_RX_CIPHER_WEP64 1 +#define ZYD_RX_CIPHER_TKIP 2 +#define ZYD_RX_CIPHER_AES 4 +#define ZYD_RX_CIPHER_WEP128 5 +#define ZYD_RX_CIPHER_WEP256 6 +#define ZYD_RX_CIPHER_WEP \ + (ZYD_RX_CIPHER_WEP64 | ZYD_RX_CIPHER_WEP128 | ZYD_RX_CIPHER_WEP256) + uint8_t flags; +#define ZYD_RX_OFDM (1 << 0) +#define ZYD_RX_TIMEOUT (1 << 1) +#define ZYD_RX_OVERRUN (1 << 2) +#define ZYD_RX_DECRYPTERR (1 << 3) +#define ZYD_RX_BADCRC32 (1 << 4) +#define ZYD_RX_NOT2ME (1 << 5) +#define ZYD_RX_BADCRC16 (1 << 6) +#define ZYD_RX_ERROR (1 << 7) +} __packed; + +/* this structure may be unaligned */ +struct zyd_rx_desc { +#define ZYD_MAX_RXFRAMECNT 3 + uWord len[ZYD_MAX_RXFRAMECNT]; + uWord tag; +#define ZYD_TAG_MULTIFRAME 0x697e +} __packed; + +/* I2C bus alike */ +struct zyd_rfwrite_cmd { + uint16_t code; + uint16_t width; + uint16_t bit[32]; +#define ZYD_RF_IF_LE (1 << 1) +#define ZYD_RF_CLK (1 << 2) +#define ZYD_RF_DATA (1 << 3) +} __packed; + +struct zyd_cmd { + uint16_t code; +#define ZYD_CMD_IOWR 0x0021 /* write HMAC or PHY register */ +#define ZYD_CMD_IORD 0x0022 /* read HMAC or PHY register */ +#define ZYD_CMD_RFCFG 0x0023 /* write RF register */ +#define ZYD_NOTIF_IORD 0x9001 /* response for ZYD_CMD_IORD */ +#define ZYD_NOTIF_MACINTR 0x9001 /* interrupt notification */ +#define ZYD_NOTIF_RETRYSTATUS 0xa001 /* Tx retry notification */ + uint8_t data[64]; +} __packed; + +/* structure for command ZYD_CMD_IOWR */ +struct zyd_pair { + uint16_t reg; +/* helpers macros to read/write 32-bit registers */ +#define ZYD_REG32_LO(reg) (reg) +#define ZYD_REG32_HI(reg) \ + ((reg) + ((((reg) & 0xf000) == 0x9000) ? 2 : 1)) + uint16_t val; +} __packed; + +/* structure for notification ZYD_NOTIF_RETRYSTATUS */ +struct zyd_notif_retry { + uint16_t rate; + uint8_t macaddr[IEEE80211_ADDR_LEN]; + uint16_t count; +} __packed; + +#define ZYD_CONFIG_INDEX 0 +#define ZYD_IFACE_INDEX 0 + +#define ZYD_INTR_TIMEOUT 1000 +#define ZYD_TX_TIMEOUT 10000 + +#define ZYD_MAX_TXBUFSZ \ + (sizeof(struct zyd_tx_desc) + MCLBYTES) +#define ZYD_MIN_FRAGSZ \ + (sizeof(struct zyd_plcphdr) + IEEE80211_MIN_LEN + \ + sizeof(struct zyd_rx_stat)) +#define ZYD_MIN_RXBUFSZ ZYD_MIN_FRAGSZ +#define ZYX_MAX_RXBUFSZ \ + ((sizeof (struct zyd_plcphdr) + IEEE80211_MAX_LEN + \ + sizeof (struct zyd_rx_stat)) * ZYD_MAX_RXFRAMECNT + \ + sizeof (struct zyd_rx_desc)) +#define ZYD_TX_DESC_SIZE (sizeof (struct zyd_tx_desc)) + +#define ZYD_RX_LIST_CNT 1 +#define ZYD_TX_LIST_CNT 5 +#define ZYD_CMD_FLAG_READ (1 << 0) +#define ZYD_CMD_FLAG_SENT (1 << 1) + +/* quickly determine if a given rate is CCK or OFDM */ +#define ZYD_RATE_IS_OFDM(rate) ((rate) >= 12 && (rate) != 22) + +struct zyd_phy_pair { + uint16_t reg; + uint8_t val; +}; + +struct zyd_mac_pair { + uint16_t reg; + uint32_t val; +}; + +struct zyd_tx_data { + STAILQ_ENTRY(zyd_tx_data) next; + struct zyd_softc *sc; + struct zyd_tx_desc desc; + struct mbuf *m; + struct ieee80211_node *ni; + int rate; +}; +typedef STAILQ_HEAD(, zyd_tx_data) zyd_txdhead; + +struct zyd_rx_data { + struct mbuf *m; + int rssi; +}; + +struct zyd_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint8_t wr_rate; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + int8_t wr_antsignal; + int8_t wr_antnoise; +} __packed; + +#define ZYD_RX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL)) + +struct zyd_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint8_t wt_rate; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; +} __packed; + +#define ZYD_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL)) + +struct zyd_softc; /* forward declaration */ + +struct zyd_rf { + /* RF methods */ + int (*init)(struct zyd_rf *); + int (*switch_radio)(struct zyd_rf *, int); + int (*set_channel)(struct zyd_rf *, uint8_t); + int (*bandedge6)(struct zyd_rf *, + struct ieee80211_channel *); + /* RF attributes */ + struct zyd_softc *rf_sc; /* back-pointer */ + int width; + int idx; /* for GIT RF */ + int update_pwr; +}; + +struct zyd_rq { + struct zyd_cmd *cmd; + const uint16_t *idata; + struct zyd_pair *odata; + int ilen; + int olen; + int flags; + STAILQ_ENTRY(zyd_rq) rq; +}; + +struct zyd_vap { + struct ieee80211vap vap; + int (*newstate)(struct ieee80211vap *, + enum ieee80211_state, int); +}; +#define ZYD_VAP(vap) ((struct zyd_vap *)(vap)) + +enum { + ZYD_BULK_WR, + ZYD_BULK_RD, + ZYD_INTR_WR, + ZYD_INTR_RD, + ZYD_N_TRANSFER = 4, +}; + +struct zyd_softc { + struct ifnet *sc_ifp; + device_t sc_dev; + struct usb_device *sc_udev; + + struct usb_xfer *sc_xfer[ZYD_N_TRANSFER]; + + int sc_flags; +#define ZYD_FLAG_FWLOADED (1 << 0) +#define ZYD_FLAG_INITONCE (1 << 1) +#define ZYD_FLAG_INITDONE (1 << 2) + + struct zyd_rf sc_rf; + + STAILQ_HEAD(, zyd_rq) sc_rtx; + STAILQ_HEAD(, zyd_rq) sc_rqh; + + uint8_t sc_bssid[IEEE80211_ADDR_LEN]; + uint16_t sc_fwbase; + uint8_t sc_regdomain; + uint8_t sc_macrev; + uint16_t sc_fwrev; + uint8_t sc_rfrev; + uint8_t sc_parev; + uint8_t sc_al2230s; + uint8_t sc_bandedge6; + uint8_t sc_newphy; + uint8_t sc_cckgain; + uint8_t sc_fix_cr157; + uint8_t sc_ledtype; + uint8_t sc_txled; + + uint32_t sc_atim_wnd; + uint32_t sc_pre_tbtt; + uint32_t sc_bcn_int; + + uint8_t sc_pwrcal[14]; + uint8_t sc_pwrint[14]; + uint8_t sc_ofdm36_cal[14]; + uint8_t sc_ofdm48_cal[14]; + uint8_t sc_ofdm54_cal[14]; + + struct mtx sc_mtx; + struct zyd_tx_data tx_data[ZYD_TX_LIST_CNT]; + zyd_txdhead tx_q; + zyd_txdhead tx_free; + int tx_nfree; + struct zyd_rx_desc sc_rx_desc; + struct zyd_rx_data sc_rx_data[ZYD_MAX_RXFRAMECNT]; + int sc_rx_count; + + struct zyd_cmd sc_ibuf; + + struct zyd_rx_radiotap_header sc_rxtap; + int sc_rxtap_len; + struct zyd_tx_radiotap_header sc_txtap; + int sc_txtap_len; +}; + +#define ZYD_LOCK(sc) mtx_lock(&(sc)->sc_mtx) +#define ZYD_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) +#define ZYD_LOCK_ASSERT(sc, t) mtx_assert(&(sc)->sc_mtx, t) + diff --git a/usr.bin/u4bhidctl/Makefile b/usr.bin/u4bhidctl/Makefile new file mode 100644 index 0000000000..4a82c1cca1 --- /dev/null +++ b/usr.bin/u4bhidctl/Makefile @@ -0,0 +1,12 @@ +# $NetBSD: Makefile,v 1.4 1999/05/11 21:02:25 augustss Exp $ +# $FreeBSD: src/usr.bin/usbhidctl/Makefile,v 1.2.2.1 2002/04/03 16:48:25 joe Exp $ + +.include + +PROG= usbhidctl +SRCS= usbhid.c + +LDADD+= -lusbhid +DPADD+= ${LIBUSBHID} + +.include diff --git a/usr.bin/u4bhidctl/usbhid.c b/usr.bin/u4bhidctl/usbhid.c new file mode 100644 index 0000000000..d9d960a974 --- /dev/null +++ b/usr.bin/u4bhidctl/usbhid.c @@ -0,0 +1,531 @@ +/* $NetBSD: usbhid.c,v 1.14 2000/07/03 02:51:37 matt Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (augustss@netbsd.org). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct variable { + char *name; + int instance; + int val; + struct hid_item h; + struct variable *next; +} *vars; + +static int verbose = 0; +static int noname = 0; +static int hexdump = 0; +static int wflag = 0; +static int zflag = 0; + +static void usage(void); +static void dumpitem(const char *label, struct hid_item *h); +static void dumpitems(report_desc_t r); +static void prdata(u_char *buf, struct hid_item *h); +static void dumpdata(int f, report_desc_t r, int loop); +static void writedata(int f, report_desc_t r); + +static void +parceargs(report_desc_t r, int all, int nnames, char **names) +{ + struct hid_data *d; + struct hid_item h; + char colls[1000]; + char hname[1000], *tmp1, *tmp2; + struct variable *var, **pnext; + int i, instance, cp, t; + + pnext = &vars; + if (all) { + if (wflag) + errx(1, "Must not specify -w to read variables"); + cp = 0; + for (d = hid_start_parse(r, + 1<name, "%s%s%s:%s", + colls, colls[0] != 0 ? "." : "", + hid_usage_page(HID_PAGE(h.usage)), + hid_usage_in_page(h.usage)); + var->h = h; + *pnext = var; + pnext = &var->next; + } + hid_end_parse(d); + return; + } + for (i = 0; i < nnames; i++) { + var = malloc(sizeof(*var)); + memset(var, 0, sizeof(*var)); + tmp1 = tmp2 = strdup(names[i]); + strsep(&tmp2, "="); + var->name = strsep(&tmp1, "#"); + if (tmp1 != NULL) + var->instance = atoi(tmp1); + if (tmp2 != NULL) { + if (!wflag) + errx(1, "Must specify -w to write variables"); + var->val = atoi(tmp2); + } else + if (wflag) + errx(1, "Must not specify -w to read variables"); + *pnext = var; + pnext = &var->next; + + instance = 0; + cp = 0; + for (d = hid_start_parse(r, + 1<name); + if (t > 0) { + if (strcmp(hname + t, var->name) != 0) + continue; + if (hname[t - 1] != '.') + continue; + } else if (strcmp(hname, var->name) != 0) + continue; + if (var->instance != instance++) + continue; + var->h = h; + break; + } + hid_end_parse(d); + if (var->h.usage == 0) + errx(1, "Unknown item '%s'", var->name); + } +} + +static void +usage(void) +{ + + fprintf(stderr, + "usage: %s -f device " + "[-l] [-n] [-r] [-t tablefile] [-v] [-x] name ...\n", + getprogname()); + fprintf(stderr, + " %s -f device " + "[-l] [-n] [-r] [-t tablefile] [-v] [-x] -a\n", + getprogname()); + fprintf(stderr, + " %s -f device " + "[-t tablefile] [-v] [-z] -w name=value\n", + getprogname()); + exit(1); +} + +static void +dumpitem(const char *label, struct hid_item *h) +{ + if ((h->flags & HIO_CONST) && !verbose) + return; + printf("%s rid=%d size=%d count=%d page=%s usage=%s%s%s", label, + h->report_ID, h->report_size, h->report_count, + hid_usage_page(HID_PAGE(h->usage)), + hid_usage_in_page(h->usage), + h->flags & HIO_CONST ? " Const" : "", + h->flags & HIO_VARIABLE ? "" : " Array"); + printf(", logical range %d..%d", + h->logical_minimum, h->logical_maximum); + if (h->physical_minimum != h->physical_maximum) + printf(", physical range %d..%d", + h->physical_minimum, h->physical_maximum); + if (h->unit) + printf(", unit=0x%02x exp=%d", h->unit, h->unit_exponent); + printf("\n"); +} + +static const char * +hid_collection_type(int32_t type) +{ + static char num[8]; + + switch (type) { + case 0: return ("Physical"); + case 1: return ("Application"); + case 2: return ("Logical"); + case 3: return ("Report"); + case 4: return ("Named_Array"); + case 5: return ("Usage_Switch"); + case 6: return ("Usage_Modifier"); + } + snprintf(num, sizeof(num), "0x%02x", type); + return (num); +} + +static void +dumpitems(report_desc_t r) +{ + struct hid_data *d; + struct hid_item h; + int size; + + for (d = hid_start_parse(r, ~0, -1); hid_get_item(d, &h); ) { + switch (h.kind) { + case hid_collection: + printf("Collection type=%s page=%s usage=%s\n", + hid_collection_type(h.collection), + hid_usage_page(HID_PAGE(h.usage)), + hid_usage_in_page(h.usage)); + break; + case hid_endcollection: + printf("End collection\n"); + break; + case hid_input: + dumpitem("Input ", &h); + break; + case hid_output: + dumpitem("Output ", &h); + break; + case hid_feature: + dumpitem("Feature", &h); + break; + } + } + hid_end_parse(d); + size = hid_report_size(r, hid_input, -1); + printf("Total input size %d bytes\n", size); + + size = hid_report_size(r, hid_output, -1); + printf("Total output size %d bytes\n", size); + + size = hid_report_size(r, hid_feature, -1); + printf("Total feature size %d bytes\n", size); +} + +static void +prdata(u_char *buf, struct hid_item *h) +{ + u_int data; + int i, pos; + + pos = h->pos; + for (i = 0; i < h->report_count; i++) { + data = hid_get_data(buf, h); + if (i > 0) + printf(" "); + if (h->logical_minimum < 0) + printf("%d", (int)data); + else + printf("%u", data); + if (hexdump) + printf(" [0x%x]", data); + h->pos += h->report_size; + } + h->pos = pos; +} + +static void +dumpdata(int f, report_desc_t rd, int loop) +{ + struct variable *var; + int dlen, havedata, i, match, r, rid, use_rid; + u_char *dbuf; + enum hid_kind kind; + + kind = 0; + rid = -1; + use_rid = !!hid_get_report_id(f); + do { + if (kind < 3) { + if (++rid >= 256) { + rid = 0; + kind++; + } + if (kind >= 3) + rid = -1; + for (var = vars; var; var = var->next) { + if (rid == var->h.report_ID && + kind == var->h.kind) + break; + } + if (var == NULL) + continue; + } + dlen = hid_report_size(rd, kind < 3 ? kind : hid_input, rid); + if (dlen <= 0) + continue; + dbuf = malloc(dlen); + memset(dbuf, 0, dlen); + if (kind < 3) { + dbuf[0] = rid; + r = hid_get_report(f, kind, dbuf, dlen); + if (r < 0) + warn("hid_get_report(rid %d)", rid); + havedata = !r && (rid == 0 || dbuf[0] == rid); + if (rid != 0) + dbuf[0] = rid; + } else { + r = read(f, dbuf, dlen); + if (r < 1) + err(1, "read error"); + havedata = 1; + } + if (verbose) { + printf("Got %s report %d (%d bytes):", + kind == hid_output ? "output" : + kind == hid_feature ? "feature" : "input", + use_rid ? dbuf[0] : 0, dlen); + if (havedata) { + for (i = 0; i < dlen; i++) + printf(" %02x", dbuf[i]); + } + printf("\n"); + } + match = 0; + for (var = vars; var; var = var->next) { + if ((kind < 3 ? kind : hid_input) != var->h.kind) + continue; + if (var->h.report_ID != 0 && + dbuf[0] != var->h.report_ID) + continue; + match = 1; + if (!noname) + printf("%s=", var->name); + if (havedata) + prdata(dbuf, &var->h); + printf("\n"); + } + if (match) + printf("\n"); + free(dbuf); + } while (loop || kind < 3); +} + +static void +writedata(int f, report_desc_t rd) +{ + struct variable *var; + int dlen, i, r, rid; + u_char *dbuf; + enum hid_kind kind; + + kind = 0; + rid = 0; + for (kind = 0; kind < 3; kind ++) { + for (rid = 0; rid < 256; rid ++) { + for (var = vars; var; var = var->next) { + if (rid == var->h.report_ID && kind == var->h.kind) + break; + } + if (var == NULL) + continue; + dlen = hid_report_size(rd, kind, rid); + if (dlen <= 0) + continue; + dbuf = malloc(dlen); + memset(dbuf, 0, dlen); + dbuf[0] = rid; + if (!zflag && hid_get_report(f, kind, dbuf, dlen) == 0) { + if (verbose) { + printf("Got %s report %d (%d bytes):", + kind == hid_input ? "input" : + kind == hid_output ? "output" : "feature", + rid, dlen); + for (i = 0; i < dlen; i++) + printf(" %02x", dbuf[i]); + printf("\n"); + } + } else if (!zflag) { + warn("hid_get_report(rid %d)", rid); + if (verbose) { + printf("Can't get %s report %d (%d bytes). " + "Will be initialized with zeros.\n", + kind == hid_input ? "input" : + kind == hid_output ? "output" : "feature", + rid, dlen); + } + } + for (var = vars; var; var = var->next) { + if (rid != var->h.report_ID || kind != var->h.kind) + continue; + hid_set_data(dbuf, &var->h, var->val); + } + if (verbose) { + printf("Setting %s report %d (%d bytes):", + kind == hid_output ? "output" : + kind == hid_feature ? "feature" : "input", + rid, dlen); + for (i = 0; i < dlen; i++) + printf(" %02x", dbuf[i]); + printf("\n"); + } + r = hid_set_report(f, kind, dbuf, dlen); + if (r != 0) + warn("hid_set_report(rid %d)", rid); + free(dbuf); + } + } +} + +int +main(int argc, char **argv) +{ + report_desc_t r; + char *table = 0; + char devnam[100], *dev = NULL; + int f; + int all = 0; + int ch; + int repdump = 0; + int loop = 0; + + while ((ch = getopt(argc, argv, "af:lnrt:vwxz")) != -1) { + switch(ch) { + case 'a': + all++; + break; + case 'f': + dev = optarg; + break; + case 'l': + loop ^= 1; + break; + case 'n': + noname++; + break; + case 'r': + repdump++; + break; + case 't': + table = optarg; + break; + case 'v': + verbose++; + break; + case 'w': + wflag = 1; + break; + case 'x': + hexdump = 1; + break; + case 'z': + zflag = 1; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + if (dev == NULL) + usage(); + + if (argc == 0 && !all && !repdump) + usage(); + + if (dev[0] != '/') { + if (isdigit(dev[0])) + snprintf(devnam, sizeof(devnam), "/dev/uhid%s", dev); + else + snprintf(devnam, sizeof(devnam), "/dev/%s", dev); + dev = devnam; + } + + hid_init(table); + + f = open(dev, O_RDWR); + if (f < 0) + err(1, "%s", dev); + + r = hid_get_report_desc(f); + if (r == 0) + errx(1, "USB_GET_REPORT_DESC"); + + if (repdump) { + printf("Report descriptor:\n"); + dumpitems(r); + } + if (argc != 0 || all) { + parceargs(r, all, argc, argv); + if (wflag) + writedata(f, r); + else + dumpdata(f, r, loop); + } + + hid_dispose_report_desc(r); + exit(0); +} diff --git a/usr.bin/u4bhidctl/usbhidctl.1 b/usr.bin/u4bhidctl/usbhidctl.1 new file mode 100644 index 0000000000..5681189078 --- /dev/null +++ b/usr.bin/u4bhidctl/usbhidctl.1 @@ -0,0 +1,148 @@ +.\" $NetBSD: usbhidctl.1,v 1.8 1999/05/11 21:03:58 augustss Exp $ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 1998 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Lennart Augustsson. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd August 01, 2011 +.Dt USBHIDCTL 1 +.Os +.Sh NAME +.Nm usbhidctl +.Nd manipulate USB HID devices +.Sh SYNOPSIS +.Nm +.Fl f Ar device +.Op Fl t Ar table +.Op Fl v +.Op Fl x +.Fl r +.Nm +.Fl f Ar device +.Op Fl t Ar table +.Op Fl l +.Op Fl v +.Op Fl x +.Fl a +.Nm +.Fl f Ar device +.Op Fl t Ar table +.Op Fl l +.Op Fl n +.Op Fl v +.Op Fl x +.Ar item ... +.Nm +.Fl f Ar device +.Op Fl t Ar table +.Op Fl v +.Op Fl z +.Fl w +.Ar item=value ... +.Sh DESCRIPTION +The +.Nm +utility can be used to dump and modify the state of a USB HID (Human +Interface Device). +Each named +.Ar item +is printed. +If the +.Fl w +flag is specified +.Nm +attempts to set the specified items to the given values. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl a +Show all items and their current values if device returns. +.It Fl f Ar device +Specify a path name for the device to operate on. +.It Fl l +Loop and dump the device data every time it changes. +.It Fl n +Suppress printing of the item name. +.It Fl r +Dump the report descriptor. +.It Fl t Ar table +Specify a path name for the HID usage table file. +.It Fl v +Be verbose. +.It Fl w +Change item values. +Only 'output' and 'feature' kinds can be set with this option. +.It Fl x +Dump data in hexadecimal as well as decimal. +.It Fl z +Reset reports to zero before processing +.Fl w +arguments. If not specified, current values will be requested from device. +.El +.Sh SYNTAX +.Nm +compares the names of items specified on the command line against the human +interface items reported by the USB device. +Each human interface item is mapped from its native form to a human readable +name, using the HID usage table file. +Command line items are compared with the generated item names, +and the USB HID device is operated on when a match is found. +.Pp +Each human interface item is named by the +.Qq page +it appears in, the +.Qq usage +within that page, and the list of +.Qq collections +containing the item. +Each collection in turn is also identified by page, and +the usage within that page. +.Pp +On the +.Nm +command line the page name is separated from the usage name with the character +.Sq Cm \&: . +The collections are separated by the character +.Sq Cm \&. . +.Pp +Some devices give the same name to more than one item. +.Nm +supports isolating each item by appending a +.Sq Cm \&# . +character and a decimal item instance number, starting at zero. +.Sh FILES +.Pa /usr/share/misc/usb_hid_usages +The default HID usage table. +.Sh SEE ALSO +.Xr usbhid 3 , +.Xr uhid 4 , +.Xr usb 4 +.Sh HISTORY +The +.Nm +command appeared in +.Nx 1.4 .