bus: emul: Update i2c/spi emulators with mock transport

Adding a hook for tests to inject a mock transport and migrating the
accel test to test bmi160 specific things. The old version of the test
which checks for read values is now covered by the generic test in
the sensor build_all target.

Signed-off-by: Yuval Peress <peress@google.com>
This commit is contained in:
Yuval Peress 2023-11-17 09:38:54 -07:00 committed by Carles Cufí
parent fd07f8ce69
commit b58b03196b
13 changed files with 315 additions and 202 deletions

View file

@ -90,12 +90,14 @@ static int i2c_emul_transfer(const struct device *dev, struct i2c_msg *msgs, uin
__ASSERT_NO_MSG(emul->api);
__ASSERT_NO_MSG(emul->api->transfer);
ret = api->transfer(emul->target, msgs, num_msgs, addr);
if (ret) {
return ret;
if (emul->mock_api != NULL && emul->mock_api->transfer != NULL) {
ret = emul->mock_api->transfer(emul->target, msgs, num_msgs, addr);
if (ret != -ENOSYS) {
return ret;
}
}
return 0;
return api->transfer(emul->target, msgs, num_msgs, addr);
}
/**
@ -125,7 +127,7 @@ int i2c_emul_register(const struct device *dev, struct i2c_emul *emul)
sys_slist_append(&data->emuls, &emul->node);
LOG_INF("Register emulator '%s' at I2C addr %02x\n", name, emul->addr);
LOG_INF("Register emulator '%s' at I2C addr %02x", name, emul->addr);
return 0;
}

View file

@ -176,52 +176,44 @@ static int bmi160_emul_io_spi(const struct emul *target, const struct spi_config
__ASSERT_NO_MSG(!tx_bufs || !rx_bufs || tx_bufs->count == rx_bufs->count);
count = tx_bufs ? tx_bufs->count : rx_bufs->count;
switch (count) {
case 2:
tx = tx_bufs->buffers;
txd = &tx_bufs->buffers[1];
rxd = rx_bufs ? &rx_bufs->buffers[1] : NULL;
switch (tx->len) {
case 1:
regn = *(uint8_t *)tx->buf;
if ((regn & BMI160_REG_READ) && rxd == NULL) {
LOG_ERR("Cannot read without rxd");
return -EPERM;
}
switch (txd->len) {
case 1:
if (regn & BMI160_REG_READ) {
regn &= BMI160_REG_MASK;
val = reg_read(target, regn);
*(uint8_t *)rxd->buf = val;
} else {
val = *(uint8_t *)txd->buf;
reg_write(target, regn, val);
}
break;
case BMI160_SAMPLE_SIZE:
if (regn & BMI160_REG_READ) {
for (int i = 0; i < BMI160_SAMPLE_SIZE; ++i) {
((uint8_t *)rxd->buf)[i] = reg_read(
target, (regn & BMI160_REG_MASK) + i);
}
} else {
LOG_DBG("Unknown sample write");
}
break;
default:
LOG_DBG("Unknown A txd->len %d", txd->len);
break;
}
break;
default:
LOG_DBG("Unknown tx->len %d", tx->len);
break;
}
break;
default:
if (count != 2) {
LOG_DBG("Unknown tx_bufs->count %d", count);
break;
return -EIO;
}
tx = tx_bufs->buffers;
txd = &tx_bufs->buffers[1];
rxd = rx_bufs ? &rx_bufs->buffers[1] : NULL;
if (tx->len != 1) {
LOG_DBG("Unknown tx->len %d", tx->len);
return -EIO;
}
regn = *(uint8_t *)tx->buf;
if ((regn & BMI160_REG_READ) && rxd == NULL) {
LOG_ERR("Cannot read without rxd");
return -EPERM;
}
if (txd->len == 1) {
if (regn & BMI160_REG_READ) {
regn &= BMI160_REG_MASK;
val = reg_read(target, regn);
*(uint8_t *)rxd->buf = val;
} else {
val = *(uint8_t *)txd->buf;
reg_write(target, regn, val);
}
} else {
if (regn & BMI160_REG_READ) {
regn &= BMI160_REG_MASK;
for (int i = 0; i < txd->len; ++i) {
((uint8_t *)rxd->buf)[i] = reg_read(target, regn + i);
}
} else {
LOG_ERR("Unknown sample write");
return -EIO;
}
}
return 0;
@ -446,7 +438,6 @@ static int bmi160_emul_backend_get_sample_range(const struct emul *target, enum
default:
return -EINVAL;
}
}
static int bmi160_emul_backend_set_offset(const struct emul *target, enum sensor_channel ch,

View file

@ -68,6 +68,7 @@ static int spi_emul_io(const struct device *dev, const struct spi_config *config
{
struct spi_emul *emul;
const struct spi_emul_api *api;
int ret;
emul = spi_emul_find(dev, config->slave);
if (!emul) {
@ -78,6 +79,13 @@ static int spi_emul_io(const struct device *dev, const struct spi_config *config
__ASSERT_NO_MSG(emul->api);
__ASSERT_NO_MSG(emul->api->io);
if (emul->mock_api != NULL && emul->mock_api->io != NULL) {
ret = emul->mock_api->io(emul->target, config, tx_bufs, rx_bufs);
if (ret != -ENOSYS) {
return ret;
}
}
return api->io(emul->target, config, tx_bufs, rx_bufs);
}

View file

@ -468,6 +468,18 @@ static inline bool i2c_is_ready_dt(const struct i2c_dt_spec *spec)
return device_is_ready(spec->bus);
}
/**
* @brief Check if the current message is a read operation
*
* @param msg The message to check
* @return true if the I2C message is sa read operation
* @return false if the I2C message is a write operation
*/
static inline bool i2c_is_read_op(struct i2c_msg *msg)
{
return (msg->flags & I2C_MSG_READ) == I2C_MSG_READ;
}
/**
* @brief Dump out an I2C message
*

View file

@ -15,6 +15,7 @@
#include <zephyr/device.h>
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/sys/slist.h>
#include <zephyr/types.h>
@ -42,6 +43,12 @@ struct i2c_emul {
/* API provided for this device */
const struct i2c_emul_api *api;
/**
* A mock API that if not NULL will take precedence over the actual API. If set, a return
* value of -ENOSYS will revert back to the default api.
*/
struct i2c_emul_api *mock_api;
/* I2C address of the emulated device */
uint16_t addr;
};

View file

@ -43,6 +43,12 @@ struct spi_emul {
/* API provided for this device */
const struct spi_emul_api *api;
/**
* A mock API that if not NULL will take precedence over the actual API. If set, a return
* value of -ENOSYS will revert back to the default api.
*/
struct spi_emul_api *mock_api;
/* SPI chip-select of the emulated device */
uint16_t chipsel;
};

View file

@ -4,5 +4,12 @@ cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(device)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})
target_include_directories(app PRIVATE include)
target_sources(app
PRIVATE
include/checks.h
include/fixture.h
src/fixture.c
src/i2c.c
src/spi.c
)

View file

@ -0,0 +1,36 @@
/*
* Copyright 2023 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef TEST_DRIVERS_SENSOR_ACCEL_INCLUDE_CHECKS_H_
#define TEST_DRIVERS_SENSOR_ACCEL_INCLUDE_CHECKS_H_
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/spi.h>
#include "bmi160.h"
static inline bool bmi160_i2c_is_touching_reg(struct i2c_msg *msgs, int num_msgs, int reg)
{
__ASSERT_NO_MSG(num_msgs == 2);
__ASSERT_NO_MSG(msgs[0].len == 1);
uint8_t start_reg = msgs[0].buf[0];
return (start_reg <= reg) && (reg < start_reg + msgs[1].len);
}
static inline bool bmi160_spi_is_touching_reg(const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs, int reg)
{
__ASSERT_NO_MSG(tx_bufs->count == 2);
const struct spi_buf *tx = &tx_bufs->buffers[0];
const struct spi_buf *tx_data = &tx_bufs->buffers[1];
uint32_t start_reg = ((uint8_t *)(tx->buf))[0] & BMI160_REG_MASK;
return (start_reg <= reg) && (reg < start_reg + tx_data->len);
}
#endif /* TEST_DRIVERS_SENSOR_ACCEL_INCLUDE_CHECKS_H_ */

View file

@ -0,0 +1,20 @@
/*
* Copyright 2023 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef TEST_DRIVERS_SENSOR_ACCEL_INCLUDE_FIXTURE_H_
#define TEST_DRIVERS_SENSOR_ACCEL_INCLUDE_FIXTURE_H_
#include <zephyr/device.h>
#include <zephyr/drivers/emul.h>
struct bmi160_fixture {
const struct device *dev_spi;
const struct device *dev_i2c;
const struct emul *emul_spi;
const struct emul *emul_i2c;
};
#endif /* TEST_DRIVERS_SENSOR_ACCEL_INCLUDE_FIXTURE_H_ */

View file

@ -0,0 +1,69 @@
/*
* Copyright 2023 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "fixture.h"
#include <zephyr/drivers/emul_sensor.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/ztest.h>
static void sensor_bmi160_setup_emulator(const struct device *dev, const struct emul *emulator)
{
static struct {
enum sensor_channel channel;
q31_t value;
} values[] = {
{SENSOR_CHAN_ACCEL_X, 0}, {SENSOR_CHAN_ACCEL_Y, 1 << 28},
{SENSOR_CHAN_ACCEL_Z, 2 << 28}, {SENSOR_CHAN_GYRO_X, 3 << 28},
{SENSOR_CHAN_GYRO_Y, 4 << 28}, {SENSOR_CHAN_GYRO_Z, 5 << 28},
};
static struct sensor_value scale;
/* 4g */
scale.val1 = 39;
scale.val2 = 226600;
zassert_ok(sensor_attr_set(dev, SENSOR_CHAN_ACCEL_XYZ, SENSOR_ATTR_FULL_SCALE, &scale));
/* 125 deg/s */
scale.val1 = 2;
scale.val2 = 181661;
zassert_ok(sensor_attr_set(dev, SENSOR_CHAN_GYRO_XYZ, SENSOR_ATTR_FULL_SCALE, &scale));
for (size_t i = 0; i < ARRAY_SIZE(values); ++i) {
zassert_ok(emul_sensor_backend_set_channel(emulator, values[i].channel,
&values[i].value, 3));
}
}
static void *bmi160_setup(void)
{
static struct bmi160_fixture fixture = {
.dev_spi = DEVICE_DT_GET(DT_ALIAS(accel_0)),
.dev_i2c = DEVICE_DT_GET(DT_ALIAS(accel_1)),
.emul_spi = EMUL_DT_GET(DT_ALIAS(accel_0)),
.emul_i2c = EMUL_DT_GET(DT_ALIAS(accel_1)),
};
sensor_bmi160_setup_emulator(fixture.dev_i2c, fixture.emul_i2c);
sensor_bmi160_setup_emulator(fixture.dev_spi, fixture.emul_spi);
return &fixture;
}
static void bmi160_before(void *f)
{
struct bmi160_fixture *fixture = (struct bmi160_fixture *)f;
zassert_true(device_is_ready(fixture->dev_spi), "'%s' device is not ready",
fixture->dev_spi->name);
zassert_true(device_is_ready(fixture->dev_i2c), "'%s' device is not ready",
fixture->dev_i2c->name);
k_object_access_grant(fixture->dev_spi, k_current_get());
k_object_access_grant(fixture->dev_i2c, k_current_get());
}
ZTEST_SUITE(bmi160, NULL, bmi160_setup, bmi160_before, NULL, NULL);

View file

@ -0,0 +1,52 @@
/*
* Copyright 2023 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/emul_sensor.h>
#include <zephyr/drivers/i2c_emul.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/ztest.h>
#include "bmi160.h"
#include "checks.h"
#include "fixture.h"
static int mock_i2c_transfer_fail_reg_number = -1;
static int mock_i2c_transfer(const struct emul *target, struct i2c_msg *msgs, int num_msgs,
int addr)
{
ARG_UNUSED(target);
ARG_UNUSED(addr);
if (mock_i2c_transfer_fail_reg_number >= 0 && i2c_is_read_op(&msgs[1]) &&
bmi160_i2c_is_touching_reg(msgs, num_msgs, mock_i2c_transfer_fail_reg_number)) {
return -EIO;
}
return -ENOSYS;
}
ZTEST_USER_F(bmi160, test_bmi160_i2c_get_offset_fail_to_read_offset_acc)
{
struct i2c_emul_api mock_bus_api;
struct sensor_value value;
fixture->emul_i2c->bus.i2c->mock_api = &mock_bus_api;
mock_bus_api.transfer = mock_i2c_transfer;
enum sensor_channel channels[] = {SENSOR_CHAN_ACCEL_XYZ, SENSOR_CHAN_GYRO_XYZ};
int fail_registers[] = {BMI160_REG_OFFSET_ACC_X, BMI160_REG_OFFSET_ACC_Y,
BMI160_REG_OFFSET_ACC_Z, BMI160_REG_OFFSET_GYR_X,
BMI160_REG_OFFSET_GYR_Y, BMI160_REG_OFFSET_GYR_Z,
BMI160_REG_OFFSET_EN};
for (int fail_reg_idx = 0; fail_reg_idx < ARRAY_SIZE(fail_registers); ++fail_reg_idx) {
mock_i2c_transfer_fail_reg_number = fail_registers[fail_reg_idx];
for (int chan_idx = 0; chan_idx < ARRAY_SIZE(channels); ++chan_idx) {
zassert_equal(-EIO, sensor_attr_get(fixture->dev_i2c, channels[chan_idx],
SENSOR_ATTR_OFFSET, &value));
}
}
}

View file

@ -1,149 +0,0 @@
/*
* Copyright 2020 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup driver_sensor_subsys_tests sensor_subsys
* @ingroup all_tests
* @{
* @}
*/
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/emul_sensor.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/ztest.h>
struct sensor_accel_fixture {
const struct device *accel_spi;
const struct device *accel_i2c;
const struct emul *accel_emul_spi;
const struct emul *accel_emul_i2c;
};
static enum sensor_channel channel[] = {
SENSOR_CHAN_ACCEL_X, SENSOR_CHAN_ACCEL_Y, SENSOR_CHAN_ACCEL_Z,
SENSOR_CHAN_GYRO_X, SENSOR_CHAN_GYRO_Y, SENSOR_CHAN_GYRO_Z,
};
static int32_t compute_epsilon_micro(q31_t value, int8_t shift)
{
int64_t intermediate = value;
if (shift > 0) {
intermediate <<= shift;
} else if (shift < 0) {
intermediate >>= -shift;
}
intermediate = intermediate * INT64_C(1000000) / INT32_MAX;
return CLAMP(intermediate, INT32_MIN, INT32_MAX);
}
static void test_sensor_accel_basic(const struct device *dev, const struct emul *emulator)
{
q31_t accel_ranges[3];
q31_t gyro_ranges[3];
int8_t accel_shift;
int8_t gyro_shift;
zassert_ok(sensor_sample_fetch(dev), "fail to fetch sample");
zassert_ok(emul_sensor_backend_get_sample_range(emulator, SENSOR_CHAN_ACCEL_XYZ,
&accel_ranges[0], &accel_ranges[1],
&accel_ranges[2], &accel_shift));
zassert_ok(emul_sensor_backend_get_sample_range(emulator, SENSOR_CHAN_GYRO_XYZ,
&gyro_ranges[0], &gyro_ranges[1],
&gyro_ranges[2], &gyro_shift));
int32_t accel_epsilon = compute_epsilon_micro(accel_ranges[2], accel_shift);
int32_t gyro_epsilon = compute_epsilon_micro(gyro_ranges[2], gyro_shift);
for (int i = 0; i < ARRAY_SIZE(channel); i++) {
struct sensor_value val;
int64_t micro_val;
int32_t epsilon = (i < 3) ? accel_epsilon : gyro_epsilon;
zassert_ok(sensor_channel_get(dev, channel[i], &val), "fail to get channel");
micro_val = sensor_value_to_micro(&val);
zassert_within(i * INT64_C(1000000), micro_val, epsilon,
"%d. expected %" PRIi64 " to be within %d of %" PRIi64, i,
i * INT64_C(1000000), epsilon, micro_val);
}
}
/* Run all of our tests on an accelerometer device with the given label */
static void run_tests_on_accel(const struct device *accel)
{
zassert_true(device_is_ready(accel), "Accelerometer device is not ready");
PRINT("Running tests on '%s'\n", accel->name);
k_object_access_grant(accel, k_current_get());
}
ZTEST_USER_F(sensor_accel, test_sensor_accel_basic_spi)
{
run_tests_on_accel(fixture->accel_spi);
test_sensor_accel_basic(fixture->accel_spi, fixture->accel_emul_spi);
}
ZTEST_USER_F(sensor_accel, test_sensor_accel_basic_i2c)
{
if (fixture->accel_i2c == NULL) {
ztest_test_skip();
}
run_tests_on_accel(fixture->accel_i2c);
test_sensor_accel_basic(fixture->accel_i2c, fixture->accel_emul_i2c);
}
static void sensor_accel_setup_emulator(const struct device *dev, const struct emul *accel_emul)
{
if (accel_emul == NULL) {
return;
}
static struct {
enum sensor_channel channel;
q31_t value;
} values[] = {
{SENSOR_CHAN_ACCEL_X, 0}, {SENSOR_CHAN_ACCEL_Y, 1 << 28},
{SENSOR_CHAN_ACCEL_Z, 2 << 28}, {SENSOR_CHAN_GYRO_X, 3 << 28},
{SENSOR_CHAN_GYRO_Y, 4 << 28}, {SENSOR_CHAN_GYRO_Z, 5 << 28},
};
static struct sensor_value scale;
/* 4g */
scale.val1 = 39;
scale.val2 = 226600;
zassert_ok(sensor_attr_set(dev, SENSOR_CHAN_ACCEL_XYZ, SENSOR_ATTR_FULL_SCALE, &scale));
/* 125 deg/s */
scale.val1 = 2;
scale.val2 = 181661;
zassert_ok(sensor_attr_set(dev, SENSOR_CHAN_GYRO_XYZ, SENSOR_ATTR_FULL_SCALE, &scale));
for (int i = 0; i < ARRAY_SIZE(values); ++i) {
zassert_ok(emul_sensor_backend_set_channel(accel_emul, values[i].channel,
&values[i].value, 3));
}
}
static void *sensor_accel_setup(void)
{
static struct sensor_accel_fixture fixture = {
.accel_spi = DEVICE_DT_GET(DT_ALIAS(accel_0)),
.accel_i2c = DEVICE_DT_GET_OR_NULL(DT_ALIAS(accel_1)),
.accel_emul_spi = EMUL_DT_GET(DT_ALIAS(accel_0)),
.accel_emul_i2c = EMUL_DT_GET_OR_NULL(DT_ALIAS(accel_1)),
};
sensor_accel_setup_emulator(fixture.accel_spi, fixture.accel_emul_spi);
sensor_accel_setup_emulator(fixture.accel_i2c, fixture.accel_emul_i2c);
return &fixture;
}
ZTEST_SUITE(sensor_accel, NULL, sensor_accel_setup, NULL, NULL, NULL);

View file

@ -0,0 +1,52 @@
/*
* Copyright 2023 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/emul_sensor.h>
#include <zephyr/drivers/spi_emul.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/ztest.h>
#include "bmi160.h"
#include "checks.h"
#include "fixture.h"
static int mock_spi_io_fail_reg_number = -1;
static int mock_spi_io(const struct emul *target, const struct spi_config *config,
const struct spi_buf_set *tx_bufs, const struct spi_buf_set *rx_bufs)
{
ARG_UNUSED(target);
ARG_UNUSED(config);
if (mock_spi_io_fail_reg_number >= 0 &&
bmi160_spi_is_touching_reg(tx_bufs, rx_bufs, mock_spi_io_fail_reg_number)) {
return -EIO;
}
return -ENOSYS;
}
ZTEST_USER_F(bmi160, test_bmi160_spi_get_offset_fail_to_read_offset_acc)
{
struct spi_emul_api mock_bus_api;
struct sensor_value value;
fixture->emul_spi->bus.spi->mock_api = &mock_bus_api;
mock_bus_api.io = mock_spi_io;
enum sensor_channel channels[] = {SENSOR_CHAN_ACCEL_XYZ, SENSOR_CHAN_GYRO_XYZ};
int fail_registers[] = {BMI160_REG_OFFSET_ACC_X, BMI160_REG_OFFSET_ACC_Y,
BMI160_REG_OFFSET_ACC_Z, BMI160_REG_OFFSET_GYR_X,
BMI160_REG_OFFSET_GYR_Y, BMI160_REG_OFFSET_GYR_Z,
BMI160_REG_OFFSET_EN};
for (int fail_reg_idx = 0; fail_reg_idx < ARRAY_SIZE(fail_registers); ++fail_reg_idx) {
mock_spi_io_fail_reg_number = fail_registers[fail_reg_idx];
for (int chan_idx = 0; chan_idx < ARRAY_SIZE(channels); ++chan_idx) {
zassert_equal(-EIO, sensor_attr_get(fixture->dev_spi, channels[chan_idx],
SENSOR_ATTR_OFFSET, &value));
}
}
}