From e4954d41949e32e8b8fcdc3882c0ca611b8cec40 Mon Sep 17 00:00:00 2001 From: sakumisu <1203593632@qq.com> Date: Sat, 17 Aug 2024 20:58:56 +0800 Subject: [PATCH] feat: add usb adb device with cherrysh --- Kconfig | 5 + README.md | 1 + README_zh.md | 1 + cherryusb.cmake | 4 + class/adb/usbd_adb.c | 301 ++++++++++++++++++++++++++++++++ class/adb/usbd_adb.h | 38 ++++ demo/adb/cherryadb.png | Bin 0 -> 42508 bytes demo/adb/cherrysh_port.c | 330 +++++++++++++++++++++++++++++++++++ demo/adb/usbd_adb_template.c | 228 ++++++++++++++++++++++++ 9 files changed, 908 insertions(+) create mode 100644 class/adb/usbd_adb.c create mode 100644 class/adb/usbd_adb.h create mode 100644 demo/adb/cherryadb.png create mode 100644 demo/adb/cherrysh_port.c create mode 100644 demo/adb/usbd_adb_template.c diff --git a/Kconfig b/Kconfig index a630e73c..c2b2ab4c 100644 --- a/Kconfig +++ b/Kconfig @@ -105,6 +105,11 @@ if CHERRYUSB prompt "Enable usb dfu device" default n + config CHERRYUSB_DEVICE_ADB + bool + prompt "Enable usb adb device" + default n + choice prompt "Select usb device template" default CHERRYUSB_DEVICE_TEMPLATE_NONE diff --git a/README.md b/README.md index a0645bbc..5e817c4c 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ CherryUSB Device Stack has the following functions: - Support WINUSB1.0、WINUSB2.0、WEBUSB、BOS - Support Vendor class - Support UF2 +- Support Android Debug Bridge (Only support shell) - Support multi device with the same USB IP CherryUSB Device Stack resource usage (GCC 10.2 with -O2): diff --git a/README_zh.md b/README_zh.md index 6552acab..091b5cad 100644 --- a/README_zh.md +++ b/README_zh.md @@ -68,6 +68,7 @@ CherryUSB Device 协议栈当前实现以下功能: - 支持 WINUSB1.0、WINUSB2.0、WEBUSB、BOS - 支持 Vendor 类 class - 支持 UF2 +- 支持 Android Debug Bridge (Only support shell) - 支持相同 USB IP 的多从机 CherryUSB Device 协议栈资源占用说明(GCC 10.2 with -O2): diff --git a/cherryusb.cmake b/cherryusb.cmake index f5d6fa51..beec519c 100644 --- a/cherryusb.cmake +++ b/cherryusb.cmake @@ -36,6 +36,7 @@ ${CMAKE_CURRENT_LIST_DIR}/class/audio ${CMAKE_CURRENT_LIST_DIR}/class/video ${CMAKE_CURRENT_LIST_DIR}/class/wireless ${CMAKE_CURRENT_LIST_DIR}/class/midi +${CMAKE_CURRENT_LIST_DIR}/class/adb ${CMAKE_CURRENT_LIST_DIR}/class/vendor/net ${CMAKE_CURRENT_LIST_DIR}/class/vendor/serial ${CMAKE_CURRENT_LIST_DIR}/class/vendor/wifi @@ -70,6 +71,9 @@ if(CONFIG_CHERRYUSB_DEVICE) if(CONFIG_CHERRYUSB_DEVICE_DFU) list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/dfu/usbd_dfu.c) endif() + if(CONFIG_CHERRYUSB_DEVICE_ADB) + list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/adb/usbd_adb.c) + endif() if(DEFINED CONFIG_CHERRYUSB_DEVICE_DCD) if("${CONFIG_CHERRYUSB_DEVICE_DCD}" STREQUAL "fsdev") diff --git a/class/adb/usbd_adb.c b/class/adb/usbd_adb.c new file mode 100644 index 00000000..b193e35f --- /dev/null +++ b/class/adb/usbd_adb.c @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2024, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "usbd_core.h" +#include "usbd_adb.h" + +#define ADB_OUT_EP_IDX 0 +#define ADB_IN_EP_IDX 1 + +#define ADB_STATE_READ_MSG 0 +#define ADB_STATE_READ_DATA 1 +#define ADB_STATE_WRITE_MSG 2 +#define ADB_STATE_WRITE_DATA 3 +#define ADB_STATE_AWRITE_MSG 4 +#define ADB_STATE_AWRITE_DATA 5 + +#define MAX_PAYLOAD_V1 (4 * 1024) +#define MAX_PAYLOAD_V2 (256 * 1024) +#define MAX_PAYLOAD MAX_PAYLOAD_V1 +#define A_VERSION 0x01000000 + +#define A_SYNC 0x434e5953 +#define A_CNXN 0x4e584e43 +#define A_OPEN 0x4e45504f +#define A_OKAY 0x59414b4f +#define A_CLSE 0x45534c43 +#define A_WRTE 0x45545257 +#define A_AUTH 0x48545541 + +struct adb_msg { + uint32_t command; /* command identifier constant (A_CNXN, ...) */ + uint32_t arg0; /* first argument */ + uint32_t arg1; /* second argument */ + uint32_t data_length; /* length of payload (0 is allowed) */ + uint32_t data_crc32; /* crc32 of data payload */ + uint32_t magic; /* command ^ 0xffffffff */ +}; + +struct adb_packet { + struct adb_msg msg; + uint8_t payload[MAX_PAYLOAD]; +}; + +struct usbd_adb { + uint8_t state; + uint8_t common_state; + uint8_t write_state; + bool writable; + uint32_t localid; + uint32_t shell_remoteid; + uint32_t file_remoteid; +} adb_client; + +static struct usbd_endpoint adb_ep_data[2]; + +USB_NOCACHE_RAM_SECTION struct adb_packet tx_packet; +USB_NOCACHE_RAM_SECTION struct adb_packet rx_packet; + +static inline uint32_t adb_packet_checksum(struct adb_packet *packet) +{ + uint32_t sum = 0; + int i; + + for (i = 0; i < packet->msg.data_length; ++i) { + sum += (uint32_t)(packet->payload[i]); + } + + return sum; +} + +static uint32_t usbd_adb_get_remoteid(uint32_t localid) +{ + if (localid == ADB_SHELL_LOALID) { + return adb_client.shell_remoteid; + } else { + return adb_client.file_remoteid; + } +} + +static void adb_send_msg(struct adb_packet *packet) +{ + adb_client.common_state = ADB_STATE_WRITE_MSG; + + packet->msg.data_crc32 = adb_packet_checksum(packet); + packet->msg.magic = packet->msg.command ^ 0xffffffff; + + usbd_ep_start_write(0, adb_ep_data[ADB_IN_EP_IDX].ep_addr, (uint8_t *)&packet->msg, sizeof(struct adb_msg)); +} + +static void adb_send_okay(struct adb_packet *packet, uint32_t localid) +{ + packet->msg.command = A_OKAY; + packet->msg.arg0 = localid; + packet->msg.arg1 = usbd_adb_get_remoteid(localid); + packet->msg.data_length = 0; + + adb_send_msg(&tx_packet); +} + +static void adb_send_close(struct adb_packet *packet, uint32_t localid, uint32_t remoteid) +{ + packet->msg.command = A_CLSE; + packet->msg.arg0 = localid; + packet->msg.arg1 = remoteid; + packet->msg.data_length = 0; + + adb_send_msg(&tx_packet); +} + +void usbd_adb_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + if (adb_client.common_state == ADB_STATE_READ_MSG) { + if (nbytes != sizeof(struct adb_msg)) { + USB_LOG_ERR("invalid adb msg size:%d\r\n", nbytes); + return; + } + + USB_LOG_DBG("command:%x arg0:%x arg1:%x len:%d\r\n", + rx_packet.msg.command, + rx_packet.msg.arg0, + rx_packet.msg.arg1, + rx_packet.msg.data_length); + + if (rx_packet.msg.data_length) { + /* setup next out ep read transfer */ + adb_client.common_state = ADB_STATE_READ_DATA; + usbd_ep_start_read(busid, adb_ep_data[ADB_OUT_EP_IDX].ep_addr, rx_packet.payload, rx_packet.msg.data_length); + } else { + if (rx_packet.msg.command == A_CLSE) { + adb_client.writable = false; + usbd_adb_notify_write_done(); + USB_LOG_INFO("Close remoteid:%x\r\n", rx_packet.msg.arg0); + } + adb_client.common_state = ADB_STATE_READ_MSG; + /* setup first out ep read transfer */ + usbd_ep_start_read(busid, adb_ep_data[ADB_OUT_EP_IDX].ep_addr, (uint8_t *)&rx_packet.msg, sizeof(struct adb_msg)); + } + } else if (adb_client.common_state == ADB_STATE_READ_DATA) { + switch (rx_packet.msg.command) { + case A_SYNC: + + break; + case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */ + char *support_feature = "device::" + "ro.product.name=cherryadb;" + "ro.product.model=cherrysh;" + "ro.product.device=cherryadb;" + "features=cmd,shell_v1"; + + tx_packet.msg.command = A_CNXN; + tx_packet.msg.arg0 = A_VERSION; + tx_packet.msg.arg1 = MAX_PAYLOAD; + tx_packet.msg.data_length = strlen(support_feature); + memcpy(tx_packet.payload, support_feature, strlen(support_feature)); + + adb_send_msg(&tx_packet); + + adb_client.writable = false; + break; + case A_OPEN: /* OPEN(local-id, 0, "destination") */ + rx_packet.payload[rx_packet.msg.data_length] = '\0'; + + if (strncmp((const char *)rx_packet.payload, "shell:", 6) == 0) { + adb_client.localid = ADB_SHELL_LOALID; + adb_client.shell_remoteid = rx_packet.msg.arg0; + adb_send_okay(&tx_packet, ADB_SHELL_LOALID); + + USB_LOG_INFO("Open shell service, remoteid:%x\r\n", rx_packet.msg.arg0); + } else if (strncmp((const char *)rx_packet.payload, "sync:", 5) == 0) { + adb_client.localid = ADB_FILE_LOALID; + adb_client.file_remoteid = rx_packet.msg.arg0; + adb_send_okay(&tx_packet, ADB_FILE_LOALID); + USB_LOG_INFO("Open file service, remoteid:%x\r\n", rx_packet.msg.arg0); + } + break; + case A_OKAY: + + break; + case A_CLSE: + + break; + case A_WRTE: /* WRITE(local-id, remote-id, "data") */ + if ((rx_packet.msg.arg0 == adb_client.shell_remoteid) && (rx_packet.msg.arg1 == ADB_SHELL_LOALID)) { + adb_send_okay(&tx_packet, rx_packet.msg.arg1); + } else if ((rx_packet.msg.arg0 == adb_client.file_remoteid) && (rx_packet.msg.arg1 == ADB_FILE_LOALID)) { + adb_send_okay(&tx_packet, rx_packet.msg.arg1); + } else { + adb_send_close(&tx_packet, 0, rx_packet.msg.arg0); + } + break; + case A_AUTH: + + break; + + default: + break; + } + } +} + +void usbd_adb_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + if (adb_client.common_state == ADB_STATE_WRITE_MSG) { + if (tx_packet.msg.data_length) { + adb_client.common_state = ADB_STATE_WRITE_DATA; + usbd_ep_start_write(busid, adb_ep_data[ADB_IN_EP_IDX].ep_addr, tx_packet.payload, tx_packet.msg.data_length); + } else { + if (rx_packet.msg.command == A_WRTE) { + adb_client.writable = true; + if (adb_client.localid == ADB_SHELL_LOALID) { + usbd_adb_notify_shell_read(rx_packet.payload, rx_packet.msg.data_length); + } else { + } + } + adb_client.common_state = ADB_STATE_READ_MSG; + /* setup first out ep read transfer */ + usbd_ep_start_read(busid, adb_ep_data[ADB_OUT_EP_IDX].ep_addr, (uint8_t *)&rx_packet.msg, sizeof(struct adb_msg)); + } + } else if (adb_client.common_state == ADB_STATE_WRITE_DATA) { + adb_client.common_state = ADB_STATE_READ_MSG; + /* setup first out ep read transfer */ + usbd_ep_start_read(busid, adb_ep_data[ADB_OUT_EP_IDX].ep_addr, (uint8_t *)&rx_packet.msg, sizeof(struct adb_msg)); + } else if (adb_client.write_state == ADB_STATE_AWRITE_MSG) { + if (tx_packet.msg.data_length) { + adb_client.write_state = ADB_STATE_AWRITE_DATA; + usbd_ep_start_write(busid, adb_ep_data[ADB_IN_EP_IDX].ep_addr, tx_packet.payload, tx_packet.msg.data_length); + } else { + } + } else if (adb_client.write_state == ADB_STATE_AWRITE_DATA) { + usbd_adb_notify_write_done(); + } +} + +void adb_notify_handler(uint8_t busid, uint8_t event, void *arg) +{ + switch (event) { + case USBD_EVENT_INIT: + break; + case USBD_EVENT_DEINIT: + break; + case USBD_EVENT_RESET: + break; + case USBD_EVENT_CONFIGURED: + adb_client.common_state = ADB_STATE_READ_MSG; + /* setup first out ep read transfer */ + usbd_ep_start_read(busid, adb_ep_data[ADB_OUT_EP_IDX].ep_addr, (uint8_t *)&rx_packet.msg, sizeof(struct adb_msg)); + break; + + default: + break; + } +} + +struct usbd_interface *usbd_adb_init_intf(uint8_t busid, struct usbd_interface *intf, uint8_t in_ep, uint8_t out_ep) +{ + intf->class_interface_handler = NULL; + intf->class_endpoint_handler = NULL; + intf->vendor_handler = NULL; + intf->notify_handler = adb_notify_handler; + + adb_ep_data[ADB_OUT_EP_IDX].ep_addr = out_ep; + adb_ep_data[ADB_OUT_EP_IDX].ep_cb = usbd_adb_bulk_out; + adb_ep_data[ADB_IN_EP_IDX].ep_addr = in_ep; + adb_ep_data[ADB_IN_EP_IDX].ep_cb = usbd_adb_bulk_in; + + usbd_add_endpoint(busid, &adb_ep_data[ADB_OUT_EP_IDX]); + usbd_add_endpoint(busid, &adb_ep_data[ADB_IN_EP_IDX]); + + return intf; +} + +bool usbd_adb_can_write(void) +{ + return adb_client.writable; +} + +int usbd_abd_write(uint32_t localid, const uint8_t *data, uint32_t len) +{ + struct adb_packet *packet; + + packet = &tx_packet; + packet->msg.command = A_WRTE; + packet->msg.arg0 = localid; + packet->msg.arg1 = usbd_adb_get_remoteid(localid); + packet->msg.data_length = len; + memcpy(packet->payload, data, len); + + packet->msg.data_crc32 = adb_packet_checksum(packet); + packet->msg.magic = packet->msg.command ^ 0xffffffff; + + adb_client.write_state = ADB_STATE_AWRITE_MSG; + usbd_ep_start_write(0, adb_ep_data[ADB_IN_EP_IDX].ep_addr, (uint8_t *)&packet->msg, sizeof(struct adb_msg)); + return 0; +} + +void usbd_adb_close(uint32_t localid) +{ + adb_send_close(&tx_packet, 0, usbd_adb_get_remoteid(localid)); +} \ No newline at end of file diff --git a/class/adb/usbd_adb.h b/class/adb/usbd_adb.h new file mode 100644 index 00000000..4dbc730f --- /dev/null +++ b/class/adb/usbd_adb.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef USBD_ADB_H +#define USBD_ADB_H + +#include + +#define ADB_SHELL_LOALID 0x01 +#define ADB_FILE_LOALID 0x02 + +// clang-format off +#define ADB_DESCRIPTOR_INIT(bFirstInterface, in_ep, out_ep, wMaxPacketSize) \ + USB_INTERFACE_DESCRIPTOR_INIT(bFirstInterface, 0x00, 0x02, 0xff, 0x42, 0x01, 0x02), \ + USB_ENDPOINT_DESCRIPTOR_INIT(in_ep, 0x02, wMaxPacketSize, 0x00), \ + USB_ENDPOINT_DESCRIPTOR_INIT(out_ep, 0x02, wMaxPacketSize, 0x00) +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +struct usbd_interface *usbd_adb_init_intf(uint8_t busid, struct usbd_interface *intf, uint8_t in_ep, uint8_t out_ep); + +void usbd_adb_notify_shell_read(uint8_t *data, uint32_t len); +void usbd_adb_notify_file_read(uint8_t *data, uint32_t len); +void usbd_adb_notify_write_done(void); +bool usbd_adb_can_write(void); +int usbd_abd_write(uint32_t localid, const uint8_t *data, uint32_t len); +void usbd_adb_close(uint32_t localid); + +#ifdef __cplusplus +} +#endif + +#endif /* USBD_ADB_H */ \ No newline at end of file diff --git a/demo/adb/cherryadb.png b/demo/adb/cherryadb.png new file mode 100644 index 0000000000000000000000000000000000000000..512586b954e80be18f05f84d734cbe204792ce64 GIT binary patch literal 42508 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 literal 0 HcmV?d00001 diff --git a/demo/adb/cherrysh_port.c b/demo/adb/cherrysh_port.c new file mode 100644 index 00000000..85971724 --- /dev/null +++ b/demo/adb/cherrysh_port.c @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2024, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "FreeRTOS.h" +#include "task.h" +#include "event_groups.h" +#include "csh.h" +#include "usbd_core.h" +#include "usbd_adb.h" +#include "chry_ringbuffer.h" + +static chry_ringbuffer_t shell_rb; +static uint8_t mempool[1024]; + +#ifndef task_repl_PRIORITY +#define task_repl_PRIORITY (configMAX_PRIORITIES - 4U) +#endif + +#ifndef task_exec_PRIORITY +#define task_exec_PRIORITY (configMAX_PRIORITIES - 5U) +#endif + +static chry_shell_t csh; +static volatile bool login = false; + +static StaticTask_t task_buffer_repl; +static StaticTask_t task_buffer_exec; + +static StackType_t task_stack_repl[1024]; +static StackType_t task_stack_exec[1024]; + +static TaskHandle_t task_hdl_repl = NULL; +static TaskHandle_t task_hdl_exec = NULL; + +static EventGroupHandle_t event_hdl; +static StaticEventGroup_t event_grp; + +void usbd_adb_notify_shell_read(uint8_t *data, uint32_t len) +{ + chry_ringbuffer_write(&shell_rb, data, len); + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xEventGroupSetBitsFromISR(event_hdl, 0x10, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +} + +void usbd_adb_notify_write_done(void) +{ + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xEventGroupSetBitsFromISR(event_hdl, 0x20, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +} + +static uint16_t csh_sput_cb(chry_readline_t *rl, const void *data, uint16_t size) +{ + (void)rl; + + if (!usb_device_is_configured(0)) { + return size; + } + + if (usbd_adb_can_write() && size) { + usbd_abd_write(ADB_SHELL_LOALID, data, size); + xEventGroupWaitBits(event_hdl, 0x20, pdTRUE, pdFALSE, portMAX_DELAY); + } + + return size; +} + +static uint16_t csh_sget_cb(chry_readline_t *rl, void *data, uint16_t size) +{ + (void)rl; + + return chry_ringbuffer_read(&shell_rb, data, size); +} + +static void wait_char(void) +{ + EventBits_t event; +wait: + /* In order to lock the log from being disrupted , wait for REPL task execution to complete */ + event = xEventGroupWaitBits(event_hdl, (0x10 | 0x01 | 0x04), pdTRUE, pdFALSE, portMAX_DELAY); + if ((event & 0x10) == 0) { + if (event & 0x01) { + chry_readline_erase_line(&csh.rl); + xEventGroupSetBits(event_hdl, 0x02); + } + if (event & 0x04) { + chry_readline_edit_refresh(&csh.rl); + xEventGroupSetBits(event_hdl, 0x08); + } + + goto wait; + } +} + +static void task_repl(void *param) +{ + (void)param; + int ret; + volatile uint8_t *pexec = (void *)&csh.exec; + + for (;;) { + restart: + if (login) { + goto repl; + } else { + } + + ret = csh_login(&csh); + if (ret == 0) { + login = true; + } else if (ret == 1) { + /*!< no enough char */ + wait_char(); + continue; + } else { + continue; + } + + repl: + ret = chry_shell_task_repl(&csh); + + if (ret == -1) { + /*!< error */ + goto restart; + } else if (ret == 1) { + /*!< no enough char */ + wait_char(); + } else { + /*!< restart */ + } + + /*!< check flag */ + if (*pexec == CSH_STATUS_EXEC_DONE) { + *pexec = CSH_STATUS_EXEC_IDLE; + chry_readline_auto_refresh(&csh.rl, true); + chry_readline_ignore(&csh.rl, false); + chry_readline_edit_refresh(&csh.rl); + } + + if (login == false) { + chry_readline_erase_line(&csh.rl); + csh.rl.noblock = false; + } + } +} + +static void task_exec(void *param) +{ + (void)param; + + /*!< execute shell command */ + chry_shell_task_exec(&csh); + + /*!< notify REPL task execute done */ + xEventGroupSetBits(event_hdl, 0x10); + + /*!< wait for REPL task delete */ + vTaskSuspend(NULL); +} + +int chry_shell_port_create_context(chry_shell_t *csh, int argc, const char **argv) +{ + volatile TaskHandle_t *p_task_hdl_exec = (void *)&task_hdl_exec; + (void)csh; + (void)argc; + (void)argv; + + if (*p_task_hdl_exec != NULL) { + vTaskDelete(*p_task_hdl_exec); + } + + *p_task_hdl_exec = xTaskCreateStatic(task_exec, "task_exec", 1024U, NULL, task_exec_PRIORITY, task_stack_exec, &task_buffer_exec); + return 0; +} + +void chry_shell_port_default_handler(chry_shell_t *csh, int sig) +{ + volatile uint8_t *pexec = (void *)&csh->exec; + volatile TaskHandle_t *p_task_hdl_exec = (void *)&task_hdl_exec; + + switch (sig) { + case CSH_SIGINT: + case CSH_SIGQUIT: + case CSH_SIGKILL: + case CSH_SIGTERM: + break; + default: + return; + } + + /*!< force delete task */ + if (*p_task_hdl_exec != NULL) { + vTaskDelete(task_hdl_exec); + *p_task_hdl_exec = NULL; + } + + switch (sig) { + case CSH_SIGINT: + csh->rl.sput(&csh->rl, "^SIGINT" CONFIG_CSH_NEWLINE, sizeof("^SIGINT" CONFIG_CSH_NEWLINE) - 1); + break; + case CSH_SIGQUIT: + csh->rl.sput(&csh->rl, "^SIGQUIT" CONFIG_CSH_NEWLINE, sizeof("^SIGQUIT" CONFIG_CSH_NEWLINE) - 1); + break; + case CSH_SIGKILL: + csh->rl.sput(&csh->rl, "^SIGKILL" CONFIG_CSH_NEWLINE, sizeof("^SIGKILL" CONFIG_CSH_NEWLINE) - 1); + break; + case CSH_SIGTERM: + csh->rl.sput(&csh->rl, "^SIGTERM" CONFIG_CSH_NEWLINE, sizeof("^SIGTERM" CONFIG_CSH_NEWLINE) - 1); + break; + default: + return; + } + + *pexec = CSH_STATUS_EXEC_IDLE; + chry_readline_auto_refresh(&csh->rl, true); + chry_readline_ignore(&csh->rl, false); + chry_readline_edit_refresh(&csh->rl); +} + +int shell_init(bool need_login) +{ + chry_shell_init_t csh_init; + + if (chry_ringbuffer_init(&shell_rb, mempool, sizeof(mempool))) { + return -1; + } + + if (need_login) { + login = false; + } else { + login = true; + } + + /*!< I/O callback */ + csh_init.sput = csh_sput_cb; + csh_init.sget = csh_sget_cb; + +#if defined(CONFIG_CSH_SYMTAB) && CONFIG_CSH_SYMTAB + extern const int __fsymtab_start; + extern const int __fsymtab_end; + extern const int __vsymtab_start; + extern const int __vsymtab_end; + + /*!< get table from ld symbol */ + csh_init.command_table_beg = &__fsymtab_start; + csh_init.command_table_end = &__fsymtab_end; + csh_init.variable_table_beg = &__vsymtab_start; + csh_init.variable_table_end = &__vsymtab_end; +#endif + +#if defined(CONFIG_CSH_PROMPTEDIT) && CONFIG_CSH_PROMPTEDIT + static char csh_prompt_buffer[128]; + + /*!< set prompt buffer */ + csh_init.prompt_buffer = csh_prompt_buffer; + csh_init.prompt_buffer_size = sizeof(csh_prompt_buffer); +#endif + +#if defined(CONFIG_CSH_HISTORY) && CONFIG_CSH_HISTORY + static char csh_history_buffer[128]; + + /*!< set history buffer */ + csh_init.history_buffer = csh_history_buffer; + csh_init.history_buffer_size = sizeof(csh_history_buffer); +#endif + +#if defined(CONFIG_CSH_LNBUFF_STATIC) && CONFIG_CSH_LNBUFF_STATIC + static char csh_line_buffer[128]; + + /*!< set linebuffer */ + csh_init.line_buffer = csh_line_buffer; + csh_init.line_buffer_size = sizeof(csh_line_buffer); +#endif + + csh_init.uid = 0; + csh_init.user[0] = "cherry"; + + /*!< The port hash function is required, + and the strcmp attribute is used weakly by default, + int chry_shell_port_hash_strcmp(const char *hash, const char *str); */ + csh_init.hash[0] = "12345678"; /*!< If there is no password, set to NULL */ + csh_init.host = "cherryadb"; + csh_init.user_data = NULL; + + int ret = chry_shell_init(&csh, &csh_init); + if (ret) { + return -1; + } + + task_hdl_exec = NULL; + event_hdl = xEventGroupCreateStatic(&event_grp); + task_hdl_repl = xTaskCreateStatic(task_repl, "task_repl", 1024U, NULL, task_repl_PRIORITY, task_stack_repl, &task_buffer_repl); + + return 0; +} + +void shell_lock(void) +{ + xEventGroupSetBits(event_hdl, 0x01); + xEventGroupWaitBits(event_hdl, 0x02, pdTRUE, pdTRUE, portMAX_DELAY); +} + +void shell_unlock(void) +{ + xEventGroupSetBits(event_hdl, 0x04); + xEventGroupWaitBits(event_hdl, 0x08, pdTRUE, pdTRUE, portMAX_DELAY); +} + +static int csh_exit(int argc, char **argv) +{ + (void)argc; + (void)argv; + + usbd_adb_close(ADB_SHELL_LOALID); + + return 0; +} +CSH_SCMD_EXPORT_ALIAS(csh_exit, exit, ); + +#define __ENV_PATH "/sbin:/bin" +const char ENV_PATH[] = __ENV_PATH; +CSH_RVAR_EXPORT(ENV_PATH, PATH, sizeof(__ENV_PATH)); + +#define __ENV_ZERO "" +const char ENV_ZERO[] = __ENV_ZERO; +CSH_RVAR_EXPORT(ENV_ZERO, ZERO, sizeof(__ENV_ZERO)); diff --git a/demo/adb/usbd_adb_template.c b/demo/adb/usbd_adb_template.c new file mode 100644 index 00000000..0c367d8a --- /dev/null +++ b/demo/adb/usbd_adb_template.c @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2024, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "usbd_core.h" +#include "usbd_adb.h" + +/*!< endpoint address */ +#define WINUSB_IN_EP 0x81 +#define WINUSB_OUT_EP 0x02 + +#define USBD_VID 0xFFFF +#define USBD_PID 0xFFFF +#define USBD_MAX_POWER 100 +#define USBD_LANGID_STRING 1033 + +/*!< config descriptor size */ +#define USB_CONFIG_SIZE (9 + 9 + 7 + 7) + +#ifdef CONFIG_USB_HS +#define WINUSB_MAX_MPS 512 +#else +#define WINUSB_MAX_MPS 64 +#endif + +#define WCID_VENDOR_CODE 0x17 +#define ADB_INTF_NUM 0 + +__ALIGN_BEGIN const uint8_t WCID_StringDescriptor_MSOS[18] __ALIGN_END = { + /////////////////////////////////////// + /// MS OS string descriptor + /////////////////////////////////////// + 0x12, /* bLength */ + USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ + /* MSFT100 */ + 'M', 0x00, 'S', 0x00, 'F', 0x00, 'T', 0x00, /* wcChar_7 */ + '1', 0x00, '0', 0x00, '0', 0x00, /* wcChar_7 */ + WCID_VENDOR_CODE, /* bVendorCode */ + 0x00, /* bReserved */ +}; + +__ALIGN_BEGIN const uint8_t WINUSB_WCIDDescriptor[40] __ALIGN_END = { + /////////////////////////////////////// + /// WCID descriptor + /////////////////////////////////////// + 0x28, 0x00, 0x00, 0x00, /* dwLength */ + 0x00, 0x01, /* bcdVersion */ + 0x04, 0x00, /* wIndex */ + 0x01, /* bCount */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* bReserved_7 */ + + /////////////////////////////////////// + /// WCID function descriptor + /////////////////////////////////////// + ADB_INTF_NUM, /* bFirstInterfaceNumber */ + 0x01, /* bReserved */ + /* Compatible ID */ + 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, /* cCID_8: WINUSB */ + /* */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* cSubCID_8 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* bReserved_6 */ +}; + +__ALIGN_BEGIN const uint8_t WINUSB_IF0_WCIDProperties[142] __ALIGN_END = { + /////////////////////////////////////// + /// WCID property descriptor + /////////////////////////////////////// + 0x8e, 0x00, 0x00, 0x00, /* dwLength */ + 0x00, 0x01, /* bcdVersion */ + 0x05, 0x00, /* wIndex */ + 0x01, 0x00, /* wCount */ + + /////////////////////////////////////// + /// registry propter descriptor + /////////////////////////////////////// + 0x84, 0x00, 0x00, 0x00, /* dwSize */ + 0x01, 0x00, 0x00, 0x00, /* dwPropertyDataType */ + 0x28, 0x00, /* wPropertyNameLength */ + /* DeviceInterfaceGUID */ + 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, /* wcName_20 */ + 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, /* wcName_20 */ + 't', 0x00, 'e', 0x00, 'r', 0x00, 'f', 0x00, /* wcName_20 */ + 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, /* wcName_20 */ + 'U', 0x00, 'I', 0x00, 'D', 0x00, 0x00, 0x00, /* wcName_20 */ + 0x4e, 0x00, 0x00, 0x00, /* dwPropertyDataLength */ + /* {1D4B2365-4749-48EA-B38A-7C6FDDDD7E26} */ + '{', 0x00, '1', 0x00, 'D', 0x00, '4', 0x00, /* wcData_39 */ + 'B', 0x00, '2', 0x00, '3', 0x00, '6', 0x00, /* wcData_39 */ + '5', 0x00, '-', 0x00, '4', 0x00, '7', 0x00, /* wcData_39 */ + '4', 0x00, '9', 0x00, '-', 0x00, '4', 0x00, /* wcData_39 */ + '8', 0x00, 'E', 0x00, 'A', 0x00, '-', 0x00, /* wcData_39 */ + 'B', 0x00, '3', 0x00, '8', 0x00, 'A', 0x00, /* wcData_39 */ + '-', 0x00, '7', 0x00, 'C', 0x00, '6', 0x00, /* wcData_39 */ + 'F', 0x00, 'D', 0x00, 'D', 0x00, 'D', 0x00, /* wcData_39 */ + 'D', 0x00, '7', 0x00, 'E', 0x00, '2', 0x00, /* wcData_39 */ + '6', 0x00, '}', 0x00, 0x00, 0x00, /* wcData_39 */ +}; + +const uint8_t *WINUSB_IFx_WCIDProperties[] = { + WINUSB_IF0_WCIDProperties, +}; + +struct usb_msosv1_descriptor msosv1_desc = { + .string = WCID_StringDescriptor_MSOS, + .vendor_code = WCID_VENDOR_CODE, + .compat_id = WINUSB_WCIDDescriptor, + .comp_id_property = WINUSB_IFx_WCIDProperties, +}; + +/*!< global descriptor */ +static const uint8_t adb_descriptor[] = { + USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, USBD_VID, USBD_PID, 0x0100, 0x01), + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x01, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + ADB_DESCRIPTOR_INIT(ADB_INTF_NUM, WINUSB_IN_EP, WINUSB_OUT_EP, WINUSB_MAX_MPS), + /////////////////////////////////////// + /// string0 descriptor + /////////////////////////////////////// + USB_LANGID_INIT(USBD_LANGID_STRING), + /////////////////////////////////////// + /// string1 descriptor + /////////////////////////////////////// + 0x14, /* bLength */ + USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ + 'C', 0x00, /* wcChar0 */ + 'h', 0x00, /* wcChar1 */ + 'e', 0x00, /* wcChar2 */ + 'r', 0x00, /* wcChar3 */ + 'r', 0x00, /* wcChar4 */ + 'y', 0x00, /* wcChar5 */ + 'U', 0x00, /* wcChar6 */ + 'S', 0x00, /* wcChar7 */ + 'B', 0x00, /* wcChar8 */ + /////////////////////////////////////// + /// string2 descriptor + /////////////////////////////////////// + 0x14, /* bLength */ + USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ + 'C', 0x00, /* wcChar0 */ + 'h', 0x00, /* wcChar1 */ + 'e', 0x00, /* wcChar2 */ + 'r', 0x00, /* wcChar3 */ + 'r', 0x00, /* wcChar4 */ + 'y', 0x00, /* wcChar5 */ + 'A', 0x00, /* wcChar6 */ + 'D', 0x00, /* wcChar7 */ + 'B', 0x00, /* wcChar8 */ + /////////////////////////////////////// + /// string3 descriptor + /////////////////////////////////////// + 0x1C, /* bLength */ + USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ + 'C', 0x00, /* wcChar0 */ + 'h', 0x00, /* wcChar1 */ + 'e', 0x00, /* wcChar2 */ + 'r', 0x00, /* wcChar3 */ + 'r', 0x00, /* wcChar4 */ + 'y', 0x00, /* wcChar5 */ + 'A', 0x00, /* wcChar6 */ + 'D', 0x00, /* wcChar7 */ + 'B', 0x00, /* wcChar8 */ + '2', 0x00, /* wcChar9 */ + '0', 0x00, /* wcChar10 */ + '2', 0x00, /* wcChar11 */ + '4', 0x00, /* wcChar12 */ +#ifdef CONFIG_USB_HS + /////////////////////////////////////// + /// device qualifier descriptor + /////////////////////////////////////// + 0x0a, + USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER, + 0x00, + 0x02, + 0x02, + 0x02, + 0x01, + 0x40, + 0x01, + 0x00, +#endif + 0x00 +}; + +static void usbd_event_handler(uint8_t busid, uint8_t event) +{ + switch (event) { + case USBD_EVENT_RESET: + break; + case USBD_EVENT_CONNECTED: + break; + case USBD_EVENT_DISCONNECTED: + break; + case USBD_EVENT_RESUME: + break; + case USBD_EVENT_SUSPEND: + break; + case USBD_EVENT_CONFIGURED: + + break; + case USBD_EVENT_SET_REMOTE_WAKEUP: + break; + case USBD_EVENT_CLR_REMOTE_WAKEUP: + break; + + default: + break; + } +} + +static struct usbd_interface intf0; + +extern int shell_init(bool need_login); +void cherryadb_init(uint8_t busid, uint32_t reg_base) +{ + /* default password is : 12345678 */ + /* shell_init() must be called in-task */ + if (0 != shell_init(false)) { + /* shell failed to be initialized */ + printf("Failed to initialize shell\r\n"); + for (;;) { + ; + } + } + + usbd_desc_register(busid, adb_descriptor); + usbd_add_interface(busid, usbd_adb_init_intf(busid, &intf0, WINUSB_IN_EP, WINUSB_OUT_EP)); + usbd_initialize(busid, reg_base, usbd_event_handler); +} \ No newline at end of file