Files
adamksmith a911b9e4cb Initial port of stecman/hw-boot-selection to ESP32-S3
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.
2026-05-04 18:25:41 +00:00

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