AnasSarkiz/usb-c-pd-brushed-motor
USB-C PD-powered motor controller with protected input, selectable 5/9/12 V buck supply, MCU qualification, PWM H-bridge drive, overvoltage sensing, regenerative energy-dump resistors, and safety interlocks.
- Version
- 0.0.1-a48-cloud-pipeline9-unrouted-c41d79a-305b1e7a
- License
- unset
- Stars
- 0
firmware/stusb4500_startup_test.c
#include "stusb4500_startup.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
static unsigned checks;
#define CHECK(condition) do { ++checks; assert(condition); } while (0)
struct mock_bus {
uint8_t registers[256], initial_status[10];
uint32_t now_us, expected_deadline_us;
unsigned calls, fail_call, late_call, corrupt_call, corrupt_byte;
unsigned detach_call, event_call, commands;
uint8_t event_alert, event_protocol;
bool apply_failed_write, apply_failed_read;
};
struct startup_test {
struct mock_bus mock;
struct stusb_startup_state state;
struct stusb_startup startup;
struct stusb_bus bus;
};
static uint32_t now_us(void *context) {
return ((struct mock_bus *)context)->now_us;
}
static void transfer_started(struct mock_bus *mock, uint32_t deadline_us) {
CHECK(deadline_us == mock->expected_deadline_us);
++mock->calls;
mock->now_us += 100;
if (mock->calls == mock->late_call) mock->now_us = deadline_us + 1;
if (mock->calls == mock->detach_call) mock->registers[0x0e] = 0;
if (mock->calls == mock->event_call) {
mock->registers[0x0b] |= mock->event_alert;
mock->registers[0x16] |= mock->event_protocol;
}
}
static void clear_status(struct mock_bus *mock, uint8_t address) {
uint8_t summary_bit = 0;
switch (address) {
case 0x0d: summary_bit = 0x40; break;
case 0x0f: summary_bit = 0x20; break;
case 0x12: summary_bit = 0x10; break;
case 0x14: mock->registers[address] &= 0xf0u; return;
case 0x16: summary_bit = 0x02; break;
default: return;
}
mock->registers[address] = 0;
mock->registers[0x0b] &= (uint8_t)~summary_bit;
}
static bool read_register(void *context, const struct stusb_read *transfer) {
struct mock_bus *mock = context;
transfer_started(mock, transfer->deadline_us);
const bool failed = mock->calls == mock->fail_call;
if (failed && !mock->apply_failed_read) return false;
memcpy(transfer->bytes, &mock->registers[transfer->register_address], transfer->byte_count);
for (unsigned i=0; i<transfer->byte_count; ++i)
clear_status(mock, transfer->register_address + i);
if (mock->calls == mock->corrupt_call)
transfer->bytes[mock->corrupt_byte] ^= transfer->register_address == 0x0c ? 2u : 1u;
return !failed;
}
static bool write_register(void *context, const struct stusb_write *transfer) {
struct mock_bus *mock = context;
transfer_started(mock, transfer->deadline_us);
const bool failed = mock->calls == mock->fail_call;
if (failed && !mock->apply_failed_write) return false;
if (transfer->register_address == 0x1a) {
CHECK(transfer->byte_count == 1 && transfer->bytes[0] == 0x26);
CHECK((mock->registers[0x70] & 7u) == 1);
CHECK(mock->registers[0x85] == 0x2c && mock->registers[0x86] == 0x91);
CHECK(mock->registers[0x87] == 1 && mock->registers[0x88] == 0);
CHECK(mock->registers[0x51] == 0x0d && mock->registers[0x52] == 0);
CHECK((mock->registers[0x0c] & 0x72u) == 0);
CHECK(mock->registers[0x16] == 0);
++mock->commands;
}
memcpy(&mock->registers[transfer->register_address], transfer->bytes, transfer->byte_count);
return !failed;
}
static void initialize(struct startup_test *test) {
*test = (struct startup_test){0};
test->mock.now_us = 100;
test->mock.expected_deadline_us = 4100;
test->mock.registers[0x0b] = 0x72;
test->mock.registers[0x0c] = 0xfb;
test->mock.registers[0x0d] = 1;
test->mock.registers[0x0e] = 1;
test->mock.registers[0x0f] = 0x0e;
test->mock.registers[0x10] = 0x0a;
test->mock.registers[0x11] = 0x13;
test->mock.registers[0x12] = 0x10;
test->mock.registers[0x13] = 0x40;
test->mock.registers[0x14] = 8;
test->mock.registers[0x15] = 2;
test->mock.registers[0x16] = 5; /* Old RX/reset cannot qualify the new request. */
test->mock.registers[0x70] = 0xa3;
memset(&test->mock.registers[0x89], 0xa5, 8);
memcpy(test->mock.initial_status, &test->mock.registers[0x0d], 10);
test->startup = (struct stusb_startup){.state=&test->state,
.acquisition_id=8, .started_us=100, .power_path_inhibited=true,
.bridge_inhibited=true, .provenance_invalidated=true};
test->bus = (struct stusb_bus){.context=&test->mock, .read=read_register,
.write=write_register, .now_us=now_us};
}
static enum stusb_startup_result execute(struct startup_test *test) {
return stusb_startup_acquire(&test->bus, &test->startup);
}
static void check_fault(struct startup_test *test) {
CHECK(test->state.faulted && test->state.completed_acquisition_id == 0);
const unsigned calls = test->mock.calls;
++test->startup.acquisition_id;
CHECK(execute(test) == STUSB_STARTUP_LATCHED);
CHECK(test->mock.calls == calls);
}
static void check_success(struct startup_test *test) {
CHECK(!test->state.faulted && test->state.completed_acquisition_id == 8);
CHECK(test->state.last_acquisition_id == 8 && test->state.snapshot_valid);
CHECK(test->state.command_started_us == test->startup.started_us + 1600);
CHECK(test->mock.calls == 17 && test->mock.commands == 1);
CHECK(test->mock.registers[0x70] == 0xa1);
for (unsigned i=0; i<10; ++i) CHECK(test->state.old_status[i] == test->mock.initial_status[i]);
for (unsigned i=0; i<8; ++i) CHECK(test->mock.registers[0x89+i] == 0xa5);
}
int main(void) {
struct startup_test test;
initialize(&test); CHECK(execute(&test) == STUSB_STARTUP_SENT); check_success(&test);
CHECK(test.mock.registers[0x0c] == 0x89);
/* Every possible initial mask: only defined bits change, reserved preserved. */
for (unsigned mask=0; mask<256; ++mask) {
initialize(&test); test.mock.registers[0x0c] = mask;
CHECK(execute(&test) == STUSB_STARTUP_SENT);
check_success(&test);
CHECK(test.mock.registers[0x0c] == (mask & 0x8du));
}
for (unsigned call=1; call<=17; ++call) {
for (unsigned apply=0; apply<2; ++apply) {
initialize(&test); test.mock.fail_call=call;
test.mock.apply_failed_write=apply; test.mock.apply_failed_read=apply;
CHECK(execute(&test) == STUSB_STARTUP_IO_ERROR);
CHECK(test.mock.calls == call && test.state.last_acquisition_id == 8);
CHECK(test.state.snapshot_valid == (call > 4));
CHECK(test.mock.commands == (call == 17 && apply ? 1u : 0u));
check_fault(&test);
}
initialize(&test); test.mock.late_call=call;
CHECK(execute(&test) == STUSB_STARTUP_LATE);
CHECK(test.mock.calls == call && test.state.snapshot_valid == (call > 4));
CHECK(test.mock.commands == (call == 17 ? 1u : 0u));
check_fault(&test);
}
const unsigned readback_calls[] = {3, 7, 9, 11, 13};
for (unsigned i=0; i<5; ++i) {
const unsigned bytes = readback_calls[i] == 9 ? 4 : readback_calls[i] == 11 ? 2 : 1;
for (unsigned byte=0; byte<bytes; ++byte) {
initialize(&test); test.mock.corrupt_call=readback_calls[i]; test.mock.corrupt_byte=byte;
CHECK(execute(&test) == STUSB_STARTUP_READBACK_MISMATCH);
CHECK(test.mock.commands == 0); check_fault(&test);
}
}
initialize(&test); test.mock.detach_call=4;
CHECK(execute(&test) == STUSB_STARTUP_DETACHED); check_fault(&test);
initialize(&test); test.mock.detach_call=14;
CHECK(execute(&test) == STUSB_STARTUP_DETACHED); check_fault(&test);
initialize(&test); test.mock.registers[0x0e] |= 8;
CHECK(execute(&test) == STUSB_STARTUP_DETACHED); check_fault(&test);
const uint8_t hardware_faults[] = {0x10, 0x80, 0x90};
for (unsigned i=0; i<3; ++i) {
initialize(&test); test.mock.registers[0x13] |= hardware_faults[i];
CHECK(execute(&test) == STUSB_STARTUP_PENDING_FAULT);
CHECK(test.state.snapshot_valid && (test.state.old_status[6] & hardware_faults[i]));
CHECK(test.mock.commands == 0); check_fault(&test);
}
/* A transition reasserted after the bulk drain invalidates acquisition. */
for (unsigned bit=0; bit<8; ++bit) {
if (bit == 1) continue;
initialize(&test); test.mock.event_call=15; test.mock.event_alert=1u<<bit;
CHECK(execute(&test) == STUSB_STARTUP_PENDING_FAULT);
CHECK(test.mock.commands == 0); check_fault(&test);
}
const uint8_t protocol_faults[] = {1, 2, 0x10, 0x80};
for (unsigned i=0; i<4; ++i) {
initialize(&test); test.mock.event_call=16; test.mock.event_protocol=protocol_faults[i];
CHECK(execute(&test) == STUSB_STARTUP_PENDING_FAULT); check_fault(&test);
}
initialize(&test); test.mock.event_call=15; test.mock.event_alert=2; test.mock.event_protocol=4;
CHECK(execute(&test) == STUSB_STARTUP_SENT); check_success(&test);
/* Drained source data are not captured or promoted by initialization. */
CHECK(test.mock.registers[0x16] == 0 && test.mock.registers[0x0b] == 0);
for (unsigned unsafe=0; unsafe<3; ++unsafe) {
initialize(&test);
if (unsafe == 0) test.startup.power_path_inhibited=false;
if (unsafe == 1) test.startup.bridge_inhibited=false;
if (unsafe == 2) test.startup.provenance_invalidated=false;
CHECK(execute(&test) == STUSB_STARTUP_UNSAFE_PRECONDITION);
CHECK(test.mock.calls == 0 && test.state.last_acquisition_id == 0); check_fault(&test);
}
const uint32_t invalid_tokens[] = {0, 7, 8};
for (unsigned i=0; i<3; ++i) {
initialize(&test); test.state.last_acquisition_id=8;
test.startup.acquisition_id=invalid_tokens[i];
CHECK(execute(&test) == STUSB_STARTUP_REUSED_TOKEN);
CHECK(test.mock.calls == 0); check_fault(&test);
}
initialize(&test); test.state.last_acquisition_id=UINT32_MAX;
CHECK(execute(&test) == STUSB_STARTUP_REUSED_TOKEN); check_fault(&test);
initialize(&test); test.mock.now_us=4101;
CHECK(execute(&test) == STUSB_STARTUP_LATE && test.mock.calls == 0); check_fault(&test);
initialize(&test); test.startup.started_us=101;
CHECK(execute(&test) == STUSB_STARTUP_LATE && test.mock.calls == 0); check_fault(&test);
initialize(&test); test.mock.now_us=UINT32_MAX-1000;
test.startup.started_us=test.mock.now_us;
test.mock.expected_deadline_us=test.mock.now_us+4000;
CHECK(execute(&test) == STUSB_STARTUP_SENT); check_success(&test);
initialize(&test); test.bus.read=NULL;
CHECK(execute(&test) == STUSB_STARTUP_INVALID_ARGUMENT); check_fault(&test);
initialize(&test); test.bus.write=NULL;
CHECK(execute(&test) == STUSB_STARTUP_INVALID_ARGUMENT); check_fault(&test);
initialize(&test); test.bus.now_us=NULL;
CHECK(execute(&test) == STUSB_STARTUP_INVALID_ARGUMENT); check_fault(&test);
initialize(&test); test.startup.state=NULL;
CHECK(execute(&test) == STUSB_STARTUP_INVALID_ARGUMENT && test.mock.calls == 0);
CHECK(stusb_startup_acquire(NULL, NULL) == STUSB_STARTUP_INVALID_ARGUMENT);
printf("STUSB4500 startup host tests passed (%u assertions)\n", checks);
return 0;
}