Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions subsys/usb/host/usbh_ch9.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
4 changes: 4 additions & 0 deletions subsys/usb/host/usbh_ch9.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
85 changes: 84 additions & 1 deletion subsys/usb/host/usbh_desc.c
Original file line number Diff line number Diff line change
@@ -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
*/

Expand All @@ -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);

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason to clone the buffer?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of net_buf_simple_clone() here to perform a shallow copy is solely to prevent modification of the original data. If net_buf_pull_u8() were used directly to retrieve the data, the header of the original string descriptor would be modified, posing a risk if the caller intends to reuse that descriptor.


/* 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;
}
43 changes: 43 additions & 0 deletions subsys/usb/host/usbh_desc.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: Copyright Nordic Semiconductor ASA
* SPDX-FileCopyrightText: Coryright 2025 - 2026 NXP
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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 */
37 changes: 21 additions & 16 deletions tests/subsys/usb/device_next/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <zephyr/usb/usbh.h>

#include "usbh_ch9.h"
#include "usbh_desc.h"
#include "usbh_device.h"

#include <zephyr/logging/log.h>
Expand Down Expand Up @@ -40,7 +41,9 @@
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;
Expand All @@ -52,19 +55,15 @@
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];

Check failure on line 66 in tests/subsys/usb/device_next/src/main.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

cast from 'const void *' to 'unsigned char *' drops const qualifier

See more on https://sonarcloud.io/project/issues?id=zephyrproject-rtos_zephyr&issues=AZ1wLsw5GCWvKhGOiy1X&open=AZ1wLsw5GCWvKhGOiy1X&pullRequest=102941

if (a != b) {
LOG_INF("%c != %c", a, b);
Expand All @@ -77,9 +76,9 @@

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;

Expand All @@ -92,19 +91,25 @@
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");
Expand Down
Loading