diff --git a/subsys/usb/host/usbh_ch9.c b/subsys/usb/host/usbh_ch9.c index 80783e430559b..db2af35db647f 100644 --- a/subsys/usb/host/usbh_ch9.c +++ b/subsys/usb/host/usbh_ch9.c @@ -180,6 +180,15 @@ int usbh_req_desc_cfg(struct usb_device *const udev, return ret; } +int usbh_req_desc_str(struct usb_device *const udev, + const uint8_t index, const uint16_t lang_id, + struct net_buf *const desc_buf) +{ + uint16_t len = MIN(net_buf_tailroom(desc_buf), UINT8_MAX); + + return usbh_req_desc(udev, USB_DESC_STRING, index, lang_id, len, desc_buf); +} + int usbh_req_set_address(struct usb_device *const udev, const uint8_t addr) { diff --git a/subsys/usb/host/usbh_ch9.h b/subsys/usb/host/usbh_ch9.h index 19879dae6e2df..ae4292623e8c6 100644 --- a/subsys/usb/host/usbh_ch9.h +++ b/subsys/usb/host/usbh_ch9.h @@ -41,6 +41,10 @@ int usbh_req_desc_cfg(struct usb_device *const udev, const uint16_t len, struct usb_cfg_descriptor *const desc); +int usbh_req_desc_str(struct usb_device *const udev, + const uint8_t index, const uint16_t lang_id, + struct net_buf *const desc_buf); + int usbh_req_set_alt(struct usb_device *const udev, const uint8_t iface, const uint8_t alt); diff --git a/subsys/usb/host/usbh_desc.c b/subsys/usb/host/usbh_desc.c index cf82b1bba1b89..8afe007bc7aff 100644 --- a/subsys/usb/host/usbh_desc.c +++ b/subsys/usb/host/usbh_desc.c @@ -1,6 +1,6 @@ /* * SPDX-FileCopyrightText: Copyright Nordic Semiconductor ASA - * SPDX-FileCopyrightText: Copyright 2025 NXP + * SPDX-FileCopyrightText: Copyright 2025 - 2026 NXP * SPDX-License-Identifier: Apache-2.0 */ @@ -10,6 +10,8 @@ #include "usbh_class.h" #include "usbh_desc.h" +#include "usbh_ch9.h" +#include "usbh_device.h" LOG_MODULE_REGISTER(usbh_desc, CONFIG_USBH_LOG_LEVEL); @@ -59,6 +61,12 @@ bool usbh_desc_is_valid_endpoint(const void *const desc) USB_DESC_ENDPOINT); } +bool usbh_desc_is_valid_string(const void *const desc) +{ + return usbh_desc_is_valid(desc, sizeof(struct usb_string_descriptor), + USB_DESC_STRING); +} + const void *usbh_desc_get_next(const void *const desc) { const struct usb_desc_header *const head = desc; @@ -218,3 +226,78 @@ const void *usbh_desc_get_next_function(const void *const desc) return NULL; } + +int usbh_desc_get_supported_langs(struct usb_device *const udev, uint16_t *const lang_ids, + const uint8_t lang_ids_len) +{ + struct net_buf *buf; + uint16_t len; + int ret; + + buf = usbh_xfer_buf_alloc(udev, lang_ids_len * sizeof(uint16_t) + 2); + if (buf == NULL) { + return -ENOMEM; + } + + ret = usbh_req_desc_str(udev, 0, 0, buf); + if (ret != 0) { + goto done; + } + + if (!usbh_desc_is_valid_string(buf->data)) { + ret = -EBADMSG; + goto done; + } + + /* Pull bytes of the content */ + len = net_buf_pull_u8(buf) - 2; + /* Drop bDescriptorType */ + net_buf_pull_u8(buf); + len = MIN(len, buf->len) / 2; + for (ret = 0; ret < len; ret++) { + lang_ids[ret] = net_buf_pull_le16(buf); + } + +done: + if (buf != NULL) { + usbh_xfer_buf_free(udev, buf); + } + + return ret; +} + +int usbh_desc_str_utf16le_to_ascii(const struct net_buf *const buf, char *const ascii_buf, + const uint16_t ascii_buf_len) +{ + struct net_buf_simple tmp_buf; + uint16_t utf16le_code; + uint16_t len; + + if (!usbh_desc_is_valid_string(buf->data)) { + return -EINVAL; + } + + net_buf_simple_clone(&buf->b, &tmp_buf); + + /* Pull bytes of the content */ + len = net_buf_simple_pull_u8(&tmp_buf) - 2; + /* Drop bDescriptorType */ + net_buf_simple_pull_u8(&tmp_buf); + len = MIN(MIN(tmp_buf.len, len) / 2, ascii_buf_len - 1); + memset(ascii_buf, '\0', ascii_buf_len); + for (unsigned int i = 0; i < len; i++) { + utf16le_code = net_buf_simple_pull_le16(&tmp_buf); + + if (utf16le_code > 0x7F) { + return -EINVAL; + } + + ascii_buf[i] = (char)utf16le_code; + + if (utf16le_code == 0) { + break; + } + } + + return 0; +} diff --git a/subsys/usb/host/usbh_desc.h b/subsys/usb/host/usbh_desc.h index 487b3f7b7853f..571dd1020e72e 100644 --- a/subsys/usb/host/usbh_desc.h +++ b/subsys/usb/host/usbh_desc.h @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: Copyright Nordic Semiconductor ASA + * SPDX-FileCopyrightText: Coryright 2025 - 2026 NXP * SPDX-License-Identifier: Apache-2.0 */ @@ -121,6 +122,16 @@ bool usbh_desc_is_valid_association(const void *const desc); */ bool usbh_desc_is_valid_endpoint(const void *const desc); +/** + * @brief Checks that the pointed descriptor is an string descriptor. + * + * @param[in] desc The descriptor to validate + * + * @return true if the descriptor size and type are correct + * @return false if the descriptor size or type is wrong + */ +bool usbh_desc_is_valid_string(const void *const desc); + /** * @brief Get the next function in the descriptor list. * @@ -151,4 +162,36 @@ const void *usbh_desc_get_next_function(const void *const desc); */ const void *usbh_desc_get_next_alt_setting(const void *const desc); +/** + * @brief Get supported USB LANGIDs. + * + * Retrieves the list of language IDs supported by the USB device. + * + * @param[in] udev Pointer to the USB device. + * @param[out] lang_ids Array to store the supported LANGIDs. + * @param[in] lang_ids_len Length of the LANGIDs array. + * + * @retval number of supported IDs. + * @retval Negative error code from getting the string descriptor. + * @retval -ENOMEM if memory allocation failed. + * @retval -EBADMSG if the descriptor is invalid. + */ +int usbh_desc_get_supported_langs(struct usb_device *const udev, uint16_t *const lang_ids, + const uint8_t lang_ids_len); + +/** + * @brief Convert UTF16LE encoded string descriptor to ASCII. + * + * Converts the UTF16LE encoded string descriptor descriptor to an ASCII string. + * The ASCII string is always null-terminated. + * + * @param[in] buf Buffer containing the string descriptor. + * @param[out] ascii_buf Buffer to store the converted ASCII string. + * @param[in] ascii_buf_len Maximum length of the ASCII buffer (including null terminator). + * + * @retval 0 on success. + * @retval -EINVAL if descriptor is malformed or conversion is not possilbe. + */ +int usbh_desc_str_utf16le_to_ascii(const struct net_buf *const buf, char *const ascii_buf, + const uint16_t ascii_buf_len); #endif /* ZEPHYR_INCLUDE_USBH_DESC_H */ diff --git a/tests/subsys/usb/device_next/src/main.c b/tests/subsys/usb/device_next/src/main.c index 7c04c18544af5..f81c1e0964857 100644 --- a/tests/subsys/usb/device_next/src/main.c +++ b/tests/subsys/usb/device_next/src/main.c @@ -9,6 +9,7 @@ #include #include "usbh_ch9.h" +#include "usbh_desc.h" #include "usbh_device.h" #include @@ -40,7 +41,9 @@ USBH_CONTROLLER_DEFINE(uhs_ctx, DEVICE_DT_GET(DT_NODELABEL(zephyr_uhc0))); static int test_cmp_string_desc(struct net_buf *const buf, const int idx) { static struct usbd_desc_node *desc_nd; - size_t len; + char ascii_str[253]; + uint16_t len; + int err; if (idx == test_mfg.str.idx) { desc_nd = &test_mfg; @@ -52,19 +55,15 @@ static int test_cmp_string_desc(struct net_buf *const buf, const int idx) return -ENOTSUP; } - if (net_buf_pull_u8(buf) != desc_nd->bLength) { - return -EINVAL; - } - - if (net_buf_pull_u8(buf) != USB_DESC_STRING) { - return -EINVAL; + err = usbh_desc_str_utf16le_to_ascii(buf, ascii_str, sizeof(ascii_str)); + if (err != 0) { + return err; } - LOG_HEXDUMP_DBG(buf->data, buf->len, ""); - len = MIN(buf->len / 2, desc_nd->bLength / 2); + len = MIN(sizeof(ascii_str), desc_nd->bLength / 2); for (size_t i = 0; i < len; i++) { - uint16_t a = net_buf_pull_le16(buf); - uint16_t b = ((uint8_t *)(desc_nd->ptr))[i]; + char a = ascii_str[i]; + char b = ((uint8_t *)(desc_nd->ptr))[i]; if (a != b) { LOG_INF("%c != %c", a, b); @@ -77,9 +76,9 @@ static int test_cmp_string_desc(struct net_buf *const buf, const int idx) ZTEST(device_next, test_get_desc_string) { - const uint8_t type = USB_DESC_STRING; - const uint16_t id = 0x0409; static struct usb_device *udev; + const uint16_t our_id = 0x0409; + uint16_t lang_ids[2]; struct net_buf *buf; int err; @@ -92,19 +91,25 @@ ZTEST(device_next, test_get_desc_string) err = k_mutex_lock(&udev->mutex, K_MSEC(200)); zassert_equal(err, 0, "Failed to lock device"); - err = usbh_req_desc(udev, type, 1, id, UINT8_MAX, buf); + err = usbh_desc_get_supported_langs(udev, lang_ids, ARRAY_SIZE(lang_ids)); + zassert_true(err > 0, 0, "Failed to get LANGIDs"); + + zassert_true(err == 1, 0, "Wrong number of LANGIDs"); + zassert_equal(lang_ids[0], our_id, "Wrong LANGID"); + + err = usbh_req_desc_str(udev, 1, our_id, buf); zassert_equal(err, 0, "Transfer status is an error"); err = test_cmp_string_desc(buf, 1); zassert_equal(err, 0, "Descriptor comparison failed"); net_buf_reset(buf); - err = usbh_req_desc(udev, type, 2, id, UINT8_MAX, buf); + err = usbh_req_desc_str(udev, 2, our_id, buf); zassert_equal(err, 0, "Transfer status is an error"); err = test_cmp_string_desc(buf, 2); zassert_equal(err, 0, "Descriptor comparison failed"); net_buf_reset(buf); - err = usbh_req_desc(udev, type, 3, id, UINT8_MAX, buf); + err = usbh_req_desc_str(udev, 3, our_id, buf); zassert_equal(err, 0, "Transfer status is an error"); err = test_cmp_string_desc(buf, 3); zassert_equal(err, 0, "Descriptor comparison failed");