| /* |
| * Copyright (c) 2022 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/shell/shell.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/usb/usbh.h> |
| |
| #include "usbh_internal.h" |
| #include "usbh_ch9.h" |
| |
| #include <zephyr/logging/log.h> |
| 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; |
| |
| if (buf->len < sizeof(struct usb_desc_header)) { |
| return; |
| } |
| |
| 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_feature_set_ppwr(const struct shell *sh, |
| size_t argc, char **argv) |
| { |
| uint8_t addr; |
| uint8_t port; |
| int err; |
| |
| addr = strtol(argv[1], NULL, 10); |
| port = strtol(argv[2], NULL, 10); |
| |
| err = usbh_req_set_hcfs_ppwr(uhs_ctx.dev, addr, port); |
| if (err) { |
| shell_error(sh, "host: Failed to set ppwr feature"); |
| } else { |
| shell_print(sh, "host: Device 0x%02x, port %d, ppwr feature set", |
| addr, port); |
| } |
| |
| return err; |
| } |
| |
| static int cmd_feature_set_prst(const struct shell *sh, |
| size_t argc, char **argv) |
| { |
| uint8_t addr; |
| uint8_t port; |
| int err; |
| |
| addr = strtol(argv[1], NULL, 10); |
| port = strtol(argv[2], NULL, 10); |
| |
| err = usbh_req_set_hcfs_prst(uhs_ctx.dev, addr, port); |
| if (err) { |
| shell_error(sh, "host: Failed to set prst feature"); |
| } else { |
| shell_print(sh, "host: Device 0x%02x, port %d, prst feature set", |
| addr, port); |
| } |
| |
| 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, "<address>", |
| cmd_desc_device, 2, 0), |
| SHELL_CMD_ARG(configuration, NULL, "<address> <index>", |
| cmd_desc_config, 3, 0), |
| SHELL_CMD_ARG(string, NULL, "<address> <id> <index>", |
| cmd_desc_string, 4, 0), |
| SHELL_SUBCMD_SET_END |
| ); |
| |
| SHELL_STATIC_SUBCMD_SET_CREATE(feature_set_cmds, |
| SHELL_CMD_ARG(rwup, NULL, "<address>", |
| cmd_feature_set_rwup, 2, 0), |
| SHELL_CMD_ARG(ppwr, NULL, "<address> <port>", |
| cmd_feature_set_ppwr, 3, 0), |
| SHELL_CMD_ARG(prst, NULL, "<address> <port>", |
| cmd_feature_set_prst, 3, 0), |
| SHELL_CMD_ARG(halt, NULL, "<address> <endpoint>", |
| cmd_feature_set_halt, 3, 0), |
| SHELL_SUBCMD_SET_END |
| ); |
| |
| SHELL_STATIC_SUBCMD_SET_CREATE(feature_clear_cmds, |
| SHELL_CMD_ARG(rwup, NULL, "<address>", |
| cmd_feature_clear_rwup, 2, 0), |
| SHELL_CMD_ARG(halt, NULL, "<address> <endpoint>", |
| cmd_feature_set_halt, 3, 0), |
| SHELL_SUBCMD_SET_END |
| ); |
| |
| SHELL_STATIC_SUBCMD_SET_CREATE(device_cmds, |
| SHELL_CMD_ARG(address, NULL, "<address>", |
| cmd_device_address, 2, 0), |
| SHELL_CMD_ARG(config, NULL, "<address> <config>", |
| cmd_device_config, 3, 0), |
| SHELL_CMD_ARG(interface, NULL, "<address> <interface> <alternate>", |
| 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, "<address> <length>", |
| cmd_vendor_in, 3, 0), |
| SHELL_CMD_ARG(vendor_out, NULL, "<address> <length>", |
| cmd_vendor_out, 3, 0), |
| SHELL_CMD_ARG(bulk, NULL, "<address> <endpoint> <length>", |
| 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); |