Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ if(CONFIG_CTSHELL_PORT_ESP32)
list(APPEND CTSHELL_DEFINITIONS "CONFIG_CTSHELL_PORT_ESP32=1")
endif()

if(CONFIG_CTSHELL_PORT_CHERRYUSB)
list(APPEND ctshell_srcs "${CMAKE_CURRENT_SOURCE_DIR}/port/cherryusb/ctshell_cherryusb.c")
list(APPEND ctshell_incs "${CMAKE_CURRENT_SOURCE_DIR}/port/cherryusb")
list(APPEND CTSHELL_DEFINITIONS "CONFIG_CTSHELL_PORT_CHERRYUSB=1")
endif()

if(CONFIG_CTSHELL_USE_FS)
list(APPEND CTSHELL_DEFINITIONS "CONFIG_CTSHELL_USE_FS=1")
if(CONFIG_CTSHELL_USE_FS_FATFS)
Expand Down
4 changes: 4 additions & 0 deletions Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ config CTSHELL_PORT_WINDOWS
bool
prompt "Windows(for test use)"

config CTSHELL_PORT_CHERRYUSB
bool
prompt "CherryUSB"

endchoice

if CTSHELL_PORT_ESP32
Expand Down
5 changes: 5 additions & 0 deletions port/cherryusb/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Note

You need to configure CherryUSB by yourself first.

This port is modified from CherryUSB cdc acm template.
242 changes: 242 additions & 0 deletions port/cherryusb/ctshell_cherryusb.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
/*
* Copyright (c) 2024, sakumisu
* Copyright (c) 2026, MDLZCOOL
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbd_core.h"
#include "usbd_cdc_acm.h"
#include "ctshell_cherryusb.h"

static ctshell_ctx_t shell_ctx;

/*!< endpoint address */
#define CDC_IN_EP 0x81
#define CDC_OUT_EP 0x02
#define CDC_INT_EP 0x83

#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 + CDC_ACM_DESCRIPTOR_LEN)

#ifdef CONFIG_USB_HS
#define CDC_MAX_MPS 512
#else
#define CDC_MAX_MPS 64
#endif

/*!< global descriptor */
static const uint8_t cdc_descriptor[] = {
USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID, 0x0100, 0x01),
USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, CDC_MAX_MPS, 0x02),
///////////////////////////////////////
/// 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
///////////////////////////////////////
0x26, /* 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 */
' ', 0x00, /* wcChar9 */
'C', 0x00, /* wcChar10 */
'D', 0x00, /* wcChar11 */
'C', 0x00, /* wcChar12 */
' ', 0x00, /* wcChar13 */
'D', 0x00, /* wcChar14 */
'E', 0x00, /* wcChar15 */
'M', 0x00, /* wcChar16 */
'O', 0x00, /* wcChar17 */
///////////////////////////////////////
/// string3 descriptor
///////////////////////////////////////
0x16, /* bLength */
USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
'2', 0x00, /* wcChar0 */
'0', 0x00, /* wcChar1 */
'2', 0x00, /* wcChar2 */
'2', 0x00, /* wcChar3 */
'1', 0x00, /* wcChar4 */
'2', 0x00, /* wcChar5 */
'3', 0x00, /* wcChar6 */
'4', 0x00, /* wcChar7 */
'5', 0x00, /* wcChar8 */
'6', 0x00, /* wcChar9 */
#ifdef CONFIG_USB_HS
///////////////////////////////////////
/// device qualifier descriptor
///////////////////////////////////////
0x0a,
USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER,
0x00,
0x02,
0x02,
0x02,
0x01,
0x40,
0x00,
0x00,
#endif
0x00
};

USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t read_buffer[2048]; /* 2048 is only for test speed , please use CDC_MAX_MPS for common*/
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t write_buffer[2048];

volatile bool ep_tx_busy_flag = false;

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:
ep_tx_busy_flag = false;
/* setup first out ep read transfer */
usbd_ep_start_read(busid, CDC_OUT_EP, read_buffer, 2048);
break;
case USBD_EVENT_SET_REMOTE_WAKEUP:
break;
case USBD_EVENT_CLR_REMOTE_WAKEUP:
break;

default:
break;
}
Comment on lines +116 to +141
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear the session flags on reset/disconnect.

dtr_enable is only driven from usbd_cdc_acm_set_dtr(). If the host drops off the bus after setting DTR, later writes still enter the transmit path and spin until timeout because the link state was never invalidated.

Proposed fix
-volatile bool ep_tx_busy_flag = false;
+volatile bool ep_tx_busy_flag = false;
+static volatile uint8_t dtr_enable = 0;
@@
 	case USBD_EVENT_RESET:
+	  dtr_enable = 0;
+	  ep_tx_busy_flag = false;
 	  break;
@@
 	case USBD_EVENT_DISCONNECTED:
+	  dtr_enable = 0;
+	  ep_tx_busy_flag = false;
 	  break;
@@
 	case USBD_EVENT_SUSPEND:
+	  dtr_enable = 0;
+	  ep_tx_busy_flag = false;
 	  break;
@@
-volatile uint8_t dtr_enable = 0;
-
 void usbd_cdc_acm_set_dtr(uint8_t busid, uint8_t intf, bool dtr)

Also applies to: 194-203

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@port/cherryusb/ctshell_cherryusb.c` around lines 116 - 141, The USB event
handler must clear session state when the link is lost: in usbd_event_handler
handle USBD_EVENT_RESET and USBD_EVENT_DISCONNECTED by resetting dtr_enable =
false and ep_tx_busy_flag = false (and any other per-session flags your transmit
path uses) so subsequent writes won't spin; keep the existing behavior for
USBD_EVENT_CONFIGURED (start read) but ensure you also stop/cancel or ignore
pending transfers if applicable. Apply the same changes to the other equivalent
event block noted (the second handler around lines 194-203) so both RESET and
DISCONNECTED clear dtr_enable and ep_tx_busy_flag.

}

void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
for (uint32_t i = 0; i < nbytes; i++) {
ctshell_input(&shell_ctx, read_buffer[i]);
}
/* setup next out ep read transfer */
usbd_ep_start_read(busid, CDC_OUT_EP, read_buffer, 2048);
}

void usbd_cdc_acm_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
//USB_LOG_RAW("actual in len:%d\r\n", nbytes);

if ((nbytes % usbd_get_ep_mps(busid, ep)) == 0 && nbytes) {
/* send zlp */
usbd_ep_start_write(busid, CDC_IN_EP, NULL, 0);
} else {
ep_tx_busy_flag = false;
}
}

/*!< endpoint call back */
struct usbd_endpoint cdc_out_ep = {
.ep_addr = CDC_OUT_EP,
.ep_cb = usbd_cdc_acm_bulk_out
};

struct usbd_endpoint cdc_in_ep = {
.ep_addr = CDC_IN_EP,
.ep_cb = usbd_cdc_acm_bulk_in
};

static struct usbd_interface intf0;
static struct usbd_interface intf1;

void cdc_acm_init(uint8_t busid, uintptr_t reg_base)
{
const uint8_t data[10] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30 };

memcpy(&write_buffer[0], data, 10);
memset(&write_buffer[10], 'a', 2038);

usbd_desc_register(busid, cdc_descriptor);
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf0));
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf1));
usbd_add_endpoint(busid, &cdc_out_ep);
usbd_add_endpoint(busid, &cdc_in_ep);
usbd_initialize(busid, reg_base, usbd_event_handler);
Comment on lines +179 to +191
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make shell_usb_write() use the configured bus and send the full payload.

Line 228 hard-codes bus 0 even though cdc_acm_init() accepts busid, and Line 220 silently truncates anything larger than 2048 bytes. That makes non-zero bus configurations fail and cuts off longer shell output.

Proposed fix
+static uint8_t cdc_busid;
+
 void cdc_acm_init(uint8_t busid, uintptr_t reg_base)
 {
+  cdc_busid = busid;
   const uint8_t data[10] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30 };
 
   memcpy(&write_buffer[0], data, 10);
   memset(&write_buffer[10], 'a', 2038);
@@
 void shell_usb_write(const char *buf, uint16_t len, void *priv)
 {
   if (!dtr_enable || len == 0) {
 	return;
   }
-  uint16_t send_len = (len > 2048) ? 2048 : len;
-  uint32_t timeout = 0xFFFFF;
-  while (ep_tx_busy_flag && timeout > 0) {
-	timeout--;
-  }
-  if (timeout == 0) return;
-  memcpy(write_buffer, buf, send_len);
-  ep_tx_busy_flag = true;
-  usbd_ep_start_write(0, CDC_IN_EP, write_buffer, send_len);
+  while (len > 0) {
+	uint16_t send_len = (len > sizeof(write_buffer)) ? sizeof(write_buffer) : len;
+	uint32_t timeout = 0xFFFFF;
+
+	while (ep_tx_busy_flag && timeout > 0) {
+	  timeout--;
+	}
+	if (timeout == 0) {
+	  return;
+	}
+
+	memcpy(write_buffer, buf, send_len);
+	ep_tx_busy_flag = true;
+	usbd_ep_start_write(cdc_busid, CDC_IN_EP, write_buffer, send_len);
+
+	buf += send_len;
+	len -= send_len;
+  }
 }

Also applies to: 215-228

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@port/cherryusb/ctshell_cherryusb.c` around lines 179 - 191, cdc_acm_init
stores the active USB bus via its busid but shell_usb_write is still hardcoding
bus 0 and truncating payloads to 2048 bytes; update the code so shell_usb_write
uses the configured busid from cdc_acm_init (e.g., store busid in a
static/global like current_bus or pass it through) instead of 0, and remove the
silent 2048-byte truncation so the function transmits the full payload length
(or fragments/sends in a loop if underlying USB endpoint limits require it)
using the write_buffer/its actual length when calling the send/submit routines.

}

volatile uint8_t dtr_enable = 0;

void usbd_cdc_acm_set_dtr(uint8_t busid, uint8_t intf, bool dtr)
{
if (dtr) {
dtr_enable = 1;
} else {
dtr_enable = 0;
}
}

void cdc_acm_data_send_with_dtr_test(uint8_t busid)
{
if (dtr_enable) {
ep_tx_busy_flag = true;
usbd_ep_start_write(busid, CDC_IN_EP, write_buffer, 2048);
while (ep_tx_busy_flag) {
}
}
}

static uint8_t priv_busid;

void shell_usb_write(const char *buf, uint16_t len, void *priv)
{
if (!dtr_enable || len == 0) {
return;
}
uint16_t send_len = (len > 2048) ? 2048 : len;
uint32_t timeout = 0xFFFFF;
while (ep_tx_busy_flag && timeout > 0) {
timeout--;
}
if (timeout == 0) return;
memcpy(write_buffer, buf, send_len);
ep_tx_busy_flag = true;
usbd_ep_start_write(priv_busid, CDC_IN_EP, write_buffer, send_len);
}

void ctshell_port_cherryusb_init(ctshell_ctx_t *ctx, uint8_t busid, uintptr_t reg_base)
{
ctshell_io_t shell_io = {
.write = shell_usb_write,
};
priv_busid = busid;

ctshell_init(ctx, shell_io, NULL);
cdc_acm_init(busid, reg_base);
}
19 changes: 19 additions & 0 deletions port/cherryusb/ctshell_cherryusb.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2026, MDLZCOOL
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once

#include "ctshell.h"
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

void ctshell_port_cherryusb_init(ctshell_ctx_t *ctx, uint8_t busid, uintptr_t reg_base);

#ifdef __cplusplus
}
#endif
Loading