/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2014 Garrett D'Amore * Copyright 2019, Joyent, Inc. */ /* * Descriptor parsing functions */ #define USBA_FRAMEWORK #include #include #define INCREMENT_BUF(buf) \ if ((buf)[0] == 0) { \ break; \ } else { \ (buf) += (buf)[0]; \ } #define isdigit(ch) ((ch >= '0') && (ch <= '9')) extern usba_cfg_pwr_descr_t default_cfg_power; extern usba_if_pwr_descr_t default_if_power; size_t usb_parse_data(char *format, const uchar_t *data, size_t datalen, void *structure, size_t structlen) { int fmt; int counter = 1; int multiplier = 0; const uchar_t *dataend = data + datalen; char *structstart = (char *)structure; void *structend = (void *)((intptr_t)structstart + structlen); if ((format == NULL) || (data == NULL) || (structure == NULL)) { return (USB_PARSE_ERROR); } while ((fmt = *format) != '\0') { /* * Could some one pass a "format" that is greater than * the structlen? Conversely, one could pass a ret_buf_len * that is less than the "format" length. * If so, we need to protect against writing over memory. */ if (counter++ > structlen) { break; } if (fmt == 'c') { uint8_t *cp = (uint8_t *)structure; cp = (uint8_t *)(((uintptr_t)cp + _CHAR_ALIGNMENT - 1) & ~(_CHAR_ALIGNMENT - 1)); if (((data + 1) > dataend) || ((cp + 1) > (uint8_t *)structend)) break; *cp++ = *data++; structure = (void *)cp; if (multiplier) { multiplier--; } if (multiplier == 0) { format++; } } else if (fmt == 's') { uint16_t *sp = (uint16_t *)structure; sp = (uint16_t *) (((uintptr_t)sp + _SHORT_ALIGNMENT - 1) & ~(_SHORT_ALIGNMENT - 1)); if (((data + 2) > dataend) || ((sp + 1) > (uint16_t *)structend)) break; *sp++ = (data[1] << 8) + data[0]; data += 2; structure = (void *)sp; if (multiplier) { multiplier--; } if (multiplier == 0) { format++; } } else if (fmt == 'l') { uint32_t *lp = (uint32_t *)structure; lp = (uint32_t *) (((uintptr_t)lp + _INT_ALIGNMENT - 1) & ~(_INT_ALIGNMENT - 1)); if (((data + 4) > dataend) || ((lp + 1) > (uint32_t *)structend)) break; *lp++ = ((((( (uint32_t)data[3] << 8) | data[2]) << 8) | data[1]) << 8) | data[0]; data += 4; structure = (void *)lp; if (multiplier) { multiplier--; } if (multiplier == 0) { format++; } } else if (fmt == 'L') { uint64_t *llp = (uint64_t *)structure; llp = (uint64_t *) (((uintptr_t)llp + _LONG_LONG_ALIGNMENT - 1) & ~(_LONG_LONG_ALIGNMENT - 1)); if (((data + 8) > dataend) || ((llp + 1) >= (uint64_t *)structend)) break; *llp++ = (((((((((((((data[7] << 8) | data[6]) << 8) | data[5]) << 8) | data[4]) << 8) | data[3]) << 8) | data[2]) << 8) | data[1]) << 8) | data[0]; data += 8; structure = (void *)llp; if (multiplier) { multiplier--; } if (multiplier == 0) { format++; } } else if (isdigit(fmt)) { multiplier = (multiplier * 10) + (fmt - '0'); format++; counter--; } else { multiplier = 0; break; } } return ((intptr_t)structure - (intptr_t)structstart); } size_t usb_parse_CV_descr(char *format, const uchar_t *data, size_t datalen, void *structure, size_t structlen) { return (usb_parse_data(format, data, datalen, structure, structlen)); } /* * Helper function: returns pointer to n-th descriptor of * type descr_type, unless the end of the buffer or a descriptor * of type stop_descr_type1 or stop_descr_type2 is encountered first. */ static const uchar_t * usb_nth_descr(const uchar_t *buf, size_t buflen, int descr_type, uint_t n, int stop_descr_type1, int stop_descr_type2) { const uchar_t *bufstart = buf; const uchar_t *bufend = buf + buflen; if (buf == NULL) { return (NULL); } while (buf + 2 <= bufend) { if ((buf != bufstart) && ((buf[1] == stop_descr_type1) || (buf[1] == stop_descr_type2))) { return (NULL); } if ((descr_type == USB_DESCR_TYPE_ANY) || (buf[1] == descr_type)) { if (n-- == 0) { return (buf); } } /* * Check for a bad buffer. * If buf[0] is 0, then this will be an infite loop */ INCREMENT_BUF(buf); } return (NULL); } size_t usb_parse_dev_descr(const uchar_t *buf, size_t buflen, usb_dev_descr_t *ret_descr, size_t ret_buf_len) { if ((buf == NULL) || (ret_descr == NULL) || (buflen < 2) || (buf[1] != USB_DESCR_TYPE_DEV)) { return (USB_PARSE_ERROR); } return (usb_parse_data("ccsccccssscccc", buf, buflen, ret_descr, ret_buf_len)); } size_t usb_parse_cfg_descr(const uchar_t *buf, size_t buflen, usb_cfg_descr_t *ret_descr, size_t ret_buf_len) { if ((buf == NULL) || (ret_descr == NULL) || (buflen < 2) || (buf[1] != USB_DESCR_TYPE_CFG)) { return (USB_PARSE_ERROR); } return (usb_parse_data("ccsccccc", buf, buflen, ret_descr, ret_buf_len)); } size_t usba_parse_cfg_pwr_descr(const uchar_t *buf, size_t buflen, usba_cfg_pwr_descr_t *ret_descr, size_t ret_buf_len) { const uchar_t *bufend = buf + buflen; if ((buf == NULL) || (ret_descr == NULL)) { return (USB_PARSE_ERROR); } while (buf + 2 <= bufend) { if (buf[1] == USBA_DESCR_TYPE_CFG_PWR_1_1) { return (usb_parse_data("ccsccccccccsss", buf, buflen, ret_descr, ret_buf_len)); } /* * Check for a bad buffer. * If buf[0] is 0, then this will be an infinite loop */ INCREMENT_BUF(buf); } /* return the default configuration power descriptor */ bcopy(&default_cfg_power, ret_descr, USBA_CFG_PWR_DESCR_SIZE); return (ret_descr->bLength); } size_t usb_parse_ia_descr(const uchar_t *buf, size_t buflen, size_t first_if, usb_ia_descr_t *ret_descr, size_t ret_buf_len) { const uchar_t *bufend = buf + buflen; if ((buf == NULL) || (ret_descr == NULL)) { return (USB_PARSE_ERROR); } while (buf + USB_IA_DESCR_SIZE <= bufend) { if ((buf[1] == USB_DESCR_TYPE_IA) && (buf[2] == first_if)) { return (usb_parse_data("cccccccc", buf, _PTRDIFF(bufend, buf), ret_descr, ret_buf_len)); } /* * Check for a bad buffer. * If buf[0] is 0, then this will be an infinite loop */ INCREMENT_BUF(buf); } return (USB_PARSE_ERROR); } size_t usb_parse_if_descr(const uchar_t *buf, size_t buflen, uint_t if_number, uint_t alt_if_setting, usb_if_descr_t *ret_descr, size_t ret_buf_len) { const uchar_t *bufend = buf + buflen; if ((buf == NULL) || (ret_descr == NULL)) { return (USB_PARSE_ERROR); } while (buf + 4 <= bufend) { if ((buf[1] == USB_DESCR_TYPE_IF) && (buf[2] == if_number) && (buf[3] == alt_if_setting)) { return (usb_parse_data("ccccccccc", buf, _PTRDIFF(bufend, buf), ret_descr, ret_buf_len)); } /* * Check for a bad buffer. * If buf[0] is 0, then this will be an infinite loop */ INCREMENT_BUF(buf); } return (USB_PARSE_ERROR); } size_t usba_parse_if_pwr_descr(const uchar_t *buf, size_t buflen, uint_t if_number, uint_t alt_if_setting, usba_if_pwr_descr_t *ret_descr, size_t ret_buf_len) { const uchar_t *bufend = buf + buflen; if ((buf == NULL) || (ret_descr == NULL)) { return (USB_PARSE_ERROR); } while (buf + 4 <= bufend) { if ((buf[1] == USB_DESCR_TYPE_IF) && (buf[2] == if_number) && (buf[3] == alt_if_setting)) { buf += buf[0]; if (buf + 2 <= bufend) { if (buf[1] == USBA_DESCR_TYPE_IF_PWR_1_1) { return ( usb_parse_data("cccccccccsss", buf, _PTRDIFF(bufend, buf), ret_descr, ret_buf_len)); } else { break; } } else { break; } } /* * Check for a bad buffer. * If buf[0] is 0, then this will be an infinite loop */ INCREMENT_BUF(buf); } /* return the default interface power descriptor */ bcopy(&default_if_power, ret_descr, USBA_IF_PWR_DESCR_SIZE); return (ret_descr->bLength); } /* * the endpoint index is relative to the interface. index 0 is * the first endpoint */ size_t usb_parse_ep_descr(const uchar_t *buf, size_t buflen, uint_t if_number, uint_t alt_if_setting, uint_t ep_index, usb_ep_descr_t *ret_descr, size_t ret_buf_len) { const uchar_t *bufend = buf + buflen; if ((buf == NULL) || (ret_descr == NULL)) { return (USB_PARSE_ERROR); } while ((buf + 4) <= bufend) { if (buf[1] == USB_DESCR_TYPE_IF && buf[2] == if_number && buf[3] == alt_if_setting) { if ((buf = usb_nth_descr(buf, _PTRDIFF(bufend, buf), USB_DESCR_TYPE_EP, ep_index, USB_DESCR_TYPE_IF, -1)) == NULL) { break; } return (usb_parse_data("ccccsc", buf, _PTRDIFF(bufend, buf), ret_descr, ret_buf_len)); } /* * Check for a bad buffer. * If buf[0] is 0, then this will be an infinite loop */ INCREMENT_BUF(buf); } return (USB_PARSE_ERROR); } /* * Returns (at ret_descr) a null-terminated string. Null termination is * guaranteed, even if the string is longer than the buffer. Thus, a * maximum of (ret_buf_len - 1) characters are returned. * Stops silently on first character not in UNICODE format. */ /*ARGSUSED*/ size_t usba_ascii_string_descr(const uchar_t *buf, size_t buflen, char *ret_descr, size_t ret_buf_len) { int i = 1; char *retstart = ret_descr; const uchar_t *bufend = buf + buflen; if ((buf == NULL) || (ret_descr == NULL) || (ret_buf_len == 0) || (buflen < 2) || (buf[0] < 2) || (buf[1] != USB_DESCR_TYPE_STRING)) { return (USB_PARSE_ERROR); } for (buf = buf + 2; buf+1 < bufend && ret_buf_len > 1 && buf[0] != 0 && buf[1] == 0 && (i < ret_buf_len); buf += 2, i++) { *ret_descr++ = buf[0]; } *ret_descr++ = 0; return (_PTRDIFF(ret_descr, retstart)); } size_t usb_parse_CV_cfg_descr(const uchar_t *buf, size_t buflen, char *fmt, uint_t descr_type, uint_t descr_index, void *ret_descr, size_t ret_buf_len) { const uchar_t *bufend = buf + buflen; if ((buf == NULL) || (ret_descr == NULL) || (fmt == NULL) || (buflen < 2) || ((buf = usb_nth_descr(buf, buflen, descr_type, descr_index, -1, -1)) == NULL)) { return (USB_PARSE_ERROR); } return (usb_parse_data(fmt, buf, _PTRDIFF(bufend, buf), ret_descr, ret_buf_len)); } size_t usb_parse_CV_if_descr(const uchar_t *buf, size_t buflen, char *fmt, uint_t if_number, uint_t alt_if_setting, uint_t descr_type, uint_t descr_index, void *ret_descr, size_t ret_buf_len) { const uchar_t *bufend = buf + buflen; if ((buf == NULL) || (ret_descr == NULL) || (fmt == NULL)) { return (USB_PARSE_ERROR); } while (buf + 4 <= bufend) { if ((buf[1] == USB_DESCR_TYPE_IF) && (buf[2] == if_number) && (buf[3] == alt_if_setting)) { if ((buf = usb_nth_descr(buf, _PTRDIFF(bufend, buf), descr_type, descr_index, USB_DESCR_TYPE_IF, -1)) == NULL) { break; } return (usb_parse_data(fmt, buf, _PTRDIFF(bufend, buf), ret_descr, ret_buf_len)); } /* * Check for a bad buffer. * If buf[0] is 0, then this will be an infinite loop */ INCREMENT_BUF(buf); } return (USB_PARSE_ERROR); } size_t usb_parse_CV_ep_descr(const uchar_t *buf, size_t buflen, char *fmt, uint_t if_number, uint_t alt_if_setting, uint_t ep_index, uint_t descr_type, uint_t descr_index, void *ret_descr, size_t ret_buf_len) { const uchar_t *bufend = buf + buflen; if ((buf == NULL) || (ret_descr == NULL) || (fmt == NULL)) { return (USB_PARSE_ERROR); } while (buf + 4 <= bufend) { if ((buf[1] == USB_DESCR_TYPE_IF) && (buf[2] == if_number) && (buf[3] == alt_if_setting)) { if ((buf = usb_nth_descr(buf, _PTRDIFF(bufend, buf), USB_DESCR_TYPE_EP, ep_index, USB_DESCR_TYPE_IF, -1)) == NULL) { break; } if ((buf = usb_nth_descr(buf, _PTRDIFF(bufend, buf), descr_type, descr_index, USB_DESCR_TYPE_EP, USB_DESCR_TYPE_IF)) == NULL) { break; } return (usb_parse_data(fmt, buf, _PTRDIFF(bufend, buf), ret_descr, ret_buf_len)); } /* * Check for a bad buffer. * If buf[0] is 0, then this will be an infite loop */ INCREMENT_BUF(buf); } return (USB_PARSE_ERROR); }