Bind the driver to an emulated SLF3S on a fake I2C adapter, back it with a regulator whose enable and disable callbacks are counted, and check that the two stay paired across suspend and resume - including the cycles in which the sensor NACKs a command or the supply refuses to come up.
The suspend/resume helpers reproduce the PM core's rule that a resume callback only runs after a suspend callback that returned 0, so that the tests cannot construct an ordering the core never produces. One of the tests relies on it: it cycles the device ten times with rotating failure injection and asserts that the driver's enable count never climbs above one. Signed-off-by: Wadim Mueller <[email protected]> --- MAINTAINERS | 2 + drivers/iio/flow/.kunitconfig | 6 + drivers/iio/flow/Kconfig | 15 + drivers/iio/flow/Makefile | 1 + drivers/iio/flow/slf3s-kunit.c | 734 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 758 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 627595e245f3..5f87c0542700 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24835,6 +24835,8 @@ R: Maxwell Doose <[email protected]> L: [email protected] S: Maintained F: Documentation/devicetree/bindings/iio/flow/sensirion,slf3s.yaml +F: drivers/iio/flow/.kunitconfig +F: drivers/iio/flow/slf3s-kunit.c F: drivers/iio/flow/slf3s.c SENSIRION SPS30 AIR POLLUTION SENSOR DRIVER diff --git a/drivers/iio/flow/.kunitconfig b/drivers/iio/flow/.kunitconfig new file mode 100644 index 000000000000..20b177a241d0 --- /dev/null +++ b/drivers/iio/flow/.kunitconfig @@ -0,0 +1,6 @@ +CONFIG_KUNIT=y +CONFIG_I2C=y +CONFIG_REGULATOR=y +CONFIG_IIO=y +CONFIG_SENSIRION_SLF3S=y +CONFIG_SENSIRION_SLF3S_KUNIT_TEST=y diff --git a/drivers/iio/flow/Kconfig b/drivers/iio/flow/Kconfig index e0e1a8e3654a..cb56f9d8395e 100644 --- a/drivers/iio/flow/Kconfig +++ b/drivers/iio/flow/Kconfig @@ -19,4 +19,19 @@ config SENSIRION_SLF3S To compile this driver as a module, choose M here: the module will be called slf3s. +config SENSIRION_SLF3S_KUNIT_TEST + tristate "KUnit tests for the Sensirion SLF3S driver" if !KUNIT_ALL_TESTS + depends on KUNIT && SENSIRION_SLF3S && REGULATOR + default KUNIT_ALL_TESTS + help + Build KUnit tests for the Sensirion SLF3S driver. The tests bind + the driver to an emulated sensor and check that its suspend and + resume callbacks keep the supply regulator balanced, including on + the error paths. + + For more information on KUnit and unit tests in general, please + refer to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + endmenu diff --git a/drivers/iio/flow/Makefile b/drivers/iio/flow/Makefile index 3cf4ab95c69c..70c9054da4b6 100644 --- a/drivers/iio/flow/Makefile +++ b/drivers/iio/flow/Makefile @@ -5,3 +5,4 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_SENSIRION_SLF3S) += slf3s.o +obj-$(CONFIG_SENSIRION_SLF3S_KUNIT_TEST) += slf3s-kunit.o diff --git a/drivers/iio/flow/slf3s-kunit.c b/drivers/iio/flow/slf3s-kunit.c new file mode 100644 index 000000000000..d5e2db855838 --- /dev/null +++ b/drivers/iio/flow/slf3s-kunit.c @@ -0,0 +1,734 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for the Sensirion SLF3S driver's power management. + * + * The tests bind the real driver to a fake I2C device backed by a + * programmable regulator, so that every regulator_enable() and + * regulator_disable() the driver issues can be counted. The point of the + * suite is the balance of those calls across suspend/resume cycles, + * including the cycles in which the sensor or the supply fails. + * + * Copyright (c) 2026 + */ + +#include <kunit/device.h> +#include <kunit/resource.h> +#include <kunit/test.h> + +#include <linux/cleanup.h> +#include <linux/crc8.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/sprintf.h> +#include <linux/string.h> + +#define SLF3S_TEST_ADDR 0x08 +#define SLF3S_TEST_CRC8_POLY 0x31 +#define SLF3S_TEST_CRC8_INIT 0xff +#define SLF3S_TEST_PID_LEN 18 +/* "%d-%04x" for an adapter number and a 16-bit address, plus the nul. */ +#define SLF3S_TEST_CONSUMER_LEN 16 + +/* + * Family byte 0x03 and sub-type byte 0x03 make slf3s_detect_variant() + * settle on the SLF3S-0600F. The bytes sit at offsets 1 and 3 of the + * product-info block, i.e. in the low half of the first word and the high + * half of the second one. + */ +#define SLF3S_TEST_PID_WORD0 0x0003 +#define SLF3S_TEST_PID_WORD1 0x0300 + +enum slf3s_fake_state { + SLF3S_FAKE_OFF, + SLF3S_FAKE_IDLE, + SLF3S_FAKE_MEASURING, +}; + +enum slf3s_fake_cmd { + SLF3S_FAKE_CMD_NONE, + SLF3S_FAKE_CMD_PREP_PID, + SLF3S_FAKE_CMD_READ_PID, + SLF3S_FAKE_CMD_START_WATER, + SLF3S_FAKE_CMD_START_IPA, + SLF3S_FAKE_CMD_STOP, + SLF3S_FAKE_CMD_UNKNOWN, + SLF3S_FAKE_CMD_COUNT, +}; + +/** + * struct slf3s_fake - protocol-level emulation of an SLF3S sensor + * @adap: I2C adapter the emulated sensor answers on + * @lock: serialises state against the driver's transfers + * @state: power/measurement state of the emulated part + * @pid_armed: a product-id read command was accepted, a read may follow + * @fail_cmd: command whose next occurrence is rejected + * @fail_err: error returned for @fail_cmd + * @n_cmd: per-command counters, indexed by enum slf3s_fake_cmd + * @crc_table: CRC-8 table used to sign the emulated responses + */ +struct slf3s_fake { + struct i2c_adapter adap; + struct mutex lock; /* serialises state against the driver's transfers */ + enum slf3s_fake_state state; + bool pid_armed; + enum slf3s_fake_cmd fail_cmd; + int fail_err; + unsigned int n_cmd[SLF3S_FAKE_CMD_COUNT]; + u8 crc_table[CRC8_TABLE_SIZE]; +}; + +/** + * struct slf3s_test_reg - regulator that counts what the driver does to it + * @rdev: registered regulator device + * @lock: protects the counters against concurrent callbacks + * @n_enable: successful regulator_enable() calls seen + * @n_disable: successful regulator_disable() calls seen + * @enabled: current state of the emulated supply + * @fail_enable: error returned by the next enable, then cleared + */ +struct slf3s_test_reg { + struct regulator_dev *rdev; + struct mutex lock; /* protects the counters against concurrent callbacks */ + unsigned int n_enable; + unsigned int n_disable; + bool enabled; + int fail_enable; +}; + +/** + * struct slf3s_test_ctx - per-test fixture + * @fake: emulated sensor + * @reg: emulated supply + * @client: I2C client the driver is bound to + * @suspended: mirrors dev->power.is_suspended for the PM sequencer + */ +struct slf3s_test_ctx { + struct slf3s_fake fake; + struct slf3s_test_reg reg; + struct i2c_client *client; + bool suspended; +}; + +/* --- emulated sensor ---------------------------------------------------- */ + +static enum slf3s_fake_cmd slf3s_fake_decode(const u8 *buf) +{ + static const struct { + u8 bytes[2]; + enum slf3s_fake_cmd cmd; + } cmds[] = { + { { 0x36, 0x7c }, SLF3S_FAKE_CMD_PREP_PID }, + { { 0xe1, 0x02 }, SLF3S_FAKE_CMD_READ_PID }, + { { 0x36, 0x08 }, SLF3S_FAKE_CMD_START_WATER }, + { { 0x36, 0x15 }, SLF3S_FAKE_CMD_START_IPA }, + { { 0x3f, 0xf9 }, SLF3S_FAKE_CMD_STOP }, + }; + + for (unsigned int i = 0; i < ARRAY_SIZE(cmds); i++) { + if (!memcmp(buf, cmds[i].bytes, sizeof(cmds[i].bytes))) + return cmds[i].cmd; + } + + return SLF3S_FAKE_CMD_UNKNOWN; +} + +static int slf3s_fake_write(struct slf3s_fake *f, struct i2c_msg *msg) +{ + enum slf3s_fake_cmd cmd; + + if (msg->len != 2) + return -EIO; + + cmd = slf3s_fake_decode(msg->buf); + f->n_cmd[cmd]++; + + if (f->fail_cmd == cmd) { + f->fail_cmd = SLF3S_FAKE_CMD_NONE; + return f->fail_err; + } + + switch (cmd) { + case SLF3S_FAKE_CMD_PREP_PID: + case SLF3S_FAKE_CMD_READ_PID: + if (f->state != SLF3S_FAKE_IDLE) + return -ENXIO; + f->pid_armed = cmd == SLF3S_FAKE_CMD_READ_PID; + return 0; + case SLF3S_FAKE_CMD_START_WATER: + case SLF3S_FAKE_CMD_START_IPA: + if (f->state != SLF3S_FAKE_IDLE) + return -ENXIO; + f->state = SLF3S_FAKE_MEASURING; + return 0; + case SLF3S_FAKE_CMD_STOP: + /* The real part NACKs a stop when it is already idle. */ + if (f->state != SLF3S_FAKE_MEASURING) + return -ENXIO; + f->state = SLF3S_FAKE_IDLE; + return 0; + default: + return -ENXIO; + } +} + +static void slf3s_fake_put_word(struct slf3s_fake *f, u8 *dst, u16 val) +{ + dst[0] = val >> 8; + dst[1] = val & 0xff; + dst[2] = crc8(f->crc_table, dst, 2, SLF3S_TEST_CRC8_INIT); +} + +/* + * Only the product-info block is emulated. Sample reads are not part of + * these tests, and the driver never issues one on its own. + */ +static int slf3s_fake_read(struct slf3s_fake *f, struct i2c_msg *msg) +{ + u8 buf[SLF3S_TEST_PID_LEN]; + + if (!f->pid_armed) + return -ENXIO; + + f->pid_armed = false; + + if (msg->len != sizeof(buf)) + return -EIO; + + slf3s_fake_put_word(f, &buf[0], SLF3S_TEST_PID_WORD0); + slf3s_fake_put_word(f, &buf[3], SLF3S_TEST_PID_WORD1); + for (unsigned int i = 6; i < sizeof(buf); i += 3) + slf3s_fake_put_word(f, &buf[i], 0); + + memcpy(msg->buf, buf, sizeof(buf)); + + return 0; +} + +static int slf3s_fake_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + struct slf3s_fake *f = i2c_get_adapdata(adap); + + guard(mutex)(&f->lock); + + for (int i = 0; i < num; i++) { + int ret; + + if (msgs[i].addr != SLF3S_TEST_ADDR) + return -ENXIO; + + if (f->state == SLF3S_FAKE_OFF) + return -ENXIO; + + if (msgs[i].flags & I2C_M_RD) + ret = slf3s_fake_read(f, &msgs[i]); + else + ret = slf3s_fake_write(f, &msgs[i]); + + if (ret) + return ret; + } + + return num; +} + +static u32 slf3s_fake_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm slf3s_fake_algo = { + .xfer = slf3s_fake_xfer, + .functionality = slf3s_fake_func, +}; + +static void slf3s_fake_fail_next(struct slf3s_fake *f, + enum slf3s_fake_cmd cmd, int err) +{ + guard(mutex)(&f->lock); + + f->fail_cmd = cmd; + f->fail_err = err; +} + +static enum slf3s_fake_state slf3s_fake_state(struct slf3s_fake *f) +{ + guard(mutex)(&f->lock); + + return f->state; +} + +/* --- emulated supply ---------------------------------------------------- */ + +static int slf3s_test_reg_enable(struct regulator_dev *rdev) +{ + struct slf3s_test_reg *reg = rdev_get_drvdata(rdev); + + guard(mutex)(®->lock); + + if (reg->fail_enable) { + int err = reg->fail_enable; + + reg->fail_enable = 0; + return err; + } + + reg->n_enable++; + reg->enabled = true; + + return 0; +} + +static int slf3s_test_reg_disable(struct regulator_dev *rdev) +{ + struct slf3s_test_reg *reg = rdev_get_drvdata(rdev); + + guard(mutex)(®->lock); + + reg->n_disable++; + reg->enabled = false; + + return 0; +} + +static int slf3s_test_reg_is_enabled(struct regulator_dev *rdev) +{ + struct slf3s_test_reg *reg = rdev_get_drvdata(rdev); + + guard(mutex)(®->lock); + + return reg->enabled; +} + +static void slf3s_test_reg_fail_enable(struct slf3s_test_reg *reg, int err) +{ + guard(mutex)(®->lock); + + reg->fail_enable = err; +} + +static const struct regulator_ops slf3s_test_reg_ops = { + .enable = slf3s_test_reg_enable, + .disable = slf3s_test_reg_disable, + .is_enabled = slf3s_test_reg_is_enabled, +}; + +static const struct regulator_desc slf3s_test_reg_desc = { + .name = "slf3s-test-vdd", + .id = -1, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .ops = &slf3s_test_reg_ops, +}; + +/* --- PM sequencer ------------------------------------------------------- */ + +/* + * device_suspend() sets dev->power.is_suspended only when the callback + * returned 0, and device_resume() bails out before running any callback + * when the flag is clear. The resume callback therefore runs if and only + * if the immediately preceding suspend callback succeeded. The helpers + * below reproduce that rule so the tests cannot construct a sequence the + * PM core never produces. + */ +static int slf3s_test_suspend(struct kunit *test, struct slf3s_test_ctx *ctx) +{ + struct device *dev = &ctx->client->dev; + int ret; + + KUNIT_ASSERT_FALSE_MSG(test, ctx->suspended, + "suspend called twice without a resume"); + KUNIT_ASSERT_NOT_NULL(test, dev->driver); + KUNIT_ASSERT_NOT_NULL(test, dev->driver->pm); + KUNIT_ASSERT_NOT_NULL(test, dev->driver->pm->suspend); + + ret = dev->driver->pm->suspend(dev); + ctx->suspended = ret == 0; + + return ret; +} + +static int slf3s_test_resume(struct kunit *test, struct slf3s_test_ctx *ctx) +{ + struct device *dev = &ctx->client->dev; + + KUNIT_ASSERT_TRUE_MSG(test, ctx->suspended, + "resume after a failed suspend: the PM core does not do this"); + KUNIT_ASSERT_NOT_NULL(test, dev->driver->pm->resume); + + ctx->suspended = false; + + return dev->driver->pm->resume(dev); +} + +/* + * After a resume whose start command failed, the sensor is powered but + * idle. A test that wants to continue cycling has to put it back into a + * measuring state; doing so through the fake rather than through the + * driver keeps the driver's call counts untouched. + */ +static void slf3s_test_resume_sensor(struct kunit *test, + struct slf3s_test_ctx *ctx) +{ + guard(mutex)(&ctx->fake.lock); + + if (ctx->fake.state == SLF3S_FAKE_IDLE) + ctx->fake.state = SLF3S_FAKE_MEASURING; +} + +#define KUNIT_EXPECT_REG_BALANCED(test, ctx) do { \ + KUNIT_EXPECT_EQ((test), (ctx)->reg.n_enable, \ + (ctx)->reg.n_disable); \ + KUNIT_EXPECT_FALSE((test), (ctx)->reg.enabled); \ +} while (0) + +/* --- fixture ------------------------------------------------------------ */ + +static void slf3s_test_del_adapter(void *ptr) +{ + i2c_del_adapter(ptr); +} + +static void slf3s_test_unregister_reg(void *ptr) +{ + regulator_unregister(ptr); +} + +static void slf3s_test_unregister_client(void *ptr) +{ + i2c_unregister_device(ptr); +} + +static void slf3s_test_destroy_mutex(void *data) +{ + mutex_destroy(data); +} + +static int slf3s_test_init(struct kunit *test) +{ + struct regulator_consumer_supply *supply; + struct regulator_init_data *init_data; + struct i2c_board_info info = { }; + struct regulator_config config = { }; + struct slf3s_test_ctx *ctx; + struct device *parent; + char *consumer; + int ret; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + test->priv = ctx; + + /* Actions run in reverse, so destroy after the unbind takes the locks. */ + mutex_init(&ctx->fake.lock); + ret = kunit_add_action_or_reset(test, slf3s_test_destroy_mutex, + &ctx->fake.lock); + KUNIT_ASSERT_EQ(test, ret, 0); + + mutex_init(&ctx->reg.lock); + ret = kunit_add_action_or_reset(test, slf3s_test_destroy_mutex, + &ctx->reg.lock); + KUNIT_ASSERT_EQ(test, ret, 0); + + crc8_populate_msb(ctx->fake.crc_table, SLF3S_TEST_CRC8_POLY); + ctx->fake.state = SLF3S_FAKE_OFF; + + parent = kunit_device_register(test, "slf3s-test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent); + + strscpy(ctx->fake.adap.name, "slf3s-test-adapter", + sizeof(ctx->fake.adap.name)); + ctx->fake.adap.owner = THIS_MODULE; + ctx->fake.adap.algo = &slf3s_fake_algo; + ctx->fake.adap.dev.parent = parent; + i2c_set_adapdata(&ctx->fake.adap, &ctx->fake); + + ret = i2c_add_adapter(&ctx->fake.adap); + KUNIT_ASSERT_EQ(test, ret, 0); + ret = kunit_add_action_or_reset(test, slf3s_test_del_adapter, + &ctx->fake.adap); + KUNIT_ASSERT_EQ(test, ret, 0); + + /* + * The supply has to be resolvable by the time the client probes, and + * without a device tree that means a consumer map keyed on the client + * name. The name is "<bus>-<addr>", so the adapter has to exist + * first. + */ + consumer = kunit_kzalloc(test, SLF3S_TEST_CONSUMER_LEN, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, consumer); + scnprintf(consumer, SLF3S_TEST_CONSUMER_LEN, "%d-%04x", + ctx->fake.adap.nr, SLF3S_TEST_ADDR); + + supply = kunit_kzalloc(test, sizeof(*supply), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, supply); + supply->supply = "vdd"; + supply->dev_name = consumer; + + init_data = kunit_kzalloc(test, sizeof(*init_data), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, init_data); + init_data->constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; + init_data->num_consumer_supplies = 1; + init_data->consumer_supplies = supply; + + config.dev = parent; + config.init_data = init_data; + config.driver_data = &ctx->reg; + + ctx->reg.rdev = regulator_register(parent, &slf3s_test_reg_desc, + &config); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->reg.rdev); + ret = kunit_add_action_or_reset(test, slf3s_test_unregister_reg, + ctx->reg.rdev); + KUNIT_ASSERT_EQ(test, ret, 0); + + /* Powered but idle, like a part that has just been given its supply. */ + ctx->fake.state = SLF3S_FAKE_IDLE; + + strscpy(info.type, "slf3s-0600f", sizeof(info.type)); + info.addr = SLF3S_TEST_ADDR; + + ctx->client = i2c_new_client_device(&ctx->fake.adap, &info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->client); + ret = kunit_add_action_or_reset(test, slf3s_test_unregister_client, + ctx->client); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_ASSERT_NOT_NULL_MSG(test, ctx->client->dev.driver, + "the slf3s driver did not bind"); + /* One enable proves we got this regulator, not the dummy one. */ + KUNIT_ASSERT_EQ_MSG(test, ctx->reg.n_enable, 1, + "driver did not enable the test regulator"); + + return 0; +} + +/* --- tests -------------------------------------------------------------- */ + +/* T1: a clean cycle pairs every enable with a disable. */ +static void slf3s_test_cycle_balanced(struct kunit *test) +{ + struct slf3s_test_ctx *ctx = test->priv; + + KUNIT_EXPECT_EQ(test, slf3s_fake_state(&ctx->fake), + SLF3S_FAKE_MEASURING); + + KUNIT_EXPECT_EQ(test, slf3s_test_suspend(test, ctx), 0); + KUNIT_EXPECT_EQ(test, slf3s_fake_state(&ctx->fake), SLF3S_FAKE_IDLE); + KUNIT_EXPECT_FALSE(test, ctx->reg.enabled); + KUNIT_EXPECT_EQ(test, ctx->reg.n_disable, 1); + + KUNIT_EXPECT_EQ(test, slf3s_test_resume(test, ctx), 0); + KUNIT_EXPECT_EQ(test, slf3s_fake_state(&ctx->fake), + SLF3S_FAKE_MEASURING); + KUNIT_EXPECT_TRUE(test, ctx->reg.enabled); + KUNIT_EXPECT_EQ(test, ctx->reg.n_enable, 2); + + kunit_release_action(test, slf3s_test_unregister_client, ctx->client); + + KUNIT_EXPECT_EQ(test, ctx->reg.n_enable, 2); + KUNIT_EXPECT_EQ(test, ctx->reg.n_disable, 2); + KUNIT_EXPECT_REG_BALANCED(test, ctx); +} + +/* + * T5: the proof for the question raised on the list. The PM core never + * runs two resumes in a row, so the driver's enable count can never climb + * above one, no matter how often the sensor fails. The sequencer refuses + * illegal orderings, so this loop is the legal worst case. + */ +static void slf3s_test_cycles_never_leak(struct kunit *test) +{ + struct slf3s_test_ctx *ctx = test->priv; + + for (unsigned int i = 0; i < 10; i++) { + int ret; + + switch (i % 3) { + case 1: + slf3s_fake_fail_next(&ctx->fake, + SLF3S_FAKE_CMD_START_WATER, + -ENXIO); + break; + case 2: + slf3s_fake_fail_next(&ctx->fake, SLF3S_FAKE_CMD_STOP, + -ENXIO); + break; + default: + break; + } + + ret = slf3s_test_suspend(test, ctx); + KUNIT_EXPECT_LE_MSG(test, ctx->reg.n_enable - ctx->reg.n_disable, + 1, "supply enabled more than once, cycle %u", + i); + if (ret) + continue; + + slf3s_test_resume(test, ctx); + KUNIT_EXPECT_LE_MSG(test, ctx->reg.n_enable - ctx->reg.n_disable, + 1, "supply enabled more than once, cycle %u", + i); + + /* Put the sensor back into a measuring state for the next round. */ + if (slf3s_fake_state(&ctx->fake) != SLF3S_FAKE_MEASURING && + !ctx->suspended) + slf3s_test_resume_sensor(test, ctx); + } +} + +/* T6: unbinding after a failed resume must not leave the supply on. */ +static void slf3s_test_unbind_after_failed_resume(struct kunit *test) +{ + struct slf3s_test_ctx *ctx = test->priv; + + KUNIT_EXPECT_EQ(test, slf3s_test_suspend(test, ctx), 0); + + slf3s_fake_fail_next(&ctx->fake, SLF3S_FAKE_CMD_START_WATER, -ENXIO); + KUNIT_EXPECT_LT(test, slf3s_test_resume(test, ctx), 0); + + kunit_release_action(test, slf3s_test_unregister_client, ctx->client); + + KUNIT_EXPECT_REG_BALANCED(test, ctx); +} + +/* T7: a probe that fails must not leave the supply enabled. */ +static void slf3s_test_probe_failure(struct kunit *test) +{ + struct slf3s_test_ctx *ctx = test->priv; + struct i2c_board_info info = { }; + struct i2c_client *client; + + /* Start from a clean slate: drop the client the fixture bound. */ + kunit_release_action(test, slf3s_test_unregister_client, ctx->client); + ctx->client = NULL; + KUNIT_ASSERT_FALSE(test, ctx->reg.enabled); + + slf3s_fake_fail_next(&ctx->fake, SLF3S_FAKE_CMD_PREP_PID, -ENXIO); + + strscpy(info.type, "slf3s-0600f", sizeof(info.type)); + info.addr = SLF3S_TEST_ADDR; + + client = i2c_new_client_device(&ctx->fake.adap, &info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, client); + + KUNIT_EXPECT_NULL_MSG(test, client->dev.driver, + "probe was expected to fail"); + KUNIT_EXPECT_REG_BALANCED(test, ctx); + + i2c_unregister_device(client); +} + +/* T2: a resume whose start command fails must switch the supply back off. */ +static void slf3s_test_resume_start_failure(struct kunit *test) +{ + struct slf3s_test_ctx *ctx = test->priv; + + KUNIT_ASSERT_EQ(test, slf3s_test_suspend(test, ctx), 0); + + slf3s_fake_fail_next(&ctx->fake, SLF3S_FAKE_CMD_START_WATER, -ENXIO); + KUNIT_EXPECT_LT(test, slf3s_test_resume(test, ctx), 0); + + KUNIT_EXPECT_FALSE_MSG(test, ctx->reg.enabled, + "supply left on after a failed resume"); + KUNIT_EXPECT_REG_BALANCED(test, ctx); +} + +/* T3: a stop command that fails is no reason to keep the supply on. */ +static void slf3s_test_suspend_stop_failure(struct kunit *test) +{ + struct slf3s_test_ctx *ctx = test->priv; + + slf3s_fake_fail_next(&ctx->fake, SLF3S_FAKE_CMD_STOP, -ENXIO); + slf3s_test_suspend(test, ctx); + + KUNIT_EXPECT_FALSE_MSG(test, ctx->reg.enabled, + "supply left on after a failed stop command"); + KUNIT_EXPECT_REG_BALANCED(test, ctx); +} + +/* + * T4: if the supply itself refuses to come up, the next suspend must not + * call regulator_disable() on a supply that was never enabled. Doing so + * trips "unbalanced disables" in the regulator core and aborts the whole + * system suspend with -EIO. + */ +static void slf3s_test_resume_enable_failure(struct kunit *test) +{ + struct slf3s_test_ctx *ctx = test->priv; + unsigned int n_disable; + + KUNIT_ASSERT_EQ(test, slf3s_test_suspend(test, ctx), 0); + n_disable = ctx->reg.n_disable; + + slf3s_test_reg_fail_enable(&ctx->reg, -EIO); + KUNIT_EXPECT_EQ(test, slf3s_test_resume(test, ctx), -EIO); + KUNIT_EXPECT_FALSE(test, ctx->reg.enabled); + + /* The sensor answers even though the driver's supply never came up. */ + slf3s_test_resume_sensor(test, ctx); + + KUNIT_EXPECT_EQ_MSG(test, slf3s_test_suspend(test, ctx), 0, + "suspend failed after a resume that could not enable the supply"); + KUNIT_EXPECT_EQ_MSG(test, ctx->reg.n_disable, n_disable, + "driver disabled a supply it never enabled"); + KUNIT_EXPECT_REG_BALANCED(test, ctx); +} + +/* + * T8: the devm cleanup must not disable a supply that is already off. + * + * An unbalanced regulator_disable() WARNs and returns before the fake's + * disable op runs, so n_disable cannot tell buggy from correct here. + * Count the WARN() instead. + */ +static void slf3s_test_unbind_after_enable_failure(struct kunit *test) +{ + struct slf3s_test_ctx *ctx = test->priv; + unsigned int n_disable; + + KUNIT_ASSERT_EQ(test, slf3s_test_suspend(test, ctx), 0); + n_disable = ctx->reg.n_disable; + + slf3s_test_reg_fail_enable(&ctx->reg, -EIO); + KUNIT_EXPECT_EQ(test, slf3s_test_resume(test, ctx), -EIO); + + kunit_warning_suppress(test) { + kunit_release_action(test, slf3s_test_unregister_client, + ctx->client); + KUNIT_EXPECT_EQ_MSG(test, KUNIT_SUPPRESSED_WARNING_COUNT(), 0, + "unbind triggered \"unbalanced disables\" for a supply that was already off"); + } + + KUNIT_EXPECT_EQ_MSG(test, ctx->reg.n_disable, n_disable, + "unbind disabled a supply that was already off"); + KUNIT_EXPECT_REG_BALANCED(test, ctx); +} + +static struct kunit_case slf3s_test_cases[] = { + KUNIT_CASE(slf3s_test_cycle_balanced), + KUNIT_CASE(slf3s_test_cycles_never_leak), + KUNIT_CASE(slf3s_test_unbind_after_failed_resume), + KUNIT_CASE(slf3s_test_probe_failure), + KUNIT_CASE(slf3s_test_resume_start_failure), + KUNIT_CASE(slf3s_test_suspend_stop_failure), + KUNIT_CASE(slf3s_test_resume_enable_failure), + KUNIT_CASE(slf3s_test_unbind_after_enable_failure), + { } +}; + +static struct kunit_suite slf3s_test_suite = { + .name = "slf3s-pm", + .init = slf3s_test_init, + .test_cases = slf3s_test_cases, +}; + +kunit_test_suite(slf3s_test_suite); + +MODULE_DESCRIPTION("KUnit tests for the Sensirion SLF3S driver"); +MODULE_LICENSE("GPL"); -- 2.55.0

