Skip to content

Simultaneous USB CDC and MSC #11245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
1 task done
PaulaScharf opened this issue Apr 14, 2025 · 1 comment
Open
1 task done

Simultaneous USB CDC and MSC #11245

PaulaScharf opened this issue Apr 14, 2025 · 1 comment
Labels
Status: Needs investigation We need to do some research before taking next steps on this issue

Comments

@PaulaScharf
Copy link

Board

sensebox_mcu_esp32s2

Device Description

schematics in case thats relevant

Hardware Configuration

no

Version

latest stable Release (if not listed below)

IDE Name

Arduino IDE 2.3.6

Operating System

Ubuntu

Flash frequency

80Mhz

PSRAM enabled

yes

Upload speed

921600

Description

In version 2.0.17 of arduino-esp32 we used to be able to use both MSC and CDC. Since version 3.0.0 or newer we have issues with that.

If only either MSC or CDC are enabled they each work fine.

However, when both CDC and MSC are enabled:​

  • We are able to receive Serial output through the serial monitor​
  • When trying to copy bin-files on it the device appears as read-only

Do we need to change anything in our boards.txt definition perhaps? Or could it be an issue with our TinyUF2?

##############################################################
# senseBox MCU ESP32-S2

sensebox_mcu_esp32s2.name=senseBox MCU-S2 ESP32-S2
sensebox_mcu_esp32s2.vid.0=0x303A
sensebox_mcu_esp32s2.pid.0=0x81B8
sensebox_mcu_esp32s2.vid.1=0x303A
sensebox_mcu_esp32s2.pid.1=0x81B9
sensebox_mcu_esp32s2.vid.2=0x303A
sensebox_mcu_esp32s2.pid.2=0x81BA

sensebox_mcu_esp32s2.bootloader.tool=esptool_py
sensebox_mcu_esp32s2.bootloader.tool.default=esptool_py

sensebox_mcu_esp32s2.upload.tool=esptool_py
sensebox_mcu_esp32s2.upload.tool.default=esptool_py
sensebox_mcu_esp32s2.upload.tool.network=esp_ota

sensebox_mcu_esp32s2.upload.maximum_size=1310720
sensebox_mcu_esp32s2.upload.maximum_data_size=327680
sensebox_mcu_esp32s2.upload.flags=
sensebox_mcu_esp32s2.upload.extra_flags=
sensebox_mcu_esp32s2.upload.use_1200bps_touch=true
sensebox_mcu_esp32s2.upload.wait_for_upload_port=true

sensebox_mcu_esp32s2.serial.disableDTR=false
sensebox_mcu_esp32s2.serial.disableRTS=false

sensebox_mcu_esp32s2.build.tarch=xtensa
sensebox_mcu_esp32s2.build.bootloader_addr=0x1000
sensebox_mcu_esp32s2.build.target=esp32s2
sensebox_mcu_esp32s2.build.mcu=esp32s2
sensebox_mcu_esp32s2.build.core=esp32
sensebox_mcu_esp32s2.build.variant=sensebox_mcu_esp32s2
sensebox_mcu_esp32s2.build.board=SENSEBOX_MCU_ESP32S2

sensebox_mcu_esp32s2.build.cdc_on_boot=1
sensebox_mcu_esp32s2.build.msc_on_boot=1
sensebox_mcu_esp32s2.build.dfu_on_boot=0
sensebox_mcu_esp32s2.build.f_cpu=240000000L
sensebox_mcu_esp32s2.build.flash_size=4MB
sensebox_mcu_esp32s2.build.flash_freq=80m
sensebox_mcu_esp32s2.build.flash_mode=dio
sensebox_mcu_esp32s2.build.boot=qio
sensebox_mcu_esp32s2.build.partitions=default
sensebox_mcu_esp32s2.build.defines=

sensebox_mcu_esp32s2.menu.CDCOnBoot.cdc=Enabled
sensebox_mcu_esp32s2.menu.CDCOnBoot.cdc.build.cdc_on_boot=1
sensebox_mcu_esp32s2.menu.CDCOnBoot.default=Disabled
sensebox_mcu_esp32s2.menu.CDCOnBoot.default.build.cdc_on_boot=0

sensebox_mcu_esp32s2.menu.MSCOnBoot.msc=Enabled
sensebox_mcu_esp32s2.menu.MSCOnBoot.msc.build.msc_on_boot=1
sensebox_mcu_esp32s2.menu.MSCOnBoot.default=Disabled
sensebox_mcu_esp32s2.menu.MSCOnBoot.default.build.msc_on_boot=0

sensebox_mcu_esp32s2.menu.DFUOnBoot.default=Disabled
sensebox_mcu_esp32s2.menu.DFUOnBoot.default.build.dfu_on_boot=0
sensebox_mcu_esp32s2.menu.DFUOnBoot.dfu=Enabled
sensebox_mcu_esp32s2.menu.DFUOnBoot.dfu.build.dfu_on_boot=1

sensebox_mcu_esp32s2.menu.UploadMode.cdc=Internal USB
sensebox_mcu_esp32s2.menu.UploadMode.cdc.upload.use_1200bps_touch=true
sensebox_mcu_esp32s2.menu.UploadMode.cdc.upload.wait_for_upload_port=true
sensebox_mcu_esp32s2.menu.UploadMode.default=UART0
sensebox_mcu_esp32s2.menu.UploadMode.default.upload.use_1200bps_touch=false
sensebox_mcu_esp32s2.menu.UploadMode.default.upload.wait_for_upload_port=false

sensebox_mcu_esp32s2.menu.PSRAM.enabled=Enabled
sensebox_mcu_esp32s2.menu.PSRAM.enabled.build.defines=-DBOARD_HAS_PSRAM
sensebox_mcu_esp32s2.menu.PSRAM.disabled=Disabled
sensebox_mcu_esp32s2.menu.PSRAM.disabled.build.defines=

sensebox_mcu_esp32s2.menu.PartitionScheme.tinyuf2=TinyUF2 4MB (1.3MB APP/960KB FATFS)
sensebox_mcu_esp32s2.menu.PartitionScheme.tinyuf2.build.custom_bootloader=bootloader-tinyuf2
sensebox_mcu_esp32s2.menu.PartitionScheme.tinyuf2.build.custom_partitions=partitions-4MB-tinyuf2
sensebox_mcu_esp32s2.menu.PartitionScheme.tinyuf2.upload.maximum_size=1441792
sensebox_mcu_esp32s2.menu.PartitionScheme.tinyuf2.upload.extra_flags=0x2d0000 "{runtime.platform.path}/variants/{build.variant}/tinyuf2.bin"
sensebox_mcu_esp32s2.menu.PartitionScheme.default=Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS)
sensebox_mcu_esp32s2.menu.PartitionScheme.default.build.partitions=default
sensebox_mcu_esp32s2.menu.PartitionScheme.defaultffat=Default 4MB with ffat (1.2MB APP/1.5MB FATFS)
sensebox_mcu_esp32s2.menu.PartitionScheme.defaultffat.build.partitions=default_ffat
sensebox_mcu_esp32s2.menu.PartitionScheme.minimal=Minimal (1.3MB APP/700KB SPIFFS)
sensebox_mcu_esp32s2.menu.PartitionScheme.minimal.build.partitions=minimal
sensebox_mcu_esp32s2.menu.PartitionScheme.no_ota=No OTA (2MB APP/2MB SPIFFS)
sensebox_mcu_esp32s2.menu.PartitionScheme.no_ota.build.partitions=no_ota
sensebox_mcu_esp32s2.menu.PartitionScheme.no_ota.upload.maximum_size=2097152
sensebox_mcu_esp32s2.menu.PartitionScheme.noota_3g=No OTA (1MB APP/3MB SPIFFS)
sensebox_mcu_esp32s2.menu.PartitionScheme.noota_3g.build.partitions=noota_3g
sensebox_mcu_esp32s2.menu.PartitionScheme.noota_3g.upload.maximum_size=1048576
sensebox_mcu_esp32s2.menu.PartitionScheme.noota_ffat=No OTA (2MB APP/2MB FATFS)
sensebox_mcu_esp32s2.menu.PartitionScheme.noota_ffat.build.partitions=noota_ffat
sensebox_mcu_esp32s2.menu.PartitionScheme.noota_ffat.upload.maximum_size=2097152
sensebox_mcu_esp32s2.menu.PartitionScheme.noota_3gffat=No OTA (1MB APP/3MB FATFS)
sensebox_mcu_esp32s2.menu.PartitionScheme.noota_3gffat.build.partitions=noota_3gffat
sensebox_mcu_esp32s2.menu.PartitionScheme.noota_3gffat.upload.maximum_size=1048576
sensebox_mcu_esp32s2.menu.PartitionScheme.huge_app=Huge APP (3MB No OTA/1MB SPIFFS)
sensebox_mcu_esp32s2.menu.PartitionScheme.huge_app.build.partitions=huge_app
sensebox_mcu_esp32s2.menu.PartitionScheme.huge_app.upload.maximum_size=3145728
sensebox_mcu_esp32s2.menu.PartitionScheme.min_spiffs=Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)
sensebox_mcu_esp32s2.menu.PartitionScheme.min_spiffs.build.partitions=min_spiffs
sensebox_mcu_esp32s2.menu.PartitionScheme.min_spiffs.upload.maximum_size=1966080

sensebox_mcu_esp32s2.menu.CPUFreq.240=240MHz (WiFi)
sensebox_mcu_esp32s2.menu.CPUFreq.240.build.f_cpu=240000000L
sensebox_mcu_esp32s2.menu.CPUFreq.160=160MHz (WiFi)
sensebox_mcu_esp32s2.menu.CPUFreq.160.build.f_cpu=160000000L
sensebox_mcu_esp32s2.menu.CPUFreq.80=80MHz (WiFi)
sensebox_mcu_esp32s2.menu.CPUFreq.80.build.f_cpu=80000000L
sensebox_mcu_esp32s2.menu.CPUFreq.40=40MHz
sensebox_mcu_esp32s2.menu.CPUFreq.40.build.f_cpu=40000000L
sensebox_mcu_esp32s2.menu.CPUFreq.20=20MHz
sensebox_mcu_esp32s2.menu.CPUFreq.20.build.f_cpu=20000000L
sensebox_mcu_esp32s2.menu.CPUFreq.10=10MHz
sensebox_mcu_esp32s2.menu.CPUFreq.10.build.f_cpu=10000000L

sensebox_mcu_esp32s2.menu.FlashMode.qio=QIO
sensebox_mcu_esp32s2.menu.FlashMode.qio.build.flash_mode=dio
sensebox_mcu_esp32s2.menu.FlashMode.qio.build.boot=qio
sensebox_mcu_esp32s2.menu.FlashMode.dio=DIO
sensebox_mcu_esp32s2.menu.FlashMode.dio.build.flash_mode=dio
sensebox_mcu_esp32s2.menu.FlashMode.dio.build.boot=dio
sensebox_mcu_esp32s2.menu.FlashMode.qout=QOUT
sensebox_mcu_esp32s2.menu.FlashMode.qout.build.flash_mode=dout
sensebox_mcu_esp32s2.menu.FlashMode.qout.build.boot=qout
sensebox_mcu_esp32s2.menu.FlashMode.dout=DOUT
sensebox_mcu_esp32s2.menu.FlashMode.dout.build.flash_mode=dout
sensebox_mcu_esp32s2.menu.FlashMode.dout.build.boot=dout

sensebox_mcu_esp32s2.menu.FlashFreq.80=80MHz
sensebox_mcu_esp32s2.menu.FlashFreq.80.build.flash_freq=80m
sensebox_mcu_esp32s2.menu.FlashFreq.40=40MHz
sensebox_mcu_esp32s2.menu.FlashFreq.40.build.flash_freq=40m

sensebox_mcu_esp32s2.menu.FlashSize.4M=4MB (32Mb)
sensebox_mcu_esp32s2.menu.FlashSize.4M.build.flash_size=4MB

sensebox_mcu_esp32s2.menu.UploadSpeed.921600=921600
sensebox_mcu_esp32s2.menu.UploadSpeed.921600.upload.speed=921600
sensebox_mcu_esp32s2.menu.UploadSpeed.115200=115200
sensebox_mcu_esp32s2.menu.UploadSpeed.115200.upload.speed=115200
sensebox_mcu_esp32s2.menu.UploadSpeed.256000.windows=256000
sensebox_mcu_esp32s2.menu.UploadSpeed.256000.upload.speed=256000
sensebox_mcu_esp32s2.menu.UploadSpeed.230400.windows.upload.speed=256000
sensebox_mcu_esp32s2.menu.UploadSpeed.230400=230400
sensebox_mcu_esp32s2.menu.UploadSpeed.230400.upload.speed=230400
sensebox_mcu_esp32s2.menu.UploadSpeed.460800.linux=460800
sensebox_mcu_esp32s2.menu.UploadSpeed.460800.macosx=460800
sensebox_mcu_esp32s2.menu.UploadSpeed.460800.upload.speed=460800
sensebox_mcu_esp32s2.menu.UploadSpeed.512000.windows=512000
sensebox_mcu_esp32s2.menu.UploadSpeed.512000.upload.speed=512000

sensebox_mcu_esp32s2.menu.DebugLevel.none=None
sensebox_mcu_esp32s2.menu.DebugLevel.none.build.code_debug=0
sensebox_mcu_esp32s2.menu.DebugLevel.error=Error
sensebox_mcu_esp32s2.menu.DebugLevel.error.build.code_debug=1
sensebox_mcu_esp32s2.menu.DebugLevel.warn=Warn
sensebox_mcu_esp32s2.menu.DebugLevel.warn.build.code_debug=2
sensebox_mcu_esp32s2.menu.DebugLevel.info=Info
sensebox_mcu_esp32s2.menu.DebugLevel.info.build.code_debug=3
sensebox_mcu_esp32s2.menu.DebugLevel.debug=Debug
sensebox_mcu_esp32s2.menu.DebugLevel.debug.build.code_debug=4
sensebox_mcu_esp32s2.menu.DebugLevel.verbose=Verbose
sensebox_mcu_esp32s2.menu.DebugLevel.verbose.build.code_debug=5

sensebox_mcu_esp32s2.menu.EraseFlash.none=Disabled
sensebox_mcu_esp32s2.menu.EraseFlash.none.upload.erase_cmd=
sensebox_mcu_esp32s2.menu.EraseFlash.all=Enabled
sensebox_mcu_esp32s2.menu.EraseFlash.all.upload.erase_cmd=-e

Any insight appreciated. Thank you for this nice repo.

Sketch

void setup() {
  Serial.begin(115200);
}

void loop() {
  Serial.println("hello");
  delay(500);
}

Debug Message

> Error while copying to 'disk'. 
The destination is read-only

Other Steps to Reproduce

No response

I have checked existing issues, online documentation and the Troubleshooting Guide

  • I confirm I have checked existing issues, online documentation and Troubleshooting guide.
@PaulaScharf PaulaScharf added the Status: Awaiting triage Issue is waiting for triage label Apr 14, 2025
@Jason2866 Jason2866 added Status: Needs investigation We need to do some research before taking next steps on this issue and removed Status: Awaiting triage Issue is waiting for triage labels Apr 14, 2025
@nitz
Copy link
Contributor

nitz commented Apr 30, 2025

I have this issue too, and have spent almost 2 weeks trying to fix it. The real kicker for me, is it worked sometimes, but I couldn't figure out why.

I will spare everyone the absolute nightmare of a journey I went on, and present what I believe is the heart of the problem, and a workaround:

Workaround

It seems that there are two instances of the function tud_msc_is_writable_cb, and the wrong one gets linked (usually.)

The workaround is to comment out or remove the __attribute__((weak)) function declaration here:

__attribute__((weak)) bool tud_msc_is_writable_cb(uint8_t lun) {
return false;
}

This leaves the tud_msc_is_writable_cb in USBMSC.cpp as the only applicable candidate to be linked. Now the tinyusb stack invokes the correct callback, and the device will properly report that it is, in fact, writable.

Root Cause

As to when and why this happened, I can't figure out. It does explain the behavior where it would work correctly for me seemingly out of the blue sometimes, because it is dependent on the order in which files are passed to the linker. As long as USBMSC.cpp's object is passed to the linker before esp32-hal-tinyusb.c's object, the correct implementation of tud_msc_is_writable_cb is linked. If it's the other way around (which seems to be the typical case for me,) the implementation that just returns false gets linked, and the correct one gets dropped from the binary completely.

It seems like it was likely previously working no matter what, with older versions of the compiler toolchain. Using godbolt.org I was able to build a small setup that narrowed down when the behavior in GCC changed between the versions 10.3 and 10.4, though I couldn't replicate the behavior exactly. You can see that the little program there outputs 1 in the case of compiling/linking with 10.3, but the exact same code and link order produces a 0 when doing so with 10.4.

I think it has something to do with how the header is declaring the symbol with the weak attribute and "C" linkage, but then the actual definitions are in separate translation units (one being C, the other C++) This mimics how tud_msc_is_writable_cb is declared/defined in arduino-esp32: One with the weak attribute in a C translation unit of esp32-hal-tinyusb.c, and another in the C++ translation unit of USBMSC.cpp.

But this is the thing that really bakes my noodle: If the linker selects the wrong tud_msc_is_writable_cb, why does the exact same thing not happen for tud_msc_write10_cb, tud_msc_read10_cb, tud_msc_scsi_cb, etc. Those are all also defined in both files, but the correct ones seem to be the ones linked in every other case!

The definitions of the tud_ callbacks in USBMSC.cpp are not marked extern "C", which I feel like means they should have mangled names, and thus don't get linked correctly... except that isn't what happens for all the callbacks not named tud_msc_is_writable_cb, so clearly the declarations of them within an extern "C" block seem to be enough to get them to be named plainly.

Suggested "Fix"?

All of the bizarre linkage issues aside, it seems the real bugbear is just the presence of the extra definitions of the callback functions, despite being marked __attribute__((weak)). It seems that those should be removed in the cases where there are real definitions elsewhere in the library. I don't think it makes much sense to have them defined twice anyways, even if one set is marked as weak. (This would go for all the tud_ callbacks, imo.)


I can't believe I've lost so much time on the linker picking the wrong function. 🙃 In conclusion, my head hurts and I would like a nap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Needs investigation We need to do some research before taking next steps on this issue
Projects
None yet
Development

No branches or pull requests

3 participants