From dabacde454b3438684e5629935f5276326cb1749 Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Wed, 3 Jun 2026 17:57:19 +0200 Subject: [PATCH 1/3] Restore original buspal reset path. --- device/src/shell/shell_commands.c | 2 +- right/src/slave_drivers/kboot_driver.c | 47 +++++++++++++++++++++----- right/src/slave_drivers/kboot_driver.h | 31 +++++++++++++---- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/device/src/shell/shell_commands.c b/device/src/shell/shell_commands.c index 658af924d..d7895604d 100644 --- a/device/src/shell/shell_commands.c +++ b/device/src/shell/shell_commands.c @@ -114,7 +114,7 @@ static int cmd_uhk_kboot_reset(const struct shell *shell, size_t argc, char *arg { KbootDriverState.i2cAddress = I2C_ADDRESS_RIGHT_MODULE_BOOTLOADER; KbootDriverState.phase = 0; - KbootDriverState.command = KbootCommand_Reset; + KbootDriverState.command = KbootCommand_ResetAndJump; shell_fprintf(shell, SHELL_NORMAL, "Kboot reset sent to right module (0x%02x)\n", I2C_ADDRESS_RIGHT_MODULE_BOOTLOADER); return 0; diff --git a/right/src/slave_drivers/kboot_driver.c b/right/src/slave_drivers/kboot_driver.c index 3dd6d8a53..fb21314b1 100644 --- a/right/src/slave_drivers/kboot_driver.c +++ b/right/src/slave_drivers/kboot_driver.c @@ -475,37 +475,68 @@ slave_result_t KbootSlaveDriver_Update(uint8_t kbootInstanceId) break; case KbootCommand_Reset: - if (handlePing(&res, KbootResetPhase_SendPing, KbootResetPhase_SendReset, abortReset)) { - if (KbootDriverState.phase == KbootResetPhase_SendReset) { + // Legacy buspal / UHK60 path: transmit the raw reset frame to the + // caller-supplied i2cAddress. The caller has already put the target + // module into its bootloader and pinged it, so no jump/ping is done + // here - doing so (and overwriting i2cAddress) would prevent the + // module from rebooting. Use KbootCommand_ResetAndJump for the + // self-contained variant. + switch (KbootDriverState.phase) { + case KbootResetPhase_SendReset: { + uint8_t len = buildResetCommand(); + res.status = i2cTx(txBuffer, len); + KbootDriverState.phase = KbootResetPhase_ReceiveResetAck; + break; + } + case KbootResetPhase_ReceiveResetAck: + res.status = i2cRx(KBOOT_PACKAGE_LENGTH_ACK); + KbootDriverState.phase = KbootResetPhase_ReceiveResetGenericResponse; + break; + case KbootResetPhase_ReceiveResetGenericResponse: + res.status = i2cRx(KBOOT_PACKAGE_LENGTH_GENERIC_RESPONSE); + KbootDriverState.phase = KbootResetPhase_CheckResetSendAck; + break; + case KbootResetPhase_CheckResetSendAck: + res.status = i2cTx(ackMessage, sizeof(ackMessage)); + KbootDriverState.command = KbootCommand_Idle; + break; + } + break; + + case KbootCommand_ResetAndJump: + // Self-contained reset (UHK80 native path / shell command): jump the + // right module into its bootloader, ping until it answers, then reset. + if (handlePing(&res, KbootResetAndJumpPhase_SendPing, KbootResetAndJumpPhase_SendReset, abortReset)) { + if (KbootDriverState.phase == KbootResetAndJumpPhase_SendReset) { LogU("Kboot: Sending reset\n"); buildResetCommand(); - startCmdTransaction(KbootCmdId_Reset, KbootResetPhase_Done, KBOOT_CMD_TIMEOUT_MS); + startCmdTransaction(KbootCmdId_Reset, KbootResetAndJumpPhase_Done, KBOOT_CMD_TIMEOUT_MS); } break; } switch (KbootDriverState.phase) { - case KbootResetPhase_JumpToBootloader: + case KbootResetAndJumpPhase_JumpToBootloader: LogU("Kboot: Jumping to bootloader for Reset\n"); Slaves[SlaveId_RightModule].isConnected = true; UhkModuleStates[UhkModuleDriverId_RightModule].phase = UhkModulePhase_JumpToBootloader; KbootDriverState.i2cAddress = I2C_ADDRESS_RIGHT_MODULE_BOOTLOADER; KbootDriverState.startTime = Timer_GetCurrentTime(); - KbootDriverState.phase = KbootResetPhase_WaitForBootloader; + KbootDriverState.phase = KbootResetAndJumpPhase_WaitForBootloader; pingAttemptCount = 0; break; - case KbootResetPhase_WaitForBootloader: + case KbootResetAndJumpPhase_WaitForBootloader: if (elapsedMs() < KBOOT_WAIT_AFTER_JUMP_MS) { break; } LogU("Kboot: Wait done (%dms), pinging at 0x%02x/0x%02x\n", KBOOT_WAIT_AFTER_JUMP_MS, I2C_ADDRESS_RIGHT_MODULE_BOOTLOADER, KBOOT_DEFAULT_I2C_ADDRESS); - KbootDriverState.phase = KbootResetPhase_SendPing; + KbootDriverState.phase = KbootResetAndJumpPhase_SendPing; break; - case KbootResetPhase_Done: + case KbootResetAndJumpPhase_Done: LogU("Kboot: Reset complete (%ums)\n", elapsedMs()); KbootDriverState.command = KbootCommand_Idle; break; diff --git a/right/src/slave_drivers/kboot_driver.h b/right/src/slave_drivers/kboot_driver.h index 88ba80261..2b0c4d835 100644 --- a/right/src/slave_drivers/kboot_driver.h +++ b/right/src/slave_drivers/kboot_driver.h @@ -26,6 +26,7 @@ KbootCommand_Ping, KbootCommand_Reset, KbootCommand_Flash, + KbootCommand_ResetAndJump, } kboot_command_t; typedef enum { @@ -35,17 +36,33 @@ KbootPhase_CheckPingResponseStatus, } kboot_ping_phase_t; + // Legacy reset (buspal / UHK60 path): the caller (agent) has already jumped + // the target module into its bootloader and pinged it, and supplies the + // target i2cAddress. We just transmit the raw reset frame to that address. typedef enum { - KbootResetPhase_JumpToBootloader, - KbootResetPhase_WaitForBootloader, - KbootResetPhase_SendPing, - KbootResetPhase_CheckPingStatus, - KbootResetPhase_ReceivePingResponse, - KbootResetPhase_CheckPingResponseStatus, KbootResetPhase_SendReset, - KbootResetPhase_Done, + KbootResetPhase_ReceiveResetAck, + KbootResetPhase_ReceiveResetGenericResponse, + KbootResetPhase_CheckResetSendAck, } kboot_reset_phase_t; + // Self-contained reset (UHK80 native path / shell command): jump the right + // module into its bootloader, ping until it answers, then reset it. The + // target i2cAddress is set internally, not by the caller. + // SendPing..CheckPingResponseStatus are not handled by name in the switch - + // handlePing() walks them by offset from SendPing, so they must stay + // contiguous and in this order. + typedef enum { + KbootResetAndJumpPhase_JumpToBootloader, + KbootResetAndJumpPhase_WaitForBootloader, + KbootResetAndJumpPhase_SendPing, + KbootResetAndJumpPhase_CheckPingStatus, + KbootResetAndJumpPhase_ReceivePingResponse, + KbootResetAndJumpPhase_CheckPingResponseStatus, + KbootResetAndJumpPhase_SendReset, + KbootResetAndJumpPhase_Done, + } kboot_reset_and_jump_phase_t; + // Shared command transaction phases (reused by Flash and Reset). // Values 240-249 so they don't collide with command-specific phases. typedef enum { From a2d0811760cab70c53530d5bf5baada17e95d1d2 Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Thu, 4 Jun 2026 12:59:59 +0200 Subject: [PATCH 2/3] Fix kboot w.r.t. halves and uhk60. --- device/src/shell/shell_commands.c | 6 ++- right/src/slave_drivers/kboot_driver.c | 38 ++++++++++--------- right/src/slave_drivers/kboot_driver.h | 5 +++ right/src/slave_scheduler.c | 6 ++- .../usb_commands/usb_command_flash_module.c | 3 ++ 5 files changed, 39 insertions(+), 19 deletions(-) diff --git a/device/src/shell/shell_commands.c b/device/src/shell/shell_commands.c index d7895604d..feb221eaf 100644 --- a/device/src/shell/shell_commands.c +++ b/device/src/shell/shell_commands.c @@ -23,6 +23,7 @@ #include "wormhole.h" #include "stubs.h" #include "slave_drivers/kboot_driver.h" +#include "slot.h" #include "i2c_addresses.h" #include "test_suite/test_suite.h" #include "jitter_test.h" @@ -112,7 +113,8 @@ static int cmd_uhk_testled(const struct shell *shell, size_t argc, char *argv[]) static int cmd_uhk_kboot_reset(const struct shell *shell, size_t argc, char *argv[]) { - KbootDriverState.i2cAddress = I2C_ADDRESS_RIGHT_MODULE_BOOTLOADER; + KbootDriverState.slotId = SlotId_RightModule; + KbootDriverState.moduleBootloaderAddress = I2C_ADDRESS_RIGHT_MODULE_BOOTLOADER; KbootDriverState.phase = 0; KbootDriverState.command = KbootCommand_ResetAndJump; shell_fprintf(shell, SHELL_NORMAL, "Kboot reset sent to right module (0x%02x)\n", @@ -122,6 +124,8 @@ static int cmd_uhk_kboot_reset(const struct shell *shell, size_t argc, char *arg static int cmd_uhk_kboot_flash(const struct shell *shell, size_t argc, char *argv[]) { + KbootDriverState.slotId = SlotId_RightModule; + KbootDriverState.moduleBootloaderAddress = I2C_ADDRESS_RIGHT_MODULE_BOOTLOADER; KbootDriverState.phase = 0; KbootDriverState.command = KbootCommand_Flash; shell_fprintf(shell, SHELL_NORMAL, "Kboot flash sequence started for right module\n"); diff --git a/right/src/slave_drivers/kboot_driver.c b/right/src/slave_drivers/kboot_driver.c index fb21314b1..c3f5dc1a4 100644 --- a/right/src/slave_drivers/kboot_driver.c +++ b/right/src/slave_drivers/kboot_driver.c @@ -169,9 +169,9 @@ static bool verifyRxCrc(uint8_t totalLen) static void togglePingAddress(void) { - KbootDriverState.i2cAddress = KbootDriverState.i2cAddress == I2C_ADDRESS_RIGHT_MODULE_BOOTLOADER + KbootDriverState.i2cAddress = KbootDriverState.i2cAddress == KbootDriverState.moduleBootloaderAddress ? KBOOT_DEFAULT_I2C_ADDRESS - : I2C_ADDRESS_RIGHT_MODULE_BOOTLOADER; + : KbootDriverState.moduleBootloaderAddress; } typedef void (*abort_fn_t)(const char *); @@ -504,8 +504,9 @@ slave_result_t KbootSlaveDriver_Update(uint8_t kbootInstanceId) break; case KbootCommand_ResetAndJump: - // Self-contained reset (UHK80 native path / shell command): jump the - // right module into its bootloader, ping until it answers, then reset. + // Testing code: just jump into bootloader, talk to it, and reset back. + // + // TODO: remove it once we are sure everything works and we don't need this if (handlePing(&res, KbootResetAndJumpPhase_SendPing, KbootResetAndJumpPhase_SendReset, abortReset)) { if (KbootDriverState.phase == KbootResetAndJumpPhase_SendReset) { LogU("Kboot: Sending reset\n"); @@ -516,15 +517,17 @@ slave_result_t KbootSlaveDriver_Update(uint8_t kbootInstanceId) } switch (KbootDriverState.phase) { - case KbootResetAndJumpPhase_JumpToBootloader: - LogU("Kboot: Jumping to bootloader for Reset\n"); - Slaves[SlaveId_RightModule].isConnected = true; - UhkModuleStates[UhkModuleDriverId_RightModule].phase = UhkModulePhase_JumpToBootloader; - KbootDriverState.i2cAddress = I2C_ADDRESS_RIGHT_MODULE_BOOTLOADER; + case KbootResetAndJumpPhase_JumpToBootloader: { + uint8_t driverId = UhkModuleSlaveDriver_SlotIdToDriverId(KbootDriverState.slotId); + LogU("Kboot: Jumping to bootloader for Reset (slot %u)\n", KbootDriverState.slotId); + Slaves[driverId].isConnected = true; + UhkModuleStates[driverId].phase = UhkModulePhase_JumpToBootloader; + KbootDriverState.i2cAddress = KbootDriverState.moduleBootloaderAddress; KbootDriverState.startTime = Timer_GetCurrentTime(); KbootDriverState.phase = KbootResetAndJumpPhase_WaitForBootloader; pingAttemptCount = 0; break; + } case KbootResetAndJumpPhase_WaitForBootloader: if (elapsedMs() < KBOOT_WAIT_AFTER_JUMP_MS) { @@ -532,7 +535,7 @@ slave_result_t KbootSlaveDriver_Update(uint8_t kbootInstanceId) } LogU("Kboot: Wait done (%dms), pinging at 0x%02x/0x%02x\n", KBOOT_WAIT_AFTER_JUMP_MS, - I2C_ADDRESS_RIGHT_MODULE_BOOTLOADER, KBOOT_DEFAULT_I2C_ADDRESS); + KbootDriverState.moduleBootloaderAddress, KBOOT_DEFAULT_I2C_ADDRESS); KbootDriverState.phase = KbootResetAndJumpPhase_SendPing; break; @@ -574,12 +577,13 @@ slave_result_t KbootSlaveDriver_Update(uint8_t kbootInstanceId) } LogU("Kboot: Firmware ready, %u bytes. Jumping to bootloader\n", KbootDriverState.firmwareSize); - // Force RightModule connected so the scheduler doesn't call - // UhkModuleSlaveDriver_Init (which would reset the phase we - // are about to set back to RequestSync). - Slaves[SlaveId_RightModule].isConnected = true; - UhkModuleStates[UhkModuleDriverId_RightModule].phase = UhkModulePhase_JumpToBootloader; - KbootDriverState.i2cAddress = I2C_ADDRESS_RIGHT_MODULE_BOOTLOADER; + // Force the target module connected so the scheduler doesn't + // call UhkModuleSlaveDriver_Init (which would reset the phase + // we are about to set back to RequestSync). + uint8_t driverId = UhkModuleSlaveDriver_SlotIdToDriverId(KbootDriverState.slotId); + Slaves[driverId].isConnected = true; + UhkModuleStates[driverId].phase = UhkModulePhase_JumpToBootloader; + KbootDriverState.i2cAddress = KbootDriverState.moduleBootloaderAddress; KbootDriverState.startTime = Timer_GetCurrentTime(); KbootDriverState.phase = KbootFlashPhase_WaitForBootloader; pingAttemptCount = 0; @@ -592,7 +596,7 @@ slave_result_t KbootSlaveDriver_Update(uint8_t kbootInstanceId) } LogU("Kboot: Wait done (%dms), pinging at 0x%02x/0x%02x\n", KBOOT_WAIT_AFTER_JUMP_MS, - I2C_ADDRESS_RIGHT_MODULE_BOOTLOADER, KBOOT_DEFAULT_I2C_ADDRESS); + KbootDriverState.moduleBootloaderAddress, KBOOT_DEFAULT_I2C_ADDRESS); KbootDriverState.phase = KbootFlashPhase_SendPing; break; diff --git a/right/src/slave_drivers/kboot_driver.h b/right/src/slave_drivers/kboot_driver.h index 2b0c4d835..823dc56e9 100644 --- a/right/src/slave_drivers/kboot_driver.h +++ b/right/src/slave_drivers/kboot_driver.h @@ -106,7 +106,12 @@ typedef struct { kboot_command_t command; + // i2c address we are actually talking to. Alternates between default and moduleBootloaderAddress. uint8_t i2cAddress; + // slotId to target - specified by Agent + uint8_t slotId; + // expected module bootloader i2c address - specified by Agent + uint8_t moduleBootloaderAddress; uint8_t phase; uint32_t status; uint32_t startTime; diff --git a/right/src/slave_scheduler.c b/right/src/slave_scheduler.c index 724509d4c..cb1f9353e 100644 --- a/right/src/slave_scheduler.c +++ b/right/src/slave_scheduler.c @@ -81,7 +81,11 @@ static uint8_t getNextSlaveId(uint8_t slaveId) } return slaveId; #elif DEVICE_IS_UHK80_LEFT - return slaveId == SlaveId_LeftModule ? SlaveId_ModuleLeftLedDriver : SlaveId_LeftModule; + switch (slaveId) { + case SlaveId_LeftModule: return SlaveId_ModuleLeftLedDriver; + case SlaveId_ModuleLeftLedDriver: return SlaveId_KbootDriver; + default: return SlaveId_LeftModule; + } #elif DEVICE_IS_UHK80_RIGHT switch (slaveId) { case SlaveId_RightModule: return SlaveId_RightTouchpad; diff --git a/right/src/usb_commands/usb_command_flash_module.c b/right/src/usb_commands/usb_command_flash_module.c index 39fb373b4..d11fc8d3d 100644 --- a/right/src/usb_commands/usb_command_flash_module.c +++ b/right/src/usb_commands/usb_command_flash_module.c @@ -12,6 +12,7 @@ typedef enum { void UsbCommand_FlashModule(const uint8_t *GenericHidOutBuffer, uint8_t *GenericHidInBuffer) { uint8_t slotId = GetUsbRxBufferUint8(1); + uint8_t bootloaderI2cAddress = GetUsbRxBufferUint8(2); if (!IS_VALID_MODULE_SLOT(slotId)) { SetUsbTxBufferUint8(0, UsbStatusCode_FlashModule_InvalidSlotId); @@ -26,6 +27,8 @@ void UsbCommand_FlashModule(const uint8_t *GenericHidOutBuffer, uint8_t *Generic ModuleFlashBusy = true; ModuleFlashErrorCode = 0; ModuleFlashState = ModuleFlashState_Erasing; + KbootDriverState.slotId = slotId; + KbootDriverState.moduleBootloaderAddress = bootloaderI2cAddress; KbootDriverState.phase = 0; KbootDriverState.command = KbootCommand_Flash; } From 5e6ff74f0bd10cfed378095b1912fd88799dfd2b Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Thu, 4 Jun 2026 17:57:10 +0200 Subject: [PATCH 3/3] Add module connect / disconnect logs. --- right/src/slave_drivers/kboot_driver.c | 2 ++ right/src/slave_drivers/uhk_module_driver.c | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/right/src/slave_drivers/kboot_driver.c b/right/src/slave_drivers/kboot_driver.c index c3f5dc1a4..61ae5a117 100644 --- a/right/src/slave_drivers/kboot_driver.c +++ b/right/src/slave_drivers/kboot_driver.c @@ -446,6 +446,7 @@ slave_result_t KbootSlaveDriver_Update(uint8_t kbootInstanceId) break; case KbootCommand_Ping: + LogU("Kboot: Ping command, target addr=0x%02x, phase=%d\n", KbootDriverState.i2cAddress, KbootDriverState.phase); switch (KbootDriverState.phase) { case KbootPhase_SendPing: res.status = i2cTx(pingCommand, sizeof(pingCommand)); @@ -481,6 +482,7 @@ slave_result_t KbootSlaveDriver_Update(uint8_t kbootInstanceId) // here - doing so (and overwriting i2cAddress) would prevent the // module from rebooting. Use KbootCommand_ResetAndJump for the // self-contained variant. + LogU("Kboot: Reset command, target addr=0x%02x, phase=%d\n", KbootDriverState.i2cAddress, KbootDriverState.phase); switch (KbootDriverState.phase) { case KbootResetPhase_SendReset: { uint8_t len = buildResetCommand(); diff --git a/right/src/slave_drivers/uhk_module_driver.c b/right/src/slave_drivers/uhk_module_driver.c index ecdcb3924..380f32cb6 100644 --- a/right/src/slave_drivers/uhk_module_driver.c +++ b/right/src/slave_drivers/uhk_module_driver.c @@ -462,6 +462,14 @@ slave_result_t UhkModuleSlaveDriver_Update(uint8_t uhkModuleDriverId) StateSync_UpdateProperty(StateSyncPropertyId_ModuleStateLeftModule, NULL); } #endif + LogU("Module %d initialized: protocol version %d.%d.%d, firmware version %d.%d.%d, git tag %s, git repo %s, firmware checksum %s\n", + uhkModuleDriverId, + uhkModuleState->moduleProtocolVersion.major, uhkModuleState->moduleProtocolVersion.minor, uhkModuleState->moduleProtocolVersion.patch, + uhkModuleState->firmwareVersion.major, uhkModuleState->firmwareVersion.minor, uhkModuleState->firmwareVersion.patch, + uhkModuleState->gitTag, + uhkModuleState->gitRepo, + uhkModuleState->firmwareChecksum + ); break; }