Fix SPI bugs #9 (NULL platform_ops) and #10 (missing CS toggle), widen chip_select to uint16_t

Bug #9: Both TX and RX SPI init params had platform_ops = NULL, causing
adf4382_init() -> no_os_spi_init() to fail with -EINVAL. Fixed by setting
platform_ops = &stm32_spi_ops and passing stm32_spi_extra with correct CS
port/pin for each device.

Bug #10: stm32_spi_write_and_read() never toggled chip select. Since TX
and RX ADF4382A share SPI4, every register write hit both PLLs. Rewrote
stm32_spi.c to assert CS LOW before transfer and deassert HIGH after,
using stm32_spi_extra metadata. Backward-compatible: legacy callers
(e.g., AD9523) with cs_port=NULL skip CS management.

Also widened chip_select from uint8_t to uint16_t in no_os_spi.h since
STM32 GPIO_PIN_xx values (e.g., GPIO_PIN_14=0x4000) overflow uint8_t.

10/10 tests pass (8 original + 2 new regression tests).
This commit is contained in:
Jason
2026-03-19 10:00:05 +02:00
parent 397969348e
commit 3b32f67087
11 changed files with 325 additions and 21 deletions
@@ -1,4 +1,5 @@
#include "adf4382a_manager.h" #include "adf4382a_manager.h"
#include "stm32_spi.h"
#include "diag_log.h" #include "diag_log.h"
#include "no_os_delay.h" #include "no_os_delay.h"
#include <stdio.h> #include <stdio.h>
@@ -12,7 +13,7 @@ extern SPI_HandleTypeDef hspi4;
extern TIM_HandleTypeDef htim3; extern TIM_HandleTypeDef htim3;
// Static function prototypes // Static function prototypes
static void set_chip_enable(uint8_t ce_pin, bool state); static void set_chip_enable(uint16_t ce_pin, bool state);
static void set_deladj_pin(uint8_t device, bool state); static void set_deladj_pin(uint8_t device, bool state);
static void set_delstr_pin(uint8_t device, bool state); static void set_delstr_pin(uint8_t device, bool state);
static void start_deladj_pwm(uint8_t device, uint16_t duty_cycle); static void start_deladj_pwm(uint8_t device, uint16_t duty_cycle);
@@ -25,6 +26,10 @@ int ADF4382A_Manager_Init(ADF4382A_Manager *manager, SyncMethod method)
int ret; int ret;
uint32_t t_start = HAL_GetTick(); uint32_t t_start = HAL_GetTick();
/* Platform SPI extras carry HAL handle + software CS port/pin */
static stm32_spi_extra spi_tx_extra;
static stm32_spi_extra spi_rx_extra;
DIAG_SECTION("ADF4382A LO MANAGER INIT"); DIAG_SECTION("ADF4382A LO MANAGER INIT");
DIAG("LO", "Init called with sync_method=%d (%s)", DIAG("LO", "Init called with sync_method=%d (%s)",
method, (method == SYNC_METHOD_TIMED) ? "TIMED" : "EZSYNC"); method, (method == SYNC_METHOD_TIMED) ? "TIMED" : "EZSYNC");
@@ -48,14 +53,23 @@ int ADF4382A_Manager_Init(ADF4382A_Manager *manager, SyncMethod method)
memset(&manager->spi_tx_param, 0, sizeof(manager->spi_tx_param)); memset(&manager->spi_tx_param, 0, sizeof(manager->spi_tx_param));
memset(&manager->spi_rx_param, 0, sizeof(manager->spi_rx_param)); memset(&manager->spi_rx_param, 0, sizeof(manager->spi_rx_param));
// Setup platform SPI extras with software CS for each device
spi_tx_extra.hspi = &hspi4;
spi_tx_extra.cs_port = TX_CS_GPIO_Port;
spi_tx_extra.cs_pin = TX_CS_Pin;
spi_rx_extra.hspi = &hspi4;
spi_rx_extra.cs_port = RX_CS_GPIO_Port;
spi_rx_extra.cs_pin = RX_CS_Pin;
// Setup TX SPI parameters for SPI4 // Setup TX SPI parameters for SPI4
manager->spi_tx_param.device_id = ADF4382A_SPI_DEVICE_ID; manager->spi_tx_param.device_id = ADF4382A_SPI_DEVICE_ID;
manager->spi_tx_param.max_speed_hz = ADF4382A_SPI_SPEED_HZ; manager->spi_tx_param.max_speed_hz = ADF4382A_SPI_SPEED_HZ;
manager->spi_tx_param.mode = NO_OS_SPI_MODE_0; manager->spi_tx_param.mode = NO_OS_SPI_MODE_0;
manager->spi_tx_param.chip_select = TX_CS_Pin; manager->spi_tx_param.chip_select = TX_CS_Pin;
manager->spi_tx_param.bit_order = NO_OS_SPI_BIT_ORDER_MSB_FIRST; manager->spi_tx_param.bit_order = NO_OS_SPI_BIT_ORDER_MSB_FIRST;
manager->spi_tx_param.platform_ops = NULL; manager->spi_tx_param.platform_ops = &stm32_spi_ops;
manager->spi_tx_param.extra = &hspi4; manager->spi_tx_param.extra = &spi_tx_extra;
// Setup RX SPI parameters for SPI4 // Setup RX SPI parameters for SPI4
manager->spi_rx_param.device_id = ADF4382A_SPI_DEVICE_ID; manager->spi_rx_param.device_id = ADF4382A_SPI_DEVICE_ID;
@@ -63,11 +77,12 @@ int ADF4382A_Manager_Init(ADF4382A_Manager *manager, SyncMethod method)
manager->spi_rx_param.mode = NO_OS_SPI_MODE_0; manager->spi_rx_param.mode = NO_OS_SPI_MODE_0;
manager->spi_rx_param.chip_select = RX_CS_Pin; manager->spi_rx_param.chip_select = RX_CS_Pin;
manager->spi_rx_param.bit_order = NO_OS_SPI_BIT_ORDER_MSB_FIRST; manager->spi_rx_param.bit_order = NO_OS_SPI_BIT_ORDER_MSB_FIRST;
manager->spi_rx_param.platform_ops = NULL; manager->spi_rx_param.platform_ops = &stm32_spi_ops;
manager->spi_rx_param.extra = &hspi4; manager->spi_rx_param.extra = &spi_rx_extra;
DIAG("LO", "SPI4 params: TX_CS=0x%04X RX_CS=0x%04X speed=%lu Hz", DIAG("LO", "SPI4 params: TX_CS=0x%04X RX_CS=0x%04X speed=%lu Hz platform_ops=%p",
TX_CS_Pin, RX_CS_Pin, (unsigned long)ADF4382A_SPI_SPEED_HZ); TX_CS_Pin, RX_CS_Pin, (unsigned long)ADF4382A_SPI_SPEED_HZ,
(const void*)manager->spi_tx_param.platform_ops);
// Configure TX parameters (10.5 GHz) // Configure TX parameters (10.5 GHz)
memset(&tx_param, 0, sizeof(tx_param)); memset(&tx_param, 0, sizeof(tx_param));
@@ -651,7 +666,7 @@ int ADF4382A_StrobePhaseShift(ADF4382A_Manager *manager, uint8_t device)
// Static helper functions // Static helper functions
static void set_chip_enable(uint8_t ce_pin, bool state) static void set_chip_enable(uint16_t ce_pin, bool state)
{ {
GPIO_TypeDef* port = (ce_pin == TX_CE_Pin) ? TX_CE_GPIO_Port : RX_CE_GPIO_Port; GPIO_TypeDef* port = (ce_pin == TX_CE_Pin) ? TX_CE_GPIO_Port : RX_CE_GPIO_Port;
HAL_GPIO_WritePin(port, ce_pin, state ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(port, ce_pin, state ? GPIO_PIN_SET : GPIO_PIN_RESET);
@@ -130,8 +130,8 @@ struct no_os_spi_init_param {
uint32_t device_id; uint32_t device_id;
/** maximum transfer speed */ /** maximum transfer speed */
uint32_t max_speed_hz; uint32_t max_speed_hz;
/** SPI chip select */ /** SPI chip select (widened to uint16_t for STM32 GPIO_PIN_xx masks) */
uint8_t chip_select; uint16_t chip_select;
/** SPI mode */ /** SPI mode */
enum no_os_spi_mode mode; enum no_os_spi_mode mode;
/** SPI bit order */ /** SPI bit order */
@@ -184,8 +184,8 @@ struct no_os_spi_desc {
uint32_t device_id; uint32_t device_id;
/** maximum transfer speed */ /** maximum transfer speed */
uint32_t max_speed_hz; uint32_t max_speed_hz;
/** SPI chip select */ /** SPI chip select (widened to uint16_t for STM32 GPIO_PIN_xx masks) */
uint8_t chip_select; uint16_t chip_select;
/** SPI mode */ /** SPI mode */
enum no_os_spi_mode mode; enum no_os_spi_mode mode;
/** SPI bit order */ /** SPI bit order */
@@ -1,6 +1,18 @@
#include "stm32_spi.h" #include "stm32_spi.h"
#include "no_os_error.h" #include "no_os_error.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
/**
* @brief Detect whether 'extra' points to a stm32_spi_extra struct (with
* cs_port != NULL) or is a bare SPI_HandleTypeDef* (legacy path).
*
* Heuristic: if cs_port is a valid-looking pointer (non-NULL, non-trivial)
* we treat extra as stm32_spi_extra. Legacy callers pass &hspi4 directly,
* whose first field (Instance) is a peripheral base address — never a small
* number, but also never a GPIO_TypeDef*. We use the struct's own cs_port
* field to discriminate: stm32_spi_extra always sets cs_port explicitly.
*/
int32_t stm32_spi_init(struct no_os_spi_desc **desc, int32_t stm32_spi_init(struct no_os_spi_desc **desc,
const struct no_os_spi_init_param *param) const struct no_os_spi_init_param *param)
@@ -12,10 +24,30 @@ int32_t stm32_spi_init(struct no_os_spi_desc **desc,
if (!*desc) if (!*desc)
return -ENOMEM; return -ENOMEM;
/* store platform handle (HAL SPI_HandleTypeDef*) in extra */ /*
(*desc)->extra = param->extra; * If the caller provides a stm32_spi_extra with cs_port set, allocate
* a copy so the descriptor owns the data. Otherwise, store the raw
* SPI_HandleTypeDef* for backward compatibility.
*/
const stm32_spi_extra *in_extra = (const stm32_spi_extra *)param->extra;
if (in_extra && in_extra->cs_port != NULL) {
/* Caller provided full stm32_spi_extra with software CS */
stm32_spi_extra *own = calloc(1, sizeof(stm32_spi_extra));
if (!own) {
free(*desc);
*desc = NULL;
return -ENOMEM;
}
memcpy(own, in_extra, sizeof(stm32_spi_extra));
(*desc)->extra = own;
} else {
/* Legacy: extra is a bare SPI_HandleTypeDef* */
(*desc)->extra = param->extra;
}
(*desc)->max_speed_hz = param->max_speed_hz; (*desc)->max_speed_hz = param->max_speed_hz;
(*desc)->mode = param->mode; (*desc)->mode = param->mode;
(*desc)->chip_select = param->chip_select;
return 0; return 0;
} }
@@ -27,11 +59,39 @@ int32_t stm32_spi_write_and_read(struct no_os_spi_desc *desc,
if (!desc || !data || bytes_number == 0) if (!desc || !data || bytes_number == 0)
return -EINVAL; return -EINVAL;
SPI_HandleTypeDef *hspi = (SPI_HandleTypeDef *)desc->extra; SPI_HandleTypeDef *hspi;
GPIO_TypeDef *cs_port = NULL;
uint16_t cs_pin = 0;
/*
* Determine HAL handle and optional CS info.
* If extra is a stm32_spi_extra with cs_port set, use its fields.
* Otherwise treat extra as a bare SPI_HandleTypeDef*.
*/
const stm32_spi_extra *sx = (const stm32_spi_extra *)desc->extra;
if (sx && sx->cs_port != NULL) {
hspi = sx->hspi;
cs_port = sx->cs_port;
cs_pin = sx->cs_pin;
} else {
hspi = (SPI_HandleTypeDef *)desc->extra;
}
if (!hspi) if (!hspi)
return -EINVAL; return -EINVAL;
if (HAL_SPI_TransmitReceive(hspi, data, data, bytes_number, 200) != HAL_OK) /* Assert CS (active low) */
if (cs_port)
HAL_GPIO_WritePin(cs_port, cs_pin, GPIO_PIN_RESET);
HAL_StatusTypeDef hal_ret;
hal_ret = HAL_SPI_TransmitReceive(hspi, data, data, bytes_number, 200);
/* Deassert CS */
if (cs_port)
HAL_GPIO_WritePin(cs_port, cs_pin, GPIO_PIN_SET);
if (hal_ret != HAL_OK)
return -EIO; return -EIO;
return 0; return 0;
@@ -41,6 +101,17 @@ int32_t stm32_spi_remove(struct no_os_spi_desc *desc)
{ {
if (!desc) if (!desc)
return -EINVAL; return -EINVAL;
/*
* If we allocated a stm32_spi_extra copy during init, free it.
* Detect by checking cs_port (same heuristic as init).
*/
if (desc->extra) {
const stm32_spi_extra *sx = (const stm32_spi_extra *)desc->extra;
if (sx->cs_port != NULL)
free(desc->extra);
}
free(desc); free(desc);
return 0; return 0;
} }
@@ -4,6 +4,24 @@
#include "no_os_spi.h" #include "no_os_spi.h"
#include "stm32f7xx_hal.h" #include "stm32f7xx_hal.h"
/**
* @struct stm32_spi_extra
* @brief Platform-specific SPI data for STM32.
*
* When software chip-select is needed (e.g. multiple devices sharing one SPI
* bus with GPIO-managed CS), set cs_port to the GPIO port and cs_pin to the
* GPIO pin mask. stm32_spi_write_and_read() will assert CS LOW before the
* transfer and deassert CS HIGH after.
*
* If cs_port is NULL (legacy usage where the caller passes a bare
* SPI_HandleTypeDef* as the extra pointer), CS management is skipped.
*/
typedef struct {
SPI_HandleTypeDef *hspi; /**< HAL SPI handle */
GPIO_TypeDef *cs_port; /**< GPIO port for software CS (NULL = no SW CS) */
uint16_t cs_pin; /**< GPIO pin mask for software CS */
} stm32_spi_extra;
extern const struct no_os_spi_platform_ops stm32_spi_ops; extern const struct no_os_spi_platform_ops stm32_spi_ops;
#endif /* _STM32_SPI_H_ */ #endif /* _STM32_SPI_H_ */
@@ -11,3 +11,5 @@ test_bug5_fine_phase_gpio_only
test_bug6_timer_variable_collision test_bug6_timer_variable_collision
test_bug7_gpio_pin_conflict test_bug7_gpio_pin_conflict
test_bug8_uart_commented_out test_bug8_uart_commented_out
test_bug9_platform_ops_null
test_bug10_spi_cs_not_toggled
+12 -4
View File
@@ -1,7 +1,7 @@
################################################################################ ################################################################################
# Makefile -- MCU firmware unit test harness for AERIS-10 # Makefile -- MCU firmware unit test harness for AERIS-10
# #
# Builds and runs host-side (macOS) tests for the 8 discovered firmware bugs. # Builds and runs host-side (macOS) tests for the 10 discovered firmware bugs.
# Uses mock HAL + spy/recording pattern to test real firmware code without # Uses mock HAL + spy/recording pattern to test real firmware code without
# hardware. # hardware.
# #
@@ -34,7 +34,9 @@ REAL_OBJ := adf4382a_manager.o
TESTS_WITH_REAL := test_bug1_timed_sync_init_ordering \ TESTS_WITH_REAL := test_bug1_timed_sync_init_ordering \
test_bug3_timed_sync_noop \ test_bug3_timed_sync_noop \
test_bug4_phase_shift_before_check \ test_bug4_phase_shift_before_check \
test_bug5_fine_phase_gpio_only test_bug5_fine_phase_gpio_only \
test_bug9_platform_ops_null \
test_bug10_spi_cs_not_toggled
# Tests that only need mocks (extracted patterns / static analysis) # Tests that only need mocks (extracted patterns / static analysis)
TESTS_MOCK_ONLY := test_bug2_ad9523_double_setup \ TESTS_MOCK_ONLY := test_bug2_ad9523_double_setup \
@@ -44,7 +46,7 @@ TESTS_MOCK_ONLY := test_bug2_ad9523_double_setup \
ALL_TESTS := $(TESTS_WITH_REAL) $(TESTS_MOCK_ONLY) ALL_TESTS := $(TESTS_WITH_REAL) $(TESTS_MOCK_ONLY)
.PHONY: all build test clean $(addprefix test_,bug1 bug2 bug3 bug4 bug5 bug6 bug7 bug8) .PHONY: all build test clean $(addprefix test_,bug1 bug2 bug3 bug4 bug5 bug6 bug7 bug8 bug9 bug10)
all: build test all: build test
@@ -52,7 +54,7 @@ build: $(ALL_TESTS)
test: build test: build
@echo "===============================================" @echo "==============================================="
@echo " Running all 8 bug tests..." @echo " Running all 10 bug tests..."
@echo "===============================================" @echo "==============================================="
@pass=0; fail=0; \ @pass=0; fail=0; \
for t in $(ALL_TESTS); do \ for t in $(ALL_TESTS); do \
@@ -125,6 +127,12 @@ test_bug7: test_bug7_gpio_pin_conflict
test_bug8: test_bug8_uart_commented_out test_bug8: test_bug8_uart_commented_out
./test_bug8_uart_commented_out ./test_bug8_uart_commented_out
test_bug9: test_bug9_platform_ops_null
./test_bug9_platform_ops_null
test_bug10: test_bug10_spi_cs_not_toggled
./test_bug10_spi_cs_not_toggled
# --- Clean --- # --- Clean ---
clean: clean:
@@ -47,7 +47,7 @@ struct no_os_spi_platform_ops {
struct no_os_spi_init_param { struct no_os_spi_init_param {
uint32_t device_id; uint32_t device_id;
uint32_t max_speed_hz; uint32_t max_speed_hz;
uint8_t chip_select; uint16_t chip_select;
enum no_os_spi_mode mode; enum no_os_spi_mode mode;
enum no_os_spi_bit_order bit_order; enum no_os_spi_bit_order bit_order;
enum no_os_spi_lanes lanes; enum no_os_spi_lanes lanes;
@@ -0,0 +1,26 @@
/* shim: stm32_spi.h -- provides stm32_spi_extra type and stm32_spi_ops mock
*
* The real stm32_spi.h includes stm32f7xx_hal.h which we can't use in tests.
* This shim provides the stm32_spi_extra struct and a mock stm32_spi_ops
* extern so that adf4382a_manager.c compiles against test infrastructure.
*/
#ifndef STM32_SPI_H_SHIM
#define STM32_SPI_H_SHIM
#include "stm32_hal_mock.h"
#include "ad_driver_mock.h"
/**
* @struct stm32_spi_extra
* @brief Platform-specific SPI data for STM32 (test mock version).
*/
typedef struct {
SPI_HandleTypeDef *hspi; /**< HAL SPI handle */
GPIO_TypeDef *cs_port; /**< GPIO port for software CS (NULL = no SW CS) */
uint16_t cs_pin; /**< GPIO pin mask for software CS */
} stm32_spi_extra;
/* Mock stm32_spi_ops -- declared in stm32_hal_mock.c */
extern const struct no_os_spi_platform_ops stm32_spi_ops;
#endif /* STM32_SPI_H_SHIM */
@@ -2,6 +2,7 @@
* stm32_hal_mock.c -- Spy/recording implementation of STM32 HAL stubs * stm32_hal_mock.c -- Spy/recording implementation of STM32 HAL stubs
******************************************************************************/ ******************************************************************************/
#include "stm32_hal_mock.h" #include "stm32_hal_mock.h"
#include "ad_driver_mock.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -262,3 +263,14 @@ void mock_tim_set_compare(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t Co
.extra = htim .extra = htim
}); });
} }
/* ========================= Mock stm32_spi_ops ===================== */
/* Stub SPI platform ops -- real adf4382a_manager.c references &stm32_spi_ops.
* In tests, adf4382_init() is mocked so no_os_spi_init() is never called.
* We provide a non-NULL struct so tests can assert platform_ops != NULL. */
static int mock_spi_init_stub(void) { return 0; }
const struct no_os_spi_platform_ops stm32_spi_ops = {
.init = mock_spi_init_stub,
};
@@ -0,0 +1,94 @@
/*******************************************************************************
* test_bug10_spi_cs_not_toggled.c
*
* Bug #10 (FIXED): stm32_spi_write_and_read() never toggled chip select.
* Since TX and RX ADF4382A share SPI4, every register write hit BOTH PLLs
* simultaneously.
*
* Post-fix behavior:
* 1. Manager_Init creates stm32_spi_extra structs with the correct CS
* port (GPIOG) and pin for each device (TX_CS_Pin, RX_CS_Pin).
* 2. spi_param.extra points to a stm32_spi_extra with cs_port != NULL,
* so stm32_spi_write_and_read() will assert/deassert CS around
* each transfer.
*
* Note: The actual SPI CS toggling is in stm32_spi.c at the platform level.
* This test verifies that the manager correctly provisions the CS metadata
* that stm32_spi.c uses.
******************************************************************************/
#include "adf4382a_manager.h"
#include "stm32_spi.h"
#include <assert.h>
#include <stdio.h>
int main(void)
{
ADF4382A_Manager mgr;
int ret;
printf("=== Bug #10 (FIXED): SPI CS not toggled ===\n");
/* ---- Test A: Init succeeds ---- */
spy_reset();
ret = ADF4382A_Manager_Init(&mgr, SYNC_METHOD_TIMED);
printf(" Manager_Init returned: %d (expected 0=OK)\n", ret);
assert(ret == ADF4382A_MANAGER_OK);
printf(" PASS: Init returned OK\n");
/* ---- Test B: TX extra is non-NULL ---- */
printf(" spi_tx_param.extra = %p (expected non-NULL)\n", mgr.spi_tx_param.extra);
assert(mgr.spi_tx_param.extra != NULL);
printf(" PASS: TX extra is non-NULL\n");
/* ---- Test C: TX extra has correct CS port and pin ---- */
stm32_spi_extra *tx_extra = (stm32_spi_extra *)mgr.spi_tx_param.extra;
printf(" TX cs_port = %p (expected GPIOG = %p)\n", (void *)tx_extra->cs_port, (void *)GPIOG);
assert(tx_extra->cs_port == GPIOG);
printf(" PASS: TX cs_port == GPIOG\n");
printf(" TX cs_pin = 0x%04X (expected TX_CS_Pin = 0x%04X)\n", tx_extra->cs_pin, TX_CS_Pin);
assert(tx_extra->cs_pin == TX_CS_Pin); /* GPIO_PIN_14 = 0x4000 */
printf(" PASS: TX cs_pin == TX_CS_Pin (GPIO_PIN_14)\n");
/* ---- Test D: TX extra has correct SPI handle ---- */
printf(" TX hspi = %p (expected &hspi4 = %p)\n", (void *)tx_extra->hspi, (void *)&hspi4);
assert(tx_extra->hspi == &hspi4);
printf(" PASS: TX hspi == &hspi4\n");
/* ---- Test E: RX extra is non-NULL ---- */
printf(" spi_rx_param.extra = %p (expected non-NULL)\n", mgr.spi_rx_param.extra);
assert(mgr.spi_rx_param.extra != NULL);
printf(" PASS: RX extra is non-NULL\n");
/* ---- Test F: RX extra has correct CS port and pin ---- */
stm32_spi_extra *rx_extra = (stm32_spi_extra *)mgr.spi_rx_param.extra;
printf(" RX cs_port = %p (expected GPIOG = %p)\n", (void *)rx_extra->cs_port, (void *)GPIOG);
assert(rx_extra->cs_port == GPIOG);
printf(" PASS: RX cs_port == GPIOG\n");
printf(" RX cs_pin = 0x%04X (expected RX_CS_Pin = 0x%04X)\n", rx_extra->cs_pin, RX_CS_Pin);
assert(rx_extra->cs_pin == RX_CS_Pin); /* GPIO_PIN_10 = 0x0400 */
printf(" PASS: RX cs_pin == RX_CS_Pin (GPIO_PIN_10)\n");
/* ---- Test G: RX extra has correct SPI handle ---- */
printf(" RX hspi = %p (expected &hspi4 = %p)\n", (void *)rx_extra->hspi, (void *)&hspi4);
assert(rx_extra->hspi == &hspi4);
printf(" PASS: RX hspi == &hspi4\n");
/* ---- Test H: TX and RX extras are DIFFERENT instances ---- */
printf(" TX extra = %p, RX extra = %p (expected different)\n",
(void *)tx_extra, (void *)rx_extra);
assert(tx_extra != rx_extra);
printf(" PASS: TX and RX have separate stm32_spi_extra instances\n");
/* ---- Test I: TX and RX have DIFFERENT CS pins ---- */
assert(tx_extra->cs_pin != rx_extra->cs_pin);
printf(" PASS: TX and RX CS pins differ (individual addressing)\n");
/* Cleanup */
ADF4382A_Manager_Deinit(&mgr);
printf("=== Bug #10 (FIXED): ALL TESTS PASSED ===\n\n");
return 0;
}
@@ -0,0 +1,58 @@
/*******************************************************************************
* test_bug9_platform_ops_null.c
*
* Bug #9 (FIXED): Both TX and RX SPI init params had platform_ops = NULL.
* adf4382_init() calls no_os_spi_init() which checks
* if (!param->platform_ops) return -EINVAL;
* so SPI init always silently failed.
*
* Post-fix behavior:
* 1. Manager_Init sets platform_ops = &stm32_spi_ops for both TX and RX.
* 2. platform_ops is non-NULL and points to the STM32 SPI platform ops.
******************************************************************************/
#include "adf4382a_manager.h"
#include "stm32_spi.h"
#include <assert.h>
#include <stdio.h>
int main(void)
{
ADF4382A_Manager mgr;
int ret;
printf("=== Bug #9 (FIXED): platform_ops was NULL ===\n");
/* ---- Test A: Init succeeds ---- */
spy_reset();
ret = ADF4382A_Manager_Init(&mgr, SYNC_METHOD_TIMED);
printf(" Manager_Init returned: %d (expected 0=OK)\n", ret);
assert(ret == ADF4382A_MANAGER_OK);
printf(" PASS: Init returned OK\n");
/* ---- Test B: TX platform_ops is non-NULL ---- */
printf(" spi_tx_param.platform_ops = %p (expected non-NULL)\n",
(void *)mgr.spi_tx_param.platform_ops);
assert(mgr.spi_tx_param.platform_ops != NULL);
printf(" PASS: TX platform_ops is non-NULL\n");
/* ---- Test C: RX platform_ops is non-NULL ---- */
printf(" spi_rx_param.platform_ops = %p (expected non-NULL)\n",
(void *)mgr.spi_rx_param.platform_ops);
assert(mgr.spi_rx_param.platform_ops != NULL);
printf(" PASS: RX platform_ops is non-NULL\n");
/* ---- Test D: Both point to stm32_spi_ops ---- */
printf(" &stm32_spi_ops = %p\n", (void *)&stm32_spi_ops);
assert(mgr.spi_tx_param.platform_ops == &stm32_spi_ops);
printf(" PASS: TX platform_ops == &stm32_spi_ops\n");
assert(mgr.spi_rx_param.platform_ops == &stm32_spi_ops);
printf(" PASS: RX platform_ops == &stm32_spi_ops\n");
/* Cleanup */
ADF4382A_Manager_Deinit(&mgr);
printf("=== Bug #9 (FIXED): ALL TESTS PASSED ===\n\n");
return 0;
}