ESPHome external_component that presents a synthetic 64 KB FAT12 disk over the ESP32-S3's native USB-OTG, containing a one-byte switch_position file and a switch_position_grub.cfg snippet. The disk's volume serial is fixed at 0x55AA6922 to stay drop-in compatible with the upstream /etc/grub.d hook. Components: - fat12 / msc_disk: portable FAT12 sector synthesiser ported from reference/src/fat.c and the boot-sector / FAT / dir-entry generation in reference/src/usb.c. - usb_descriptors / tusb_glue: TinyUSB MSC class with our own callbacks delegating to MscDisk::read_lba (linker --allow-multiple-definition picks ours over esp_tinyusb's storage-backed defaults). - grub_boot_switch: top-level Component, NVS persistence on target changes, exposes targets table to sub-platforms. - select: HA-facing select platform reading parent's targets map. Validated end-to-end on an ESP32-S3-DevKitC-1: USB enumerates as 26ba:8003, /dev/sdb mounts FAT12, mtype reads back the live target, and POST /select/boot_target/set?option=Windows updates both files on the next host read.
111 lines
3.8 KiB
C++
111 lines
3.8 KiB
C++
#include "usb_descriptors.h"
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "esp_mac.h"
|
|
|
|
namespace esphome {
|
|
namespace grub_boot_switch {
|
|
|
|
// USB constants we need without dragging in <tusb.h> from this header
|
|
constexpr uint8_t USB_DESC_DEVICE = 0x01;
|
|
constexpr uint8_t USB_DESC_CONFIGURATION = 0x02;
|
|
constexpr uint8_t USB_DESC_INTERFACE = 0x04;
|
|
constexpr uint8_t USB_DESC_ENDPOINT = 0x05;
|
|
|
|
constexpr uint8_t USB_CLASS_MSC = 0x08;
|
|
constexpr uint8_t USB_MSC_SUBCLASS_SCSI = 0x06;
|
|
constexpr uint8_t USB_MSC_PROTOCOL_BBB = 0x50;
|
|
|
|
constexpr uint8_t EP_BULK = 0x02;
|
|
|
|
// Mirrors the layout in reference/src/usb.c so the host sees the same shape
|
|
// as the original STM32 firmware.
|
|
constexpr uint8_t MSC_EP_IN = 0x81;
|
|
constexpr uint8_t MSC_EP_OUT = 0x01;
|
|
constexpr uint16_t MSC_EP_SIZE = 64; // Full-speed bulk
|
|
|
|
static void put16le(uint8_t *p, uint16_t v) {
|
|
p[0] = v & 0xFF;
|
|
p[1] = (v >> 8) & 0xFF;
|
|
}
|
|
|
|
void usb_descriptors_build(UsbDescriptorBundle *b, uint16_t vid, uint16_t pid,
|
|
const char *manufacturer, const char *product,
|
|
const char *serial_override) {
|
|
// ---- Device descriptor ----
|
|
uint8_t *d = b->device;
|
|
d[0] = 18; // bLength
|
|
d[1] = USB_DESC_DEVICE; // bDescriptorType
|
|
put16le(d + 2, 0x0200); // bcdUSB
|
|
d[4] = 0x00; // bDeviceClass (defined in interface)
|
|
d[5] = 0x00; // bDeviceSubClass
|
|
d[6] = 0x00; // bDeviceProtocol
|
|
d[7] = 64; // bMaxPacketSize0
|
|
put16le(d + 8, vid);
|
|
put16le(d + 10, pid);
|
|
put16le(d + 12, 0x0200); // bcdDevice
|
|
d[14] = 1; // iManufacturer (string index)
|
|
d[15] = 2; // iProduct
|
|
d[16] = 3; // iSerialNumber
|
|
d[17] = 1; // bNumConfigurations
|
|
|
|
// ---- Configuration descriptor (config + interface + 2 endpoints) ----
|
|
uint8_t *c = b->config;
|
|
// Configuration
|
|
c[0] = 9;
|
|
c[1] = USB_DESC_CONFIGURATION;
|
|
put16le(c + 2, 32); // wTotalLength
|
|
c[4] = 1; // bNumInterfaces
|
|
c[5] = 1; // bConfigurationValue
|
|
c[6] = 0; // iConfiguration
|
|
c[7] = 0x80; // bmAttributes (bus powered)
|
|
c[8] = 50; // bMaxPower (100mA)
|
|
// Interface
|
|
c[9] = 9;
|
|
c[10] = USB_DESC_INTERFACE;
|
|
c[11] = 0; // bInterfaceNumber
|
|
c[12] = 0; // bAlternateSetting
|
|
c[13] = 2; // bNumEndpoints
|
|
c[14] = USB_CLASS_MSC;
|
|
c[15] = USB_MSC_SUBCLASS_SCSI;
|
|
c[16] = USB_MSC_PROTOCOL_BBB;
|
|
c[17] = 0; // iInterface
|
|
// Endpoint OUT
|
|
c[18] = 7;
|
|
c[19] = USB_DESC_ENDPOINT;
|
|
c[20] = MSC_EP_OUT;
|
|
c[21] = EP_BULK;
|
|
put16le(c + 22, MSC_EP_SIZE);
|
|
c[24] = 0;
|
|
// Endpoint IN
|
|
c[25] = 7;
|
|
c[26] = USB_DESC_ENDPOINT;
|
|
c[27] = MSC_EP_IN;
|
|
c[28] = EP_BULK;
|
|
put16le(c + 29, MSC_EP_SIZE);
|
|
c[31] = 0;
|
|
|
|
// ---- Strings ----
|
|
// Index 0: language ID (English, US) — esp_tinyusb expects index 0 to be the
|
|
// language; it builds the actual descriptor from a static "0x0409" string.
|
|
static const char kLang[] = {0x09, 0x04, 0};
|
|
b->strings[0] = kLang;
|
|
b->strings[1] = manufacturer;
|
|
b->strings[2] = product;
|
|
|
|
if (serial_override && serial_override[0] != '\0') {
|
|
snprintf(b->serial_buf, sizeof(b->serial_buf), "%s", serial_override);
|
|
} else {
|
|
uint8_t mac[6] = {};
|
|
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
|
snprintf(b->serial_buf, sizeof(b->serial_buf), "%02X%02X%02X%02X%02X%02X", mac[0],
|
|
mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
}
|
|
b->strings[3] = b->serial_buf;
|
|
}
|
|
|
|
} // namespace grub_boot_switch
|
|
} // namespace esphome
|