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_request_test.c
#include "stusb4500_request.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];
uint32_t now_us, transfer_time_us, expected_deadline_us;
unsigned calls, fail_call, corrupt_call, corrupt_byte, detach_call, commands;
bool apply_failed_write;
};
struct test_request {
struct mock_bus mock;
struct pd_plan plan;
struct stusb_request_state state;
struct stusb_request request;
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 += mock->transfer_time_us;
if (mock->calls == mock->detach_call) mock->registers[0x0e] = 0;
}
static bool read_register(void *context, const struct stusb_read *transfer) {
struct mock_bus *mock = context;
transfer_started(mock, transfer->deadline_us);
if (mock->calls == mock->fail_call) return false;
memcpy(transfer->bytes, &mock->registers[transfer->register_address], transfer->byte_count);
if (mock->calls == mock->corrupt_call) transfer->bytes[mock->corrupt_byte] ^= 1u;
/* UM2650 rev3 1.11: reading the alert summary does not acknowledge it.
* Only its associated read-clear status register clears that event. */
if (transfer->register_address == 0x16) {
mock->registers[0x16] = 0;
mock->registers[0x0b] &= (uint8_t)~2u;
}
return true;
}
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] == 0xa2);
CHECK(mock->registers[0x51] == 0x0d && mock->registers[0x52] == 0);
mock->commands++;
}
memcpy(&mock->registers[transfer->register_address], transfer->bytes, transfer->byte_count);
return !failed;
}
static void initialize(struct test_request *test, uint8_t selector) {
*test = (struct test_request){0};
test->mock.now_us = 100;
test->mock.transfer_time_us = 100;
test->mock.expected_deadline_us = 4100;
test->mock.registers[0x0e] = 1;
test->mock.registers[0x70] = 0xa3; /* Reserved upper bits are not count bits. */
test->mock.registers[0x0b] = 2; /* Old protocol alert. */
test->mock.registers[0x16] = 4; /* Old RX must be cleared before SEND. */
memset(&test->mock.registers[0x8d], 0xa5, 4); /* Old PDO3 must stay inactive. */
const uint32_t source_pdos[] = {(100u<<10)|300u, (300u<<10)|300u, (400u<<10)|300u};
test->plan = pd_make_plan(selector, &(struct pd_capabilities){.source_pdos=source_pdos, .count=3, .source_generation=77});
CHECK(test->plan.valid);
test->request = (struct stusb_request){
.state=&test->state, .plan=&test->plan, .request_id=8, .source_generation=77,
.started_us=100, .selector_bits=selector, .power_path_inhibited=true,
.bridge_inhibited=true, .feedback_settled=true
};
test->bus = (struct stusb_bus){.context=&test->mock, .read=read_register,
.write=write_register, .now_us=now_us};
}
static enum stusb_request_result execute(struct test_request *test) {
return stusb_request_contract(&test->bus, &test->request);
}
static uint32_t pdo_at(const struct mock_bus *mock, uint8_t address) {
const uint8_t *bytes = &mock->registers[address];
return (uint32_t)bytes[0] | (uint32_t)bytes[1]<<8 |
(uint32_t)bytes[2]<<16 | (uint32_t)bytes[3]<<24;
}
static void check_fault(struct test_request *test) {
CHECK(test->state.faulted && test->state.completed_request_id == 0);
const unsigned calls = test->mock.calls;
test->request.request_id++;
CHECK(execute(test) == STUSB_REQUEST_LATCHED);
CHECK(test->mock.calls == calls); /* No implicit bus retry, even with a new token. */
}
int main(void) {
struct test_request test;
for (uint8_t selector=0; selector<3; ++selector) {
initialize(&test, selector);
CHECK(execute(&test) == STUSB_REQUEST_SENT);
CHECK(test.mock.calls == 14 && test.mock.commands == 1);
CHECK(test.state.last_request_id == 8 && test.state.completed_request_id == 8);
CHECK(!test.state.faulted && test.state.command_started_us == 1400);
CHECK(pdo_at(&test.mock, 0x85) == ((100u<<10)|300u));
CHECK(pdo_at(&test.mock, 0x89) == test.plan.sink_pdo);
CHECK(pdo_at(&test.mock, 0x8d) == 0xa5a5a5a5u);
CHECK(test.mock.registers[0x70] == 0xa2);
CHECK(test.mock.registers[0x0b] == 0 && test.mock.registers[0x16] == 0);
CHECK(execute(&test) == STUSB_REQUEST_REUSED_TOKEN);
CHECK(test.mock.commands == 1); check_fault(&test);
}
for (uint8_t selector=0; selector<3; ++selector) {
initialize(&test, selector);
test.plan.source_pdo=(test.plan.source_pdo & ~1023u)|500u;
CHECK(execute(&test) == STUSB_REQUEST_SENT);
CHECK((pdo_at(&test.mock, 0x89) & 1023u) == 300u);
CHECK(test.state.completed_request_id == 8 && !test.state.faulted);
}
for (unsigned failed=1; failed<=14; ++failed) {
initialize(&test, 1); test.mock.fail_call=failed;
test.state.completed_request_id=3; /* A failure cannot preserve old success. */
CHECK(execute(&test) == STUSB_REQUEST_IO_ERROR);
CHECK(test.mock.commands == 0 && test.mock.calls == failed); check_fault(&test);
}
/* SEND might reach the controller even though its bus acknowledgement fails. */
initialize(&test, 2); test.mock.fail_call=14; test.mock.apply_failed_write=true;
CHECK(execute(&test) == STUSB_REQUEST_IO_ERROR);
CHECK(test.mock.commands == 1); check_fault(&test);
const unsigned ambiguous_writes[] = {3,5,7,9};
for (unsigned i=0; i<4; ++i) {
initialize(&test, 0); test.mock.fail_call=ambiguous_writes[i];
test.mock.apply_failed_write=true;
CHECK(execute(&test) == STUSB_REQUEST_IO_ERROR);
CHECK(test.mock.commands == 0); check_fault(&test);
}
for (unsigned byte=0; byte<8; ++byte) {
initialize(&test, 0); test.mock.corrupt_call=6; test.mock.corrupt_byte=byte;
CHECK(execute(&test) == STUSB_REQUEST_READBACK_MISMATCH);
CHECK(test.mock.commands == 0); check_fault(&test);
}
const unsigned corrupted_reads[] = {4,8,10};
for (unsigned i=0; i<3; ++i) {
initialize(&test, 0); test.mock.corrupt_call=corrupted_reads[i];
CHECK(execute(&test) == STUSB_REQUEST_READBACK_MISMATCH);
CHECK(test.mock.commands == 0); check_fault(&test);
}
initialize(&test, 0); test.mock.corrupt_call=10; test.mock.corrupt_byte=1;
CHECK(execute(&test) == STUSB_REQUEST_READBACK_MISMATCH); check_fault(&test);
const unsigned detached_at[] = {1,11};
for (unsigned i=0; i<2; ++i) {
initialize(&test, 2); test.mock.detach_call=detached_at[i];
CHECK(execute(&test) == STUSB_REQUEST_DETACHED);
CHECK(test.mock.commands == 0); check_fault(&test);
}
initialize(&test, 0); test.mock.registers[0x0e] = 9; /* Must be a sink. */
CHECK(execute(&test) == STUSB_REQUEST_DETACHED); check_fault(&test);
for (unsigned bit=3; bit<8; ++bit) {
initialize(&test, 1); test.mock.registers[0x0b] |= 1u<<bit;
CHECK(execute(&test) == STUSB_REQUEST_PENDING_FAULT);
CHECK(test.mock.registers[0x0b] & (1u<<bit)); /* Summary read cannot clear this event. */
CHECK(test.mock.commands == 0); check_fault(&test);
}
for (unsigned bit=0; bit<8; ++bit) {
if (!(0x83u & (1u<<bit))) continue;
initialize(&test, 0); test.mock.registers[0x16] |= 1u<<bit;
CHECK(execute(&test) == STUSB_REQUEST_PENDING_FAULT); check_fault(&test);
}
for (unsigned operation=1; operation<=14; ++operation) {
initialize(&test, 2); test.mock.transfer_time_us=4000u/operation+1u;
CHECK(execute(&test) == STUSB_REQUEST_LATE); check_fault(&test);
}
initialize(&test, 0); test.mock.now_us=4101;
CHECK(execute(&test) == STUSB_REQUEST_LATE && test.mock.calls == 0); check_fault(&test);
initialize(&test, 0); test.mock.now_us=99;
CHECK(execute(&test) == STUSB_REQUEST_LATE && test.mock.calls == 0); check_fault(&test);
initialize(&test, 1); test.request.started_us=UINT32_MAX-499u;
test.mock.now_us=test.request.started_us; test.mock.expected_deadline_us=3500;
CHECK(execute(&test) == STUSB_REQUEST_SENT); CHECK(test.mock.now_us == 900);
for (unsigned invalid=0; invalid<9; ++invalid) {
initialize(&test, 1);
switch (invalid) {
case 0:test.request.power_path_inhibited=false;break;
case 1:test.request.bridge_inhibited=false;break;
case 2:test.request.feedback_settled=false;break;
case 3:test.request.source_generation++;break;
case 4:test.request.selector_bits=2;break;
case 5:test.request.selector_bits=3;break;
case 6:test.plan.current_ma=2250;break;
case 7:test.plan.sink_pdo^=1u;break;
case 8:test.request.plan=NULL;break;
}
CHECK(execute(&test) == STUSB_REQUEST_UNSAFE_PRECONDITION);
CHECK(test.mock.calls == 0); check_fault(&test);
}
for (unsigned missing=0; missing<3; ++missing) {
initialize(&test, 0);
if (missing == 0) test.bus.read=NULL;
if (missing == 1) test.bus.write=NULL;
if (missing == 2) test.bus.now_us=NULL;
CHECK(execute(&test) == STUSB_REQUEST_INVALID_ARGUMENT); check_fault(&test);
}
initialize(&test, 0); test.request.request_id=0;
CHECK(execute(&test) == STUSB_REQUEST_REUSED_TOKEN); check_fault(&test);
initialize(&test, 0); test.state.last_request_id=UINT32_MAX;
CHECK(execute(&test) == STUSB_REQUEST_REUSED_TOKEN); check_fault(&test);
CHECK(stusb_request_contract(NULL,NULL) == STUSB_REQUEST_INVALID_ARGUMENT);
printf("STUSB4500 request host tests passed: %u assertions; live negotiation/target integration pending\n", checks);
}