Loading files
/**
* @file host_link/host.cpp
* @brief hostlink::Host implementation (target-only: CDC + HAL UID).
*/
#include "alchemy/host_link/host.h"
#include <cstring>
#include <new>
#include "daisy_seed.h"
#include "stm32h7xx_hal.h" /* HAL_GetUIDw*, HAL_NVIC_SystemReset */
#include "alchemy/host_link/cdc_transport.h"
#include "alchemy/surface/control_loop.h"
#include "alchemy/surface/jack.h"
#include "alchemy/surface/page.h"
#include "alchemy/surface/presets.h"
#include "alchemy/surface/virtual_button.h"
#include "alchemy/version.h"
namespace alchemy {
namespace hostlink {
/* One Host per system (the CDC callback is a hardware singleton), so
* SDK-owned default buffers are safe to share. SDRAM: big, cold, not
* zeroed by startup. Readers stay within the length last written.
* Factory defaults have dedicated storage: GET_LIVE reuses s_snapshot.
* A refresh builds in scratch before touching the published descriptor. */
static char DSY_SDRAM_BSS s_descriptor[24u * 1024u];
static char DSY_SDRAM_BSS s_descriptor_refresh[sizeof s_descriptor];
static uint8_t DSY_SDRAM_BSS s_factory[kPresetBlobCapacity];
static uint8_t DSY_SDRAM_BSS s_staging [kPresetBlobCapacity];
static uint8_t DSY_SDRAM_BSS s_snapshot[kPresetBlobCapacity];
static CdcUsbTransport s_cdc;
static daisy::UsbHandle s_usb;
static void DefaultReboot(uint8_t mode, void*)
{
if (mode == kRebootBootloader)
daisy::System::ResetToBootloader(daisy::System::BootloaderMode::DAISY);
else
HAL_NVIC_SystemReset();
for (;;) {} /* unreachable; both paths reset */
}
Host::Host(Presets& presets, const char* id, const char* name,
const char* fw_version, const char* git_hash)
: presets_(&presets), id_(id), name_(name), fw_(fw_version), git_(git_hash)
{
/* Pre-BootLoad hook: snapshot factory state before the boot preset
* lands. The descriptor renders later, at the first Poll(), when
* everything main() declares is attached — call order never matters. */
presets_->SetPreBootHook(&PreBootTrampoline, this);
}
Host::Host(const char* id, const char* name,
const char* fw_version, const char* git_hash)
: id_(id), name_(name), fw_(fw_version), git_(git_hash) {}
void Host::PreBootTrampoline(void* self)
{
static_cast<Host*>(self)->CaptureFactory();
}
void Host::CaptureFactory()
{
if (!presets_ || factory_captured_) return;
factory_captured_ = true;
factory_schema_ = presets_->LiveSchemaHash();
factory_len_ = presets_->SerializeLive(s_factory, sizeof s_factory);
}
Host& Host::Product(const char* usb_product) { product_ = usb_product; return *this; }
Host& Host::BootSlot(uint8_t slot) { boot_slot_ = slot; return *this; }
Host& Host::Buffers(uint8_t* staging, uint8_t* snapshot, size_t cap)
{
staging_ = staging;
snapshot_ = snapshot;
buf_cap_ = cap;
return *this;
}
Host& Host::DescriptorBuffer(char* buf, size_t cap)
{
desc_buf_ = buf;
desc_cap_ = cap;
return *this;
}
Host& Host::DescriptorRefreshBuffer(char* buf, size_t cap)
{
if (!started_) { refresh_buf_ = buf; refresh_cap_ = cap; }
else configuration_ok_ = false;
return *this;
}
Host& Host::Transport(IHostTransport& transport) { transport_ = &transport; return *this; }
Host& Host::Uid(const uint8_t uid[12]) { uid_ = uid; return *this; }
Host& Host::OnReboot(void (*fn)(uint8_t, void*), void* ctx)
{
reboot_fn_ = fn;
reboot_ctx_ = ctx;
return *this;
}
Host& Host::OnDescribe(const Serializable& component,
bool (*fn)(ComponentWriter&, void*), void* ctx)
{
if (num_overrides_ < kMaxOverrides)
overrides_[num_overrides_++] = DescribeOverride{&component, fn, ctx};
return *this;
}
Host& Host::Descriptor(const void* json, uint32_t len)
{
manual_desc_ = json;
manual_desc_len_ = len;
return *this;
}
void Host::AddPage(const Page& p)
{
if (num_pages_ < kMaxPageRefs) pages_[num_pages_++] = &p;
}
Host& Host::Jacks(const Jack* jacks, uint8_t count)
{
num_jacks_ = 0u;
for (uint8_t i = 0; jacks && i < count; i++) AddJack(jacks[i]);
return *this;
}
Host& Host::Attach(const Manual& manual)
{
manual_ = &manual;
return *this;
}
Host& Host::Buttons(const VirtualButton* buttons, uint8_t count)
{
num_buttons_ = 0u;
for (uint8_t i = 0; buttons && i < count; i++) AddButton(buttons[i]);
return *this;
}
Host& Host::Extend(IHostlinkExtension& ext)
{
for (uint8_t i = 0u; i < num_extensions_; ++i)
if (extensions_[i] == &ext) return *this;
bool ok = !started_ && num_extensions_ < HostLink::kMaxExtensions
&& ext.FirstCmd() >= 0x50u && ext.FirstCmd() <= ext.LastCmd()
&& ext.LastCmd() <= 0x7Fu;
for (uint8_t i = 0u; ok && i < num_extensions_; ++i)
if (ext.FirstCmd() <= extensions_[i]->LastCmd()
&& ext.LastCmd() >= extensions_[i]->FirstCmd()) ok = false;
if (ok) extensions_[num_extensions_++] = &ext;
else configuration_ok_ = false;
return *this;
}
void Host::OnAttach(ControlLoop& loop) { loop_ = &loop; }
void Host::Start()
{
if (started_) return;
started_ = true;
CaptureFactory(); // fallback for apps that do not call BootLoad()
if (presets_ && !staging_)
{
std::memset(s_staging, 0, sizeof s_staging);
staging_ = s_staging;
snapshot_ = s_snapshot;
buf_cap_ = sizeof s_staging;
}
if (presets_ && !desc_buf_ && !manual_desc_)
{
std::memset(s_descriptor, 0, sizeof s_descriptor);
desc_buf_ = s_descriptor;
desc_cap_ = sizeof s_descriptor;
}
if (!refresh_buf_)
{
refresh_buf_ = s_descriptor_refresh;
refresh_cap_ = sizeof s_descriptor_refresh;
}
if (!transport_)
{
/* Alchemy Lab routes the panel USB-C to the Seed2 DFM's EXTERNAL
* USB pins on V2 (the port the bootloader serves DFU on); V1
* exposes the Seed's onboard port. The product string is what
* the OS and the browser's port picker display. */
#if defined(ALCHEMY_BOARD_V2)
s_cdc.Init(s_usb, daisy::UsbHandle::FS_EXTERNAL,
product_ ? product_ : name_);
#else
s_cdc.Init(s_usb, daisy::UsbHandle::FS_INTERNAL,
product_ ? product_ : name_);
#endif
transport_ = &s_cdc;
}
#if defined(ALCHEMY_BOARD_V2)
constexpr uint8_t kBoardNum = 2u;
#else
constexpr uint8_t kBoardNum = 1u;
#endif
const HostLink::Info info{id_, name_, fw_, git_, ALCHEMY_SDK_VERSION,
kBoardNum, boot_slot_};
if (presets_)
link_ = new (link_storage_) HostLink(
*transport_, *presets_, info, staging_, snapshot_, buf_cap_);
else
link_ = new (link_storage_) HostLink(*transport_, info);
if (manual_desc_)
{
link_->SetDescriptor(manual_desc_, manual_desc_len_);
}
else if (presets_)
{
desc_len_ = BuildDescriptor(desc_buf_, desc_cap_, nullptr);
link_->SetDescriptor(desc_buf_, desc_len_);
}
if (uid_)
{
link_->SetUid(uid_);
}
else
{
uint8_t uid[12];
const uint32_t w0 = HAL_GetUIDw0();
const uint32_t w1 = HAL_GetUIDw1();
const uint32_t w2 = HAL_GetUIDw2();
std::memcpy(uid + 0, &w0, 4);
std::memcpy(uid + 4, &w1, 4);
std::memcpy(uid + 8, &w2, 4);
link_->SetUid(uid);
}
link_->SetRebootHandler(reboot_fn_ ? reboot_fn_ : &DefaultReboot,
reboot_fn_ ? reboot_ctx_ : nullptr);
for (uint8_t i = 0u; i < num_extensions_; i++)
if (!link_->Extend(*extensions_[i])) configuration_ok_ = false;
}
uint32_t Host::BuildDescriptor(char* buf, size_t cap, bool* succeeded)
{
// Use live ControlLoop page references unless Pages() was explicit.
const Page* attached[kMaxPageRefs] = {};
PageSet set{pages_, num_pages_};
if (num_pages_ == 0u && loop_)
{
set.count = loop_->NumAttachedPages();
if (set.count > kMaxPageRefs) set.count = kMaxPageRefs;
for (uint8_t i = 0; i < set.count; ++i)
attached[i] = loop_->AttachedPage(i);
set.pages = attached;
}
const char* frags[HostLink::kMaxExtensions];
uint8_t num_frags = 0u;
for (uint8_t i = 0u; i < num_extensions_; ++i)
{
const char* frag = extensions_[i]->DescriptorRootJson();
if (frag) frags[num_frags++] = frag;
}
#if defined(ALCHEMY_BOARD_V2)
const char* board = "v2";
#else
const char* board = "v1";
#endif
return RenderDescriptor(buf, cap,
DescriptorBuilder::ModuleInfo{id_, name_, fw_, git_,
ALCHEMY_SDK_VERSION, board},
*presets_, set.count ? &set : nullptr, overrides_, num_overrides_,
num_buttons_ ? btn_ptrs_ : nullptr, num_buttons_,
num_frags ? frags : nullptr, num_frags,
num_jacks_ ? jack_ptrs_ : nullptr, num_jacks_, manual_,
s_factory, factory_len_, succeeded);
}
bool Host::RequestDescriptorRefresh()
{
if (!presets_ || manual_desc_) return false;
refresh_status_ = RefreshStatus::Pending;
return true;
}
// Subtraction avoids address overflow when checking caller-owned buffers.
static bool Overlap(const void* a, size_t an, const void* b, size_t bn)
{
if (!a || !b || !an || !bn) return false;
const auto x = reinterpret_cast<uintptr_t>(a);
const auto y = reinterpret_cast<uintptr_t>(b);
return x <= y ? y - x < an : x - y < bn;
}
void Host::RefreshDescriptor()
{
refresh_status_ = RefreshStatus::Failed;
if (!presets_ || manual_desc_ || !factory_captured_
|| factory_len_ != presets_->LiveSize()
|| factory_schema_ != presets_->LiveSchemaHash()) return;
const size_t cap = refresh_cap_ < desc_cap_ ? refresh_cap_ : desc_cap_;
if (!refresh_buf_ || !desc_buf_ || !cap
|| Overlap(refresh_buf_, cap, desc_buf_, desc_cap_)
|| Overlap(refresh_buf_, cap, staging_, buf_cap_)
|| Overlap(refresh_buf_, cap, snapshot_, buf_cap_)
|| Overlap(refresh_buf_, cap, s_factory, sizeof s_factory)) return;
bool succeeded = false;
const uint32_t len = BuildDescriptor(refresh_buf_, cap, &succeeded);
if (!succeeded) return; // never publish the error-descriptor fallback
if (len != desc_len_ || std::memcmp(desc_buf_, refresh_buf_, len) != 0)
{
std::memcpy(desc_buf_, refresh_buf_, len);
desc_len_ = len;
link_->SetDescriptor(desc_buf_, desc_len_);
}
refresh_status_ = RefreshStatus::Succeeded;
}
void Host::Poll(uint32_t t_ms)
{
if (!started_) Start();
if (refresh_status_ == RefreshStatus::Pending) RefreshDescriptor();
link_->Poll(t_ms);
}
} // namespace hostlink
} // namespace alchemy
b51161896This file is pinned to the selected release. Download the original to open it in your design tool.