From 7c1ef35027199acdfd68e24f16ee30427aeaf749 Mon Sep 17 00:00:00 2001 From: Johann Fischer Date: Thu, 17 Feb 2022 18:24:38 +0100 Subject: [PATCH] usb: add initial USB host support This is initial support. Necessary to test the UHC driver API, for the USB support test implementations, and upcoming USBIP support. Signed-off-by: Johann Fischer --- include/zephyr/usb/usbh.h | 161 +++++++++ subsys/Kconfig | 2 + subsys/usb/CMakeLists.txt | 1 + subsys/usb/host/CMakeLists.txt | 18 + subsys/usb/host/Kconfig | 43 +++ subsys/usb/host/usbh_api.c | 103 ++++++ subsys/usb/host/usbh_ch9.c | 172 +++++++++ subsys/usb/host/usbh_ch9.h | 52 +++ subsys/usb/host/usbh_core.c | 160 +++++++++ subsys/usb/host/usbh_data.ld | 2 + subsys/usb/host/usbh_internal.h | 15 + subsys/usb/host/usbh_shell.c | 599 ++++++++++++++++++++++++++++++++ 12 files changed, 1328 insertions(+) create mode 100644 include/zephyr/usb/usbh.h create mode 100644 subsys/usb/host/CMakeLists.txt create mode 100644 subsys/usb/host/Kconfig create mode 100644 subsys/usb/host/usbh_api.c create mode 100644 subsys/usb/host/usbh_ch9.c create mode 100644 subsys/usb/host/usbh_ch9.h create mode 100644 subsys/usb/host/usbh_core.c create mode 100644 subsys/usb/host/usbh_data.ld create mode 100644 subsys/usb/host/usbh_internal.h create mode 100644 subsys/usb/host/usbh_shell.c diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h new file mode 100644 index 00000000000..5ee45d7e5d7 --- /dev/null +++ b/include/zephyr/usb/usbh.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief New experimental USB device stack APIs and structures + * + * This file contains the USB device stack APIs and structures. + */ + +#ifndef ZEPHYR_INCLUDE_USBH_H_ +#define ZEPHYR_INCLUDE_USBH_H_ + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief USB HOST Core Layer API + * @defgroup _usb_host_core_api USB Host Core API + * @{ + */ + +/** + * USB host support runtime context + */ +struct usbh_contex { + /** Name of the USB device */ + const char *name; + /** Access mutex */ + struct k_mutex mutex; + /** Pointer to UHC device struct */ + const struct device *dev; + /** peripheral list */ + sys_dlist_t peripherals; +}; + +#define USBH_CONTROLLER_DEFINE(device_name, uhc_dev) \ + static STRUCT_SECTION_ITERABLE(usbh_contex, device_name) = { \ + .name = STRINGIFY(device_name), \ + .mutex = Z_MUTEX_INITIALIZER(device_name.mutex), \ + .dev = uhc_dev, \ + } + +/** + * @brief USB host peripheral structure + */ +struct usbh_peripheral { + /** Peripheral dlist node */ + sys_dnode_t node; + /** Peripheral address */ + uint8_t addr; + /** Detected speed (TBD) */ + uint8_t speed; +}; + +/** + * @brief Class Code + */ +struct usbh_class_code { + /** Device Class Code */ + uint8_t dclass; + /** Class Subclass Code */ + uint8_t sub; + /** Class Protocol Code */ + uint8_t proto; + /** Reserved */ + uint8_t reserved; +}; + +/** + * @brief USB host class data and class instance API + */ +struct usbh_class_data { + /** Class code supported by this instance */ + struct usbh_class_code code; + + /** Initialization of the class implementation */ + /* int (*init)(struct usbh_contex *const uhs_ctx); */ + /** Request completion event handler */ + int (*request)(struct usbh_contex *const uhs_ctx, + struct uhc_transfer *const xfer, int err); + /** Device connected handler */ + int (*connected)(struct usbh_contex *const uhs_ctx); + /** Device removed handler */ + int (*removed)(struct usbh_contex *const uhs_ctx); + /** Bus remote wakeup handler */ + int (*rwup)(struct usbh_contex *const uhs_ctx); + /** Bus suspended handler */ + int (*suspended)(struct usbh_contex *const uhs_ctx); + /** Bus resumed handler */ + int (*resumed)(struct usbh_contex *const uhs_ctx); +}; + +/** + */ +#define USBH_DEFINE_CLASS(name) \ + static STRUCT_SECTION_ITERABLE(usbh_class_data, name) + + +/** + * @brief Initialize the USB host support; + * + * @param[in] uhs_ctx Pointer to USB host support context + * + * @return 0 on success, other values on fail. + */ +int usbh_init(struct usbh_contex *uhs_ctx); + +/** + * @brief Enable the USB host support and class instances + * + * This function enables the USB host support. + * + * @param[in] uhs_ctx Pointer to USB host support context + * + * @return 0 on success, other values on fail. + */ +int usbh_enable(struct usbh_contex *uhs_ctx); + +/** + * @brief Disable the USB host support + * + * This function disables the USB host support. + * + * @param[in] uhs_ctx Pointer to USB host support context + * + * @return 0 on success, other values on fail. + */ +int usbh_disable(struct usbh_contex *uhs_ctx); + +/** + * @brief Shutdown the USB host support + * + * This function completely disables the USB host support. + * + * @param[in] uhs_ctx Pointer to USB host support context + * + * @return 0 on success, other values on fail. + */ +int usbh_shutdown(struct usbh_contex *const uhs_ctx); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_USBH_H_ */ diff --git a/subsys/Kconfig b/subsys/Kconfig index 2beb1e91d49..ba337e38d04 100644 --- a/subsys/Kconfig +++ b/subsys/Kconfig @@ -56,6 +56,8 @@ source "subsys/sd/Kconfig" source "subsys/usb/device_next/Kconfig" +source "subsys/usb/host/Kconfig" + source "subsys/dfu/Kconfig" source "subsys/random/Kconfig" diff --git a/subsys/usb/CMakeLists.txt b/subsys/usb/CMakeLists.txt index d7ad66a1b1d..6b968a4bf89 100644 --- a/subsys/usb/CMakeLists.txt +++ b/subsys/usb/CMakeLists.txt @@ -3,4 +3,5 @@ add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK device) add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK_NEXT device_next) +add_subdirectory_ifdef(CONFIG_USB_HOST_STACK host) add_subdirectory_ifdef(CONFIG_USBC_STACK usb_c) diff --git a/subsys/usb/host/CMakeLists.txt b/subsys/usb/host/CMakeLists.txt new file mode 100644 index 00000000000..94fc529f533 --- /dev/null +++ b/subsys/usb/host/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) 2022 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +zephyr_library_sources( + usbh_ch9.c + usbh_core.c + usbh_api.c +) + +zephyr_library_sources_ifdef( + CONFIG_USBH_SHELL + usbh_shell.c +) + +zephyr_linker_sources(DATA_SECTIONS usbh_data.ld) diff --git a/subsys/usb/host/Kconfig b/subsys/usb/host/Kconfig new file mode 100644 index 00000000000..bdedc6a4aa4 --- /dev/null +++ b/subsys/usb/host/Kconfig @@ -0,0 +1,43 @@ +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +menuconfig USB_HOST_STACK + bool "USB host stack [EXPERIMENTAL]" + select EXPERIMENTAL + select UHC_DRIVER + help + New experimental USB host stack. + +if USB_HOST_STACK + +module = USBH +module-str = usbh +source "subsys/logging/Kconfig.template.log_config" + +config USBH_SHELL + bool "USB host shell" + default y + depends on SHELL + help + Shell commands for USB host support. + +config USBH_INIT_PRIO + int + default 90 + help + USB host thread initialization priority level. + +config USBH_STACK_SIZE + int "USB host stack thread stack size" + default 1024 + help + USB host stack thread stack size in bytes. + +config USBH_MAX_UHC_MSG + int "Maximum number of UHC events" + default 10 + help + Maximum number of USB host controller events that can be queued. + +endif # USB_HOST_STACK diff --git a/subsys/usb/host/usbh_api.c b/subsys/usb/host/usbh_api.c new file mode 100644 index 00000000000..e1aa6e99ac3 --- /dev/null +++ b/subsys/usb/host/usbh_api.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "usbh_internal.h" + +#include +LOG_MODULE_REGISTER(uhs_api, CONFIG_USBH_LOG_LEVEL); + +int usbh_init(struct usbh_contex *uhs_ctx) +{ + int ret; + + k_mutex_lock(&uhs_ctx->mutex, K_FOREVER); + + if (!device_is_ready(uhs_ctx->dev)) { + LOG_ERR("USB host controller is not ready"); + ret = -ENODEV; + goto init_exit; + } + + if (uhc_is_initialized(uhs_ctx->dev)) { + LOG_WRN("USB host controller is already initialized"); + ret = -EALREADY; + goto init_exit; + } + + ret = usbh_init_device_intl(uhs_ctx); + +init_exit: + k_mutex_unlock(&uhs_ctx->mutex); + return ret; +} + +int usbh_enable(struct usbh_contex *uhs_ctx) +{ + int ret; + + k_mutex_lock(&uhs_ctx->mutex, K_FOREVER); + + if (!uhc_is_initialized(uhs_ctx->dev)) { + LOG_WRN("USB host controller is not initialized"); + ret = -EPERM; + goto enable_exit; + } + + if (uhc_is_enabled(uhs_ctx->dev)) { + LOG_WRN("USB host controller is already enabled"); + ret = -EALREADY; + goto enable_exit; + } + + ret = uhc_enable(uhs_ctx->dev); + if (ret != 0) { + LOG_ERR("Failed to enable controller"); + goto enable_exit; + } + +enable_exit: + k_mutex_unlock(&uhs_ctx->mutex); + return ret; +} + +int usbh_disable(struct usbh_contex *uhs_ctx) +{ + int ret; + + if (!uhc_is_enabled(uhs_ctx->dev)) { + LOG_WRN("USB host controller is already disabled"); + return 0; + } + + k_mutex_lock(&uhs_ctx->mutex, K_FOREVER); + + ret = uhc_disable(uhs_ctx->dev); + if (ret) { + LOG_ERR("Failed to disable USB controller"); + } + + k_mutex_unlock(&uhs_ctx->mutex); + + return 0; +} + +int usbh_shutdown(struct usbh_contex *const uhs_ctx) +{ + int ret; + + k_mutex_lock(&uhs_ctx->mutex, K_FOREVER); + + ret = uhc_shutdown(uhs_ctx->dev); + if (ret) { + LOG_ERR("Failed to shutdown USB device"); + } + + k_mutex_unlock(&uhs_ctx->mutex); + + return ret; +} diff --git a/subsys/usb/host/usbh_ch9.c b/subsys/usb/host/usbh_ch9.c new file mode 100644 index 00000000000..efda4737863 --- /dev/null +++ b/subsys/usb/host/usbh_ch9.c @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(usbh_ch9, CONFIG_USBH_LOG_LEVEL); + +#define SETUP_REQ_TIMEOUT 1000U + +int usbh_req_setup(const struct device *dev, + const uint8_t addr, + const uint8_t bmRequestType, + const uint8_t bRequest, + const uint16_t wValue, + const uint16_t wIndex, + const uint16_t wLength, + uint8_t *const data) +{ + struct usb_setup_packet req = { + .bmRequestType = bmRequestType, + .bRequest = bRequest, + .wValue = sys_cpu_to_le16(wValue), + .wIndex = sys_cpu_to_le16(wIndex), + .wLength = sys_cpu_to_le16(wLength), + }; + struct uhc_transfer *xfer; + struct net_buf *buf; + uint8_t ep = usb_reqtype_is_to_device(&req) ? 0x00 : 0x80; + int ret; + + xfer = uhc_xfer_alloc(dev, addr, ep, 0, 64, SETUP_REQ_TIMEOUT, NULL); + if (!xfer) { + return -ENOMEM; + } + + buf = uhc_xfer_buf_alloc(dev, xfer, sizeof(req)); + if (!buf) { + ret = -ENOMEM; + goto buf_alloc_err; + } + + net_buf_add_mem(buf, &req, sizeof(req)); + + if (wLength) { + buf = uhc_xfer_buf_alloc(dev, xfer, wLength); + if (!buf) { + ret = -ENOMEM; + goto buf_alloc_err; + } + + if (usb_reqtype_is_to_device(&req) && data != NULL) { + net_buf_add_mem(buf, data, wLength); + } + } + + buf = uhc_xfer_buf_alloc(dev, xfer, 0); + if (!buf) { + ret = -ENOMEM; + goto buf_alloc_err; + } + + return uhc_ep_enqueue(dev, xfer); + +buf_alloc_err: + uhc_xfer_free(dev, xfer); + + return ret; +} + +int usbh_req_desc(const struct device *dev, + const uint8_t addr, + const uint8_t type, const uint8_t index, + const uint8_t id, + const uint16_t len) +{ + const uint8_t bmRequestType = USB_REQTYPE_DIR_TO_HOST << 7; + const uint8_t bRequest = USB_SREQ_GET_DESCRIPTOR; + const uint16_t wValue = (type << 8) | index; + + return usbh_req_setup(dev, addr, + bmRequestType, bRequest, wValue, id, len, + NULL); +} + +int usbh_req_desc_dev(const struct device *dev, + const uint8_t addr) +{ + const uint8_t type = USB_DESC_DEVICE; + const uint16_t wLength = 18; + + return usbh_req_desc(dev, addr, type, 0, 0, wLength); +} + +int usbh_req_desc_cfg(const struct device *dev, + const uint8_t addr, + const uint8_t index, + const uint16_t len) +{ + const uint8_t type = USB_DESC_CONFIGURATION; + + return usbh_req_desc(dev, addr, type, index, 0, len); +} + +int usbh_req_set_address(const struct device *dev, + const uint8_t addr, const uint8_t new) +{ + const uint8_t bmRequestType = USB_REQTYPE_DIR_TO_DEVICE << 7; + const uint8_t bRequest = USB_SREQ_SET_ADDRESS; + + return usbh_req_setup(dev, addr, + bmRequestType, bRequest, new, 0, 0, + NULL); +} + +int usbh_req_set_cfg(const struct device *dev, + const uint8_t addr, const uint8_t new) +{ + const uint8_t bmRequestType = USB_REQTYPE_DIR_TO_DEVICE << 7; + const uint8_t bRequest = USB_SREQ_SET_CONFIGURATION; + + return usbh_req_setup(dev, addr, + bmRequestType, bRequest, new, 0, 0, + NULL); +} + +int usbh_req_set_alt(const struct device *dev, + const uint8_t addr, const uint8_t iface, + const uint8_t alt) +{ + const uint8_t bmRequestType = USB_REQTYPE_DIR_TO_DEVICE << 7 | + USB_REQTYPE_RECIPIENT_INTERFACE; + const uint8_t bRequest = USB_SREQ_SET_INTERFACE; + const uint16_t wValue = alt; + const uint16_t wIndex = iface; + + return usbh_req_setup(dev, addr, + bmRequestType, bRequest, wValue, wIndex, 0, + NULL); +} + +int usbh_req_set_sfs_rwup(const struct device *dev, + const uint8_t addr) +{ + const uint8_t bmRequestType = USB_REQTYPE_DIR_TO_DEVICE << 7; + const uint8_t bRequest = USB_SREQ_SET_FEATURE; + const uint16_t wValue = USB_SFS_REMOTE_WAKEUP; + + return usbh_req_setup(dev, addr, + bmRequestType, bRequest, wValue, 0, 0, + NULL); +} + +int usbh_req_clear_sfs_rwup(const struct device *dev, + const uint8_t addr) +{ + const uint8_t bmRequestType = USB_REQTYPE_DIR_TO_DEVICE << 7; + const uint8_t bRequest = USB_SREQ_CLEAR_FEATURE; + const uint16_t wValue = USB_SFS_REMOTE_WAKEUP; + + return usbh_req_setup(dev, addr, + bmRequestType, bRequest, wValue, 0, 0, + NULL); +} diff --git a/subsys/usb/host/usbh_ch9.h b/subsys/usb/host/usbh_ch9.h new file mode 100644 index 00000000000..407b2b90006 --- /dev/null +++ b/subsys/usb/host/usbh_ch9.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_USBH_CH9_H +#define ZEPHYR_INCLUDE_USBH_CH9_H + +#include +#include + +int usbh_req_setup(const struct device *dev, + const uint8_t addr, + const uint8_t bmRequestType, + const uint8_t bRequest, + const uint16_t wValue, + const uint16_t wIndex, + const uint16_t wLength, + uint8_t *const data); + +int usbh_req_desc(const struct device *dev, + const uint8_t addr, + const uint8_t type, const uint8_t index, + const uint8_t id, + const uint16_t len); + +int usbh_req_desc_dev(const struct device *dev, + const uint8_t addr); + +int usbh_req_desc_cfg(const struct device *dev, + const uint8_t addr, + const uint8_t index, + const uint16_t len); + +int usbh_req_set_alt(const struct device *dev, + const uint8_t addr, const uint8_t iface, + const uint8_t alt); + +int usbh_req_set_address(const struct device *dev, + const uint8_t addr, const uint8_t new); + +int usbh_req_set_cfg(const struct device *dev, + const uint8_t addr, const uint8_t new); + +int usbh_req_set_sfs_rwup(const struct device *dev, + const uint8_t addr); + +int usbh_req_clear_sfs_rwup(const struct device *dev, + const uint8_t addr); + +#endif /* ZEPHYR_INCLUDE_USBH_CH9_H */ diff --git a/subsys/usb/host/usbh_core.c b/subsys/usb/host/usbh_core.c new file mode 100644 index 00000000000..b3f2359e713 --- /dev/null +++ b/subsys/usb/host/usbh_core.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "usbh_internal.h" + +#include +LOG_MODULE_REGISTER(uhs, CONFIG_USBH_LOG_LEVEL); + +static K_KERNEL_STACK_DEFINE(usbh_stack, CONFIG_USBH_STACK_SIZE); +static struct k_thread usbh_thread_data; + +/* TODO */ +static struct usbh_class_data *class_data; + +K_MSGQ_DEFINE(usbh_msgq, sizeof(struct uhc_event), + CONFIG_USBH_MAX_UHC_MSG, sizeof(uint32_t)); + +static int usbh_event_carrier(const struct device *dev, + const struct uhc_event *const event) +{ + return k_msgq_put(&usbh_msgq, event, K_NO_WAIT); +} + +static int event_ep_request(struct usbh_contex *const ctx, + struct uhc_event *const event) +{ + struct uhc_transfer *xfer = event->xfer; + const struct device *dev = ctx->dev; + + if (class_data && class_data->request) { + return class_data->request(ctx, event->xfer, event->status); + } + + while (!k_fifo_is_empty(&xfer->done)) { + struct net_buf *buf; + + buf = net_buf_get(&xfer->done, K_NO_WAIT); + if (buf) { + LOG_HEXDUMP_INF(buf->data, buf->len, "buf"); + uhc_xfer_buf_free(dev, buf); + } + } + + return uhc_xfer_free(dev, xfer); +} + +static int ALWAYS_INLINE usbh_event_handler(struct usbh_contex *const ctx, + struct uhc_event *const event) +{ + int ret = 0; + + switch (event->type) { + case UHC_EVT_DEV_CONNECTED_LS: + case UHC_EVT_DEV_CONNECTED_FS: + case UHC_EVT_DEV_CONNECTED_HS: + LOG_DBG("Device connected event"); + if (class_data && class_data->connected) { + ret = class_data->connected(ctx); + } + break; + case UHC_EVT_DEV_REMOVED: + LOG_DBG("Device removed event"); + if (class_data && class_data->removed) { + ret = class_data->removed(ctx); + } + break; + case UHC_EVT_RESETED: + LOG_DBG("Bus reseted"); + /* TODO */ + if (class_data && class_data->removed) { + ret = class_data->removed(ctx); + } + break; + case UHC_EVT_SUSPENDED: + LOG_DBG("Bus suspended"); + if (class_data && class_data->suspended) { + ret = class_data->suspended(ctx); + } + break; + case UHC_EVT_RESUMED: + LOG_DBG("Bus resumed"); + if (class_data && class_data->resumed) { + ret = class_data->resumed(ctx); + } + break; + case UHC_EVT_RWUP: + LOG_DBG("RWUP event"); + if (class_data && class_data->rwup) { + ret = class_data->rwup(ctx); + } + break; + case UHC_EVT_EP_REQUEST: + event_ep_request(ctx, event); + break; + case UHC_EVT_ERROR: + LOG_DBG("Error event"); + break; + default: + break; + }; + + return ret; +} + +static void usbh_thread(const struct device *dev) +{ + struct uhc_event event; + + while (true) { + k_msgq_get(&usbh_msgq, &event, K_FOREVER); + + STRUCT_SECTION_FOREACH(usbh_contex, uhs_ctx) { + if (uhs_ctx->dev == event.dev) { + usbh_event_handler(uhs_ctx, &event); + } + } + } +} + +int usbh_init_device_intl(struct usbh_contex *const uhs_ctx) +{ + int ret; + + ret = uhc_init(uhs_ctx->dev, usbh_event_carrier); + if (ret != 0) { + LOG_ERR("Failed to init device driver"); + return ret; + } + + STRUCT_SECTION_FOREACH(usbh_class_data, cdata) { + LOG_DBG("class data %p", cdata); + /* TODO */ + class_data = cdata; + break; + } + + return 0; +} + +static int uhs_pre_init(const struct device *unused) +{ + k_thread_create(&usbh_thread_data, usbh_stack, + K_KERNEL_STACK_SIZEOF(usbh_stack), + (k_thread_entry_t)usbh_thread, + NULL, NULL, NULL, + K_PRIO_COOP(9), 0, K_NO_WAIT); + + k_thread_name_set(&usbh_thread_data, "usbh"); + + return 0; +} + +SYS_INIT(uhs_pre_init, POST_KERNEL, CONFIG_USBH_INIT_PRIO); diff --git a/subsys/usb/host/usbh_data.ld b/subsys/usb/host/usbh_data.ld new file mode 100644 index 00000000000..64503daae89 --- /dev/null +++ b/subsys/usb/host/usbh_data.ld @@ -0,0 +1,2 @@ +ITERABLE_SECTION_RAM(usbh_contex, 4) +ITERABLE_SECTION_RAM(usbh_class_data, 4) diff --git a/subsys/usb/host/usbh_internal.h b/subsys/usb/host/usbh_internal.h new file mode 100644 index 00000000000..c731520118e --- /dev/null +++ b/subsys/usb/host/usbh_internal.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_USBH_INTERNAL_H +#define ZEPHYR_INCLUDE_USBH_INTERNAL_H + +#include +#include + +int usbh_init_device_intl(struct usbh_contex *const uhs_ctx); + +#endif /* ZEPHYR_INCLUDE_USBH_INTERNAL_H */ diff --git a/subsys/usb/host/usbh_shell.c b/subsys/usb/host/usbh_shell.c new file mode 100644 index 00000000000..746a4af5aa4 --- /dev/null +++ b/subsys/usb/host/usbh_shell.c @@ -0,0 +1,599 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "usbh_internal.h" +#include "usbh_ch9.h" + +#include +LOG_MODULE_REGISTER(usbh_shell, CONFIG_USBH_LOG_LEVEL); + +#define FOOBAZ_VREQ_OUT 0x5b +#define FOOBAZ_VREQ_IN 0x5c + +USBH_CONTROLLER_DEFINE(uhs_ctx, DEVICE_DT_GET(DT_NODELABEL(zephyr_uhc0))); + +const static struct shell *ctx_shell; + +static void print_dev_desc(const struct shell *sh, + const struct usb_device_descriptor *const desc) +{ + shell_print(sh, "bLength\t\t\t%u", desc->bLength); + shell_print(sh, "bDescriptorType\t\t%u", desc->bDescriptorType); + shell_print(sh, "bcdUSB\t\t\t%x", desc->bcdUSB); + shell_print(sh, "bDeviceClass\t\t%u", desc->bDeviceClass); + shell_print(sh, "bDeviceSubClass\t\t%u", desc->bDeviceSubClass); + shell_print(sh, "bDeviceProtocol\t\t%u", desc->bDeviceProtocol); + shell_print(sh, "bMaxPacketSize0\t\t%u", desc->bMaxPacketSize0); + shell_print(sh, "idVendor\t\t%x", desc->idVendor); + shell_print(sh, "idProduct\t\t%x", desc->idProduct); + shell_print(sh, "bcdDevice\t\t%x", desc->bcdDevice); + shell_print(sh, "iManufacturer\t\t%u", desc->iManufacturer); + shell_print(sh, "iProduct\t\t%u", desc->iProduct); + shell_print(sh, "iSerial\t\t\t%u", desc->iSerialNumber); + shell_print(sh, "bNumConfigurations\t%u", desc->bNumConfigurations); +} + +static void print_cfg_desc(const struct shell *sh, + const struct usb_cfg_descriptor *const desc) +{ + shell_print(sh, "bLength\t\t\t%u", desc->bLength); + shell_print(sh, "bDescriptorType\t\t%u", desc->bDescriptorType); + shell_print(sh, "wTotalLength\t\t%x", desc->wTotalLength); + shell_print(sh, "bNumInterfaces\t\t%u", desc->bNumInterfaces); + shell_print(sh, "bConfigurationValue\t%u", desc->bConfigurationValue); + shell_print(sh, "iConfiguration\t\t%u", desc->iConfiguration); + shell_print(sh, "bmAttributes\t\t%02x", desc->bmAttributes); + shell_print(sh, "bMaxPower\t\t%u mA", desc->bMaxPower * 2); +} + +static void print_desc(const struct shell *sh, const struct net_buf *const buf) +{ + struct usb_desc_header *head = (void *)buf->data; + + switch (head->bDescriptorType) { + case USB_DESC_DEVICE: { + struct usb_device_descriptor *desc = (void *)buf->data; + + if (buf->len < sizeof(struct usb_device_descriptor)) { + shell_hexdump(ctx_shell, buf->data, buf->len); + break; + } + + desc->bcdUSB = sys_le16_to_cpu(desc->bcdUSB); + desc->idVendor = sys_le16_to_cpu(desc->idVendor); + desc->idProduct = sys_le16_to_cpu(desc->idProduct); + desc->bcdDevice = sys_le16_to_cpu(desc->bcdDevice); + print_dev_desc(sh, desc); + break; + } + case USB_DESC_CONFIGURATION: { + struct usb_cfg_descriptor *desc = (void *)buf->data; + + if (buf->len < sizeof(struct usb_cfg_descriptor)) { + shell_hexdump(ctx_shell, buf->data, buf->len); + break; + } + + desc->wTotalLength = sys_le16_to_cpu(desc->wTotalLength); + print_cfg_desc(sh, desc); + break; + } + default: + shell_hexdump(ctx_shell, buf->data, buf->len); + break; + } +} + +static int bazfoo_request(struct usbh_contex *const ctx, + struct uhc_transfer *const xfer, + int err) +{ + const struct device *dev = ctx->dev; + + shell_info(ctx_shell, "host: transfer finished %p, err %d", xfer, err); + + while (!k_fifo_is_empty(&xfer->done)) { + struct net_buf *buf; + + buf = net_buf_get(&xfer->done, K_NO_WAIT); + if (buf) { + /* + * FIXME: We don not distinguish the context + * of the request and always try to print it + * as descriptor first. If it is not a known descriptor, + * we show a hexdump in any case. + * This is just simple enough for first steps and will + * be revised with coming peripheral device management. + */ + if (xfer->ep == USB_CONTROL_EP_IN) { + print_desc(ctx_shell, buf); + } else { + shell_hexdump(ctx_shell, buf->data, buf->len); + } + + uhc_xfer_buf_free(dev, buf); + } + } + + return uhc_xfer_free(dev, xfer); +} + +static int bazfoo_connected(struct usbh_contex *const uhs_ctx) +{ + shell_info(ctx_shell, "host: USB device connected"); + + return 0; +} + +static int bazfoo_removed(struct usbh_contex *const uhs_ctx) +{ + shell_info(ctx_shell, "host: USB device removed"); + + return 0; +} + +static int bazfoo_rwup(struct usbh_contex *const uhs_ctx) +{ + shell_info(ctx_shell, "host: Bus remote wakeup event"); + + return 0; +} + +static int bazfoo_suspended(struct usbh_contex *const uhs_ctx) +{ + shell_info(ctx_shell, "host: Bus suspended"); + + return 0; +} + +static int bazfoo_resumed(struct usbh_contex *const uhs_ctx) +{ + shell_info(ctx_shell, "host: Bus resumed"); + + return 0; +} + +USBH_DEFINE_CLASS(bazfoo) = { + .request = bazfoo_request, + .connected = bazfoo_connected, + .removed = bazfoo_removed, + .rwup = bazfoo_rwup, + .suspended = bazfoo_suspended, + .resumed = bazfoo_resumed, +}; + +static uint8_t vreq_test_buf[1024]; + +static int cmd_bulk(const struct shell *sh, size_t argc, char **argv) +{ + struct uhc_transfer *xfer; + struct net_buf *buf; + uint8_t addr; + uint8_t ep; + size_t len; + + addr = strtol(argv[1], NULL, 10); + ep = strtol(argv[2], NULL, 16); + len = MIN(sizeof(vreq_test_buf), strtol(argv[3], NULL, 10)); + + xfer = uhc_xfer_alloc(uhs_ctx.dev, addr, ep, 0, 512, 10, NULL); + if (!xfer) { + return -ENOMEM; + } + + buf = uhc_xfer_buf_alloc(uhs_ctx.dev, xfer, len); + if (!buf) { + return -ENOMEM; + } + + if (USB_EP_DIR_IS_OUT(ep)) { + net_buf_add_mem(buf, vreq_test_buf, len); + } + + return uhc_ep_enqueue(uhs_ctx.dev, xfer); +} + +static int cmd_vendor_in(const struct shell *sh, + size_t argc, char **argv) +{ + const uint8_t bmRequestType = (USB_REQTYPE_DIR_TO_HOST << 7) | + (USB_REQTYPE_TYPE_VENDOR << 5); + const uint8_t bRequest = FOOBAZ_VREQ_IN; + const uint16_t wValue = 0x0000; + uint16_t wLength; + uint8_t addr; + + addr = strtol(argv[1], NULL, 10); + wLength = MIN(sizeof(vreq_test_buf), strtol(argv[2], NULL, 10)); + + return usbh_req_setup(uhs_ctx.dev, addr, + bmRequestType, bRequest, wValue, 0, wLength, + NULL); +} + +static int cmd_vendor_out(const struct shell *sh, + size_t argc, char **argv) +{ + const uint8_t bmRequestType = (USB_REQTYPE_DIR_TO_DEVICE << 7) | + (USB_REQTYPE_TYPE_VENDOR << 5); + const uint8_t bRequest = FOOBAZ_VREQ_OUT; + const uint16_t wValue = 0x0000; + uint16_t wLength; + uint8_t addr; + + addr = strtol(argv[1], NULL, 10); + wLength = MIN(sizeof(vreq_test_buf), strtol(argv[2], NULL, 10)); + + for (int i = 0; i < wLength; i++) { + vreq_test_buf[i] = i; + } + + return usbh_req_setup(uhs_ctx.dev, addr, + bmRequestType, bRequest, wValue, 0, wLength, + vreq_test_buf); +} + +static int cmd_desc_device(const struct shell *sh, + size_t argc, char **argv) +{ + uint8_t addr; + int err; + + addr = strtol(argv[1], NULL, 10); + + err = usbh_req_desc_dev(uhs_ctx.dev, addr); + if (err) { + shell_print(sh, "host: Failed to request device descriptor"); + } + + return err; +} + +static int cmd_desc_config(const struct shell *sh, + size_t argc, char **argv) +{ + uint8_t addr; + uint8_t cfg; + int err; + + addr = strtol(argv[1], NULL, 10); + cfg = strtol(argv[2], NULL, 10); + + /* TODO: cfg is ignored, add to usbh_req_desc_cfg */ + err = usbh_req_desc_cfg(uhs_ctx.dev, addr, cfg, 128); + if (err) { + shell_print(sh, "host: Failed to request configuration descriptor"); + } + + return err; +} + +static int cmd_desc_string(const struct shell *sh, + size_t argc, char **argv) +{ + const uint8_t type = USB_DESC_STRING; + uint8_t addr; + uint8_t id; + uint8_t idx; + int err; + + addr = strtol(argv[1], NULL, 10); + id = strtol(argv[2], NULL, 10); + idx = strtol(argv[3], NULL, 10); + + err = usbh_req_desc(uhs_ctx.dev, addr, type, idx, id, 128); + if (err) { + shell_print(sh, "host: Failed to request configuration descriptor"); + } + + return err; +} + +static int cmd_feature_set_halt(const struct shell *sh, + size_t argc, char **argv) +{ + uint8_t addr; + uint8_t ep; + int err; + + addr = strtol(argv[1], NULL, 10); + ep = strtol(argv[2], NULL, 16); + + /* TODO: add usbh_req_set_sfs_halt(uhs_ctx.dev, 0); */ + err = usbh_req_set_sfs_rwup(uhs_ctx.dev, addr); + if (err) { + shell_error(sh, "host: Failed to set halt feature"); + } else { + shell_print(sh, "host: Device 0x%02x, ep 0x%02x halt feature set", + addr, ep); + } + + return err; +} + +static int cmd_feature_clear_rwup(const struct shell *sh, + size_t argc, char **argv) +{ + uint8_t addr; + int err; + + addr = strtol(argv[1], NULL, 10); + + err = usbh_req_clear_sfs_rwup(uhs_ctx.dev, addr); + if (err) { + shell_error(sh, "host: Failed to clear rwup feature"); + } else { + shell_print(sh, "host: Device 0x%02x, rwup feature cleared", addr); + } + + return err; +} + +static int cmd_feature_set_rwup(const struct shell *sh, + size_t argc, char **argv) +{ + uint8_t addr; + int err; + + addr = strtol(argv[1], NULL, 10); + + err = usbh_req_set_sfs_rwup(uhs_ctx.dev, addr); + if (err) { + shell_error(sh, "host: Failed to set rwup feature"); + } else { + shell_print(sh, "host: Device 0x%02x, rwup feature set", addr); + } + + return err; +} + +static int cmd_device_config(const struct shell *sh, + size_t argc, char **argv) +{ + uint8_t addr; + uint8_t cfg; + int err; + + addr = strtol(argv[1], NULL, 10); + cfg = strtol(argv[2], NULL, 10); + + err = usbh_req_set_cfg(uhs_ctx.dev, addr, cfg); + if (err) { + shell_error(sh, "host: Failed to set configuration"); + } else { + shell_print(sh, "host: Device 0x%02x, new configuration %u", + addr, cfg); + } + + return err; +} + +static int cmd_device_interface(const struct shell *sh, + size_t argc, char **argv) +{ + uint8_t addr; + uint8_t iface; + uint8_t alt; + int err; + + addr = strtol(argv[1], NULL, 10); + iface = strtol(argv[2], NULL, 10); + alt = strtol(argv[3], NULL, 10); + + err = usbh_req_set_alt(uhs_ctx.dev, addr, iface, alt); + if (err) { + shell_error(sh, "host: Failed to set interface alternate"); + } else { + shell_print(sh, "host: Device 0x%02x, new %u alternate %u", + addr, iface, alt); + } + + return err; +} + +static int cmd_device_address(const struct shell *sh, + size_t argc, char **argv) +{ + uint8_t addr; + int err; + + addr = strtol(argv[1], NULL, 10); + + err = usbh_req_set_address(uhs_ctx.dev, 0, addr); + if (err) { + shell_error(sh, "host: Failed to set address"); + } else { + shell_print(sh, "host: New device address is 0x%02x", addr); + } + + return err; +} + +static int cmd_bus_suspend(const struct shell *sh, + size_t argc, char **argv) +{ + int err; + + err = uhc_bus_suspend(uhs_ctx.dev); + if (err) { + shell_error(sh, "host: Failed to perform bus suspend %d", err); + } else { + shell_print(sh, "host: USB bus suspended"); + } + + return err; +} + +static int cmd_bus_resume(const struct shell *sh, + size_t argc, char **argv) +{ + int err; + + err = uhc_bus_resume(uhs_ctx.dev); + if (err) { + shell_error(sh, "host: Failed to perform bus resume %d", err); + } else { + shell_print(sh, "host: USB bus resumed"); + } + + err = uhc_sof_enable(uhs_ctx.dev); + if (err) { + shell_error(sh, "host: Failed to start SoF generator %d", err); + } + + return err; +} + +static int cmd_bus_reset(const struct shell *sh, + size_t argc, char **argv) +{ + int err; + + err = uhc_bus_reset(uhs_ctx.dev); + if (err) { + shell_error(sh, "host: Failed to perform bus reset %d", err); + } else { + shell_print(sh, "host: USB bus reseted"); + } + + err = uhc_sof_enable(uhs_ctx.dev); + if (err) { + shell_error(sh, "host: Failed to start SoF generator %d", err); + } + + return err; +} + +static int cmd_usbh_init(const struct shell *sh, + size_t argc, char **argv) +{ + int err; + + ctx_shell = sh; + + err = usbh_init(&uhs_ctx); + if (err == -EALREADY) { + shell_error(sh, "host: USB host already initialized"); + } else if (err) { + shell_error(sh, "host: Failed to initialize %d", err); + } else { + shell_print(sh, "host: USB host initialized"); + } + + return err; +} + +static int cmd_usbh_enable(const struct shell *sh, + size_t argc, char **argv) +{ + int err; + + err = usbh_enable(&uhs_ctx); + if (err) { + shell_error(sh, "host: Failed to enable USB host support"); + } else { + shell_print(sh, "host: USB host enabled"); + } + + return err; +} + +static int cmd_usbh_disable(const struct shell *sh, + size_t argc, char **argv) +{ + int err; + + err = usbh_disable(&uhs_ctx); + if (err) { + shell_error(sh, "host: Failed to disable USB host support"); + } else { + shell_print(sh, "host: USB host disabled"); + } + + return err; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(desc_cmds, + SHELL_CMD_ARG(device, NULL, "
", + cmd_desc_device, 2, 0), + SHELL_CMD_ARG(configuration, NULL, "
", + cmd_desc_config, 3, 0), + SHELL_CMD_ARG(string, NULL, "
", + cmd_desc_string, 4, 0), + SHELL_SUBCMD_SET_END +); + +SHELL_STATIC_SUBCMD_SET_CREATE(feature_set_cmds, + SHELL_CMD_ARG(rwup, NULL, "
", + cmd_feature_set_rwup, 2, 0), + SHELL_CMD_ARG(halt, NULL, "
", + cmd_feature_set_halt, 3, 0), + SHELL_SUBCMD_SET_END +); + +SHELL_STATIC_SUBCMD_SET_CREATE(feature_clear_cmds, + SHELL_CMD_ARG(rwup, NULL, "
", + cmd_feature_clear_rwup, 2, 0), + SHELL_CMD_ARG(halt, NULL, "
", + cmd_feature_set_halt, 3, 0), + SHELL_SUBCMD_SET_END +); + +SHELL_STATIC_SUBCMD_SET_CREATE(device_cmds, + SHELL_CMD_ARG(address, NULL, "
", + cmd_device_address, 2, 0), + SHELL_CMD_ARG(config, NULL, "
", + cmd_device_config, 3, 0), + SHELL_CMD_ARG(interface, NULL, "
", + cmd_device_interface, 4, 0), + SHELL_CMD_ARG(descriptor, &desc_cmds, "descriptor request", + NULL, 1, 0), + SHELL_CMD_ARG(feature-set, &feature_set_cmds, "feature selector", + NULL, 1, 0), + SHELL_CMD_ARG(feature-clear, &feature_clear_cmds, "feature selector", + NULL, 1, 0), + SHELL_CMD_ARG(vendor_in, NULL, "
", + cmd_vendor_in, 3, 0), + SHELL_CMD_ARG(vendor_out, NULL, "
", + cmd_vendor_out, 3, 0), + SHELL_CMD_ARG(bulk, NULL, "
", + cmd_bulk, 4, 0), + SHELL_SUBCMD_SET_END +); + +SHELL_STATIC_SUBCMD_SET_CREATE(bus_cmds, + SHELL_CMD_ARG(suspend, NULL, "[nono]", + cmd_bus_suspend, 1, 0), + SHELL_CMD_ARG(resume, NULL, "[nono]", + cmd_bus_resume, 1, 0), + SHELL_CMD_ARG(reset, NULL, "[nono]", + cmd_bus_reset, 1, 0), + SHELL_SUBCMD_SET_END +); + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_usbh_cmds, + SHELL_CMD_ARG(init, NULL, "[none]", + cmd_usbh_init, 1, 0), + SHELL_CMD_ARG(enable, NULL, "[none]", + cmd_usbh_enable, 1, 0), + SHELL_CMD_ARG(disable, NULL, "[none]", + cmd_usbh_disable, 1, 0), + SHELL_CMD_ARG(bus, &bus_cmds, "bus commands", + NULL, 1, 0), + SHELL_CMD_ARG(device, &device_cmds, "device commands", + NULL, 1, 0), + SHELL_SUBCMD_SET_END +); + +SHELL_CMD_REGISTER(usbh, &sub_usbh_cmds, "USBH commands", NULL);