// TinyUSB MSC class callback implementations. // // These are weak symbols inside TinyUSB; defining them here overrides the // stubs in esp_tinyusb's MSC wrapper so we control the SCSI / block-device // surface entirely from our component, with no on-device storage. #include "grub_boot_switch.h" #include "tusb.h" #include "class/msc/msc_device.h" #include namespace esphome { namespace grub_boot_switch { // Set by GrubBootSwitch::start_usb_() before tinyusb_driver_install() runs. // Single-instance assumption (MULTI_CONF=False in __init__.py). GrubBootSwitch *g_active_component = nullptr; } // namespace grub_boot_switch } // namespace esphome using esphome::grub_boot_switch::g_active_component; extern "C" { // SCSI Inquiry: vendor / product / revision strings. Mirrors the SCSI strings // from reference/src/usb.c so the host sees the same identity as the STM32 // firmware. void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { (void) lun; static const char kVendor[] = "STECMAN "; static const char kProduct[] = "BOOT SWITCH "; static const char kRev[] = "V1 "; memcpy(vendor_id, kVendor, 8); memcpy(product_id, kProduct, 16); memcpy(product_rev, kRev, 4); } // Unit ready: report ready once the component finished setup and we have a // disk to serve. bool tud_msc_test_unit_ready_cb(uint8_t lun) { (void) lun; return g_active_component != nullptr; } // Capacity in blocks. void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) { (void) lun; if (g_active_component != nullptr) { *block_count = g_active_component->sector_count(); *block_size = static_cast(g_active_component->sector_size()); } else { *block_count = 0; *block_size = 512; } } // Start/stop unit: we have no media to load/eject, just acknowledge. bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { (void) lun; (void) power_condition; (void) start; (void) load_eject; return true; } // Read10: TinyUSB asks for `bufsize` bytes starting at (lba, offset). Our // disk is sector-granular (offset is always 0 for typical hosts), so we // generate the full sector and copy out the requested slice. int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize) { (void) lun; if (g_active_component == nullptr) return -1; if (lba >= g_active_component->sector_count()) return -1; uint8_t sector[512]; g_active_component->read_lba(lba, sector); uint32_t to_copy = bufsize; if (offset + to_copy > sizeof(sector)) to_copy = sizeof(sector) - offset; memcpy(buffer, sector + offset, to_copy); return static_cast(to_copy); } // Write10: silently accept and discard. Hosts (especially Windows) sometimes // scribble FAT metadata back; failing the writes makes them retry forever. int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) { (void) lun; (void) lba; (void) offset; (void) buffer; return static_cast(bufsize); } // Report writability — false → host treats us as read-only media (the GRUB // use case is read-only by design). bool tud_msc_is_writable_cb(uint8_t lun) { (void) lun; return false; } // SCSI passthrough for unsupported commands. Returning -1 + sense data // lets TinyUSB STALL the appropriate endpoint. int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize) { (void) lun; (void) scsi_cmd; (void) buffer; (void) bufsize; return -1; } } // extern "C"