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_nvm_test.c

#include "stusb4500_nvm.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
static unsigned checks;
#define CHECK(condition) do { ++checks; assert(condition); } while (0)
struct mock_nvm {
  uint8_t registers[256], nvm[40];
  uint32_t now_us, expected_deadline_us;
  unsigned calls, fail_call, late_call, wrong_state_call;
  unsigned busy_reads, busy_remaining, sectors_requested, reset_count;
  bool apply_failed_write, apply_failed_read, stuck_busy, stopped_timer, unlocked;
};
struct nvm_test {
  struct mock_nvm mock;
  struct stusb_nvm_image expected;
  struct stusb_nvm_state state;
  struct stusb_nvm_check check;
  struct stusb_bus bus;
};
static uint32_t now_us(void *context) {
  struct mock_nvm *mock = context;
  const uint32_t stamp = mock->now_us;
  if (!mock->stopped_timer) ++mock->now_us;
  return stamp;
}
static void transfer_started(struct mock_nvm *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;
}
static bool read_register(void *context, const struct stusb_read *transfer) {
  struct mock_nvm *mock = context;
  CHECK(mock->unlocked);
  CHECK((transfer->register_address == 0x96 && transfer->byte_count == 1) ||
        (transfer->register_address == 0x53 && transfer->byte_count == 8));
  transfer_started(mock, transfer->deadline_us);
  const bool failed = mock->calls == mock->fail_call;
  if (failed && !mock->apply_failed_read) return false;
  if (transfer->register_address == 0x96 && !mock->stuck_busy) {
    if (mock->busy_remaining) --mock->busy_remaining;
    else {
      mock->registers[0x96] &= (uint8_t)~0x10u;
      const unsigned sector = mock->registers[0x96] & 7u;
      CHECK(sector < 5);
      memcpy(&mock->registers[0x53], &mock->nvm[sector*8], 8);
    }
  }
  if (transfer->register_address == 0x53) CHECK(!(mock->registers[0x96] & 0x10u));
  memcpy(transfer->bytes, &mock->registers[transfer->register_address], transfer->byte_count);
  if (mock->calls == mock->wrong_state_call) transfer->bytes[0] ^= 1u;
  return !failed;
}
static bool write_register(void *context, const struct stusb_write *transfer) {
  struct mock_nvm *mock = context;
  CHECK(transfer->register_address == 0x95 || transfer->register_address == 0x96 ||
        transfer->register_address == 0x97);
  CHECK(transfer->byte_count == 1 ||
        (transfer->register_address == 0x96 && transfer->byte_count == 2));
  if (transfer->register_address == 0x97) CHECK(transfer->bytes[0] == 0); /* No program/erase. */
  if (transfer->byte_count == 2) CHECK(transfer->bytes[0] == 0x40 && transfer->bytes[1] == 0);
  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 == 0x95) {
    CHECK(transfer->bytes[0] == 0x47 || transfer->bytes[0] == 0);
    mock->unlocked = transfer->bytes[0] == 0x47;
  } else {
    CHECK(mock->unlocked);
    if (transfer->register_address == 0x96 && (transfer->bytes[0] & 0x10u)) {
      CHECK(mock->registers[0x97] == 0);
      CHECK((transfer->bytes[0] & 7u) == mock->sectors_requested);
      ++mock->sectors_requested;
      mock->busy_remaining=mock->busy_reads;
    }
    if (transfer->register_address == 0x96 && transfer->bytes[0] == 0)
      ++mock->reset_count;
  }
  memcpy(&mock->registers[transfer->register_address], transfer->bytes, transfer->byte_count);
  return !failed;
}
static void initialize(struct nvm_test *test) {
  *test=(struct nvm_test){0};
  test->mock.now_us=100; test->mock.expected_deadline_us=20100; test->mock.busy_reads=1;
  for (unsigned i=0; i<40; ++i) {
    test->expected.bytes[i]=(uint8_t)(3u*i+9u);
    test->mock.nvm[i]=test->expected.bytes[i];
  }
  test->check=(struct stusb_nvm_check){.state=&test->state, .expected_image=&test->expected,
    .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_nvm_result execute(struct nvm_test *test) {
  return stusb_nvm_verify(&test->bus, &test->check);
}
static void check_fault(struct nvm_test *test) {
  CHECK(test->state.faulted && !test->state.image_matches);
  const unsigned calls=test->mock.calls;
  CHECK(execute(test) == STUSB_NVM_LATCHED);
  CHECK(test->mock.calls == calls);
}
static void check_success(struct nvm_test *test) {
  CHECK(test->state.read_complete && test->state.image_matches && !test->state.faulted);
  CHECK(!test->mock.unlocked && test->mock.sectors_requested == 5);
  CHECK(test->mock.reset_count == 6 && test->mock.registers[0x96] == 0x40);
  CHECK(test->mock.registers[0x97] == 0 && test->mock.registers[0x95] == 0);
  for (unsigned i=0; i<40; ++i) CHECK(test->state.observed_image.bytes[i] == test->expected.bytes[i]);
}
int main(void) {
  struct nvm_test test;
  initialize(&test); CHECK(execute(&test) == STUSB_NVM_VERIFIED);
  CHECK(test.mock.calls == 40); check_success(&test);
  /* All40 compared bytes are required, including reserved configuration. */
  for (unsigned byte=0; byte<40; ++byte) {
    initialize(&test); test.mock.nvm[byte] ^= 1u;
    CHECK(execute(&test) == STUSB_NVM_MISMATCH);
    CHECK(test.state.read_complete && !test.mock.unlocked);
    CHECK(test.state.observed_image.bytes[byte] == test.mock.nvm[byte]); check_fault(&test);
  }
  for (unsigned call=1; call<=40; ++call) {
    for (unsigned apply=0; apply<2; ++apply) {
      initialize(&test); test.mock.fail_call=call;
      test.mock.apply_failed_read=apply; test.mock.apply_failed_write=apply;
      CHECK(execute(&test) == STUSB_NVM_IO_ERROR);
      CHECK(!test.state.read_complete && test.mock.calls == call); check_fault(&test);
    }
    initialize(&test); test.mock.late_call=call;
    CHECK(execute(&test) == STUSB_NVM_LATE);
    CHECK(!test.state.read_complete && test.mock.calls == call); check_fault(&test);
  }
  for (unsigned sector=0; sector<5; ++sector) {
    initialize(&test); test.mock.wrong_state_call=8u+sector*7u;
    CHECK(execute(&test) == STUSB_NVM_CONTROLLER_STATE);
    CHECK(!test.state.read_complete); check_fault(&test);
  }
  initialize(&test); test.mock.stuck_busy=true;
  CHECK(execute(&test) == STUSB_NVM_BUSY_TIMEOUT);
  CHECK(test.mock.calls == 70 && !test.state.read_complete); check_fault(&test);
  initialize(&test); test.mock.busy_reads=30;
  CHECK(execute(&test) == STUSB_NVM_VERIFIED); check_success(&test);
  initialize(&test); test.mock.busy_reads=40;
  CHECK(execute(&test) == STUSB_NVM_LATE && !test.state.read_complete); check_fault(&test);
  initialize(&test); test.mock.stopped_timer=true;
  CHECK(execute(&test) == STUSB_NVM_TIMER_STALLED);
  CHECK(test.mock.calls == 2); check_fault(&test);
  for (unsigned unsafe=0; unsafe<3; ++unsafe) {
    initialize(&test);
    if (unsafe == 0) test.check.power_path_inhibited=false;
    if (unsafe == 1) test.check.bridge_inhibited=false;
    if (unsafe == 2) test.check.provenance_invalidated=false;
    CHECK(execute(&test) == STUSB_NVM_UNSAFE_PRECONDITION);
    CHECK(test.mock.calls == 0); check_fault(&test);
  }
  initialize(&test); test.check.expected_image=NULL;
  CHECK(execute(&test) == STUSB_NVM_INVALID_ARGUMENT && test.mock.calls == 0); check_fault(&test);
  initialize(&test); test.check.expected_image=&test.state.observed_image;
  CHECK(execute(&test) == STUSB_NVM_INVALID_ARGUMENT && test.mock.calls == 0); check_fault(&test);
  initialize(&test); test.mock.now_us=20101;
  CHECK(execute(&test) == STUSB_NVM_LATE && test.mock.calls == 0); check_fault(&test);
  initialize(&test); test.check.started_us=101;
  CHECK(execute(&test) == STUSB_NVM_LATE && test.mock.calls == 0); check_fault(&test);
  initialize(&test); test.mock.now_us=UINT32_MAX-1000;
  test.check.started_us=test.mock.now_us; test.mock.expected_deadline_us=test.mock.now_us+20000;
  CHECK(execute(&test) == STUSB_NVM_VERIFIED); check_success(&test);
  initialize(&test); test.bus.read=NULL;
  CHECK(execute(&test) == STUSB_NVM_INVALID_ARGUMENT); check_fault(&test);
  initialize(&test); test.bus.write=NULL;
  CHECK(execute(&test) == STUSB_NVM_INVALID_ARGUMENT); check_fault(&test);
  initialize(&test); test.bus.now_us=NULL;
  CHECK(execute(&test) == STUSB_NVM_INVALID_ARGUMENT); check_fault(&test);
  initialize(&test); test.check.state=NULL;
  CHECK(execute(&test) == STUSB_NVM_INVALID_ARGUMENT && test.mock.calls == 0);
  CHECK(stusb_nvm_verify(NULL,NULL) == STUSB_NVM_INVALID_ARGUMENT);
  printf("STUSB4500 NVM host tests passed (%u assertions)\n", checks);
  return 0;
}