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.
74 lines
2.5 KiB
C++
74 lines
2.5 KiB
C++
#pragma once
|
|
|
|
#include "fat12.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <functional>
|
|
#include <vector>
|
|
#include <string>
|
|
|
|
namespace esphome {
|
|
namespace grub_boot_switch {
|
|
|
|
constexpr uint32_t SECTOR_COUNT = 128;
|
|
constexpr uint32_t SECTOR_SIZE = 512;
|
|
constexpr uint32_t SECTORS_PER_CLUSTER = 1;
|
|
constexpr uint32_t RESERVED_SECTORS = 1;
|
|
constexpr uint32_t FAT_COPIES = 2;
|
|
constexpr uint32_t ROOT_ENTRIES = 512;
|
|
constexpr uint32_t ROOT_ENTRY_LENGTH = 32;
|
|
constexpr uint32_t FILEDATA_START_CLUSTER = 3;
|
|
constexpr uint32_t DATA_REGION_SECTOR =
|
|
RESERVED_SECTORS + FAT_COPIES + (ROOT_ENTRIES * ROOT_ENTRY_LENGTH) / SECTOR_SIZE;
|
|
constexpr uint32_t FILEDATA_START_SECTOR =
|
|
DATA_REGION_SECTOR + (FILEDATA_START_CLUSTER - 2) * SECTORS_PER_CLUSTER;
|
|
|
|
using FileReadCallback = std::function<void(uint8_t *output)>;
|
|
|
|
struct VirtualFile {
|
|
std::string long_name;
|
|
FatDirEntry dir;
|
|
FileReadCallback read;
|
|
};
|
|
|
|
class MscDisk {
|
|
public:
|
|
MscDisk();
|
|
|
|
// Volume serial baked into the FAT boot sector.
|
|
// Default 0x55AA6922 matches the upstream stecman/hw-boot-selection firmware
|
|
// so existing GRUB hooks (search --fs-uuid 55AA-6922) keep working unchanged.
|
|
void set_volume_serial(uint32_t serial) { volume_serial_ = serial; }
|
|
void set_volume_label(const char *label); // 11 chars, space-padded
|
|
|
|
// Date stamped on every directory entry. GRUB 2.06 rejects 0-valued dates.
|
|
void set_file_date(uint16_t year, uint8_t month, uint8_t day);
|
|
|
|
// Register a file. Short name must be 8.3 with space padding handled by caller
|
|
// (use add_file() helper below for typical cases). size is the file's logical
|
|
// size in bytes; content_cb is called once per host read of the file's sector.
|
|
void add_file(const std::string &long_name, const std::string &short_name,
|
|
const std::string &short_ext, uint32_t size, FileReadCallback content_cb);
|
|
|
|
// Synthesise a single 512-byte sector for LBA `lba` into `out`.
|
|
// out is zeroed first. Returns 0 on success.
|
|
int read_lba(uint32_t lba, uint8_t *out);
|
|
|
|
uint32_t sector_count() const { return SECTOR_COUNT; }
|
|
uint32_t sector_size() const { return SECTOR_SIZE; }
|
|
|
|
protected:
|
|
void write_boot_sector(uint8_t *out);
|
|
void write_fat_sector(uint8_t *out);
|
|
void write_dir_sector(uint8_t *out);
|
|
|
|
uint32_t volume_serial_ = 0x55AA6922u;
|
|
char volume_label_[11] = {'S', 'W', 'I', 'T', 'C', 'H', ' ', ' ', ' ', ' ', ' '};
|
|
uint16_t file_date_ = 0;
|
|
std::vector<VirtualFile> files_;
|
|
};
|
|
|
|
} // namespace grub_boot_switch
|
|
} // namespace esphome
|