From: Birk Skyum <birk.skyum@pm.me>
To: "Anvesh Jain P" <anvesh.p@oss.qualcomm.com>,
"Sibi Sankar" <sibi.sankar@oss.qualcomm.com>,
"Hans de Goede" <hansg@kernel.org>,
"Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>
Cc: Bryan O'Donoghue <bryan.odonoghue@linaro.org>,
Guenter Roeck <linux@roeck-us.net>,
Jonathan Corbet <corbet@lwn.net>,
Shuah Khan <skhan@linuxfoundation.org>,
Randy Dunlap <rdunlap@infradead.org>,
Daniel J Blueman <daniel@quora.org>,
Maya Matuszczyk <maccraft123mc@gmail.com>,
linux-arm-msm@vger.kernel.org,
platform-driver-x86@vger.kernel.org, linux-hwmon@vger.kernel.org,
linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [RFC PATCH 2/2] platform: arm64: qcom-hamoa-ec: test sensor and variant handling
Date: Tue, 08 Sep 2026 01:57:11 +0000 [thread overview]
Message-ID: <20260908015650.84401-3-birk.skyum@pm.me> (raw)
In-Reply-To: <20260908015650.84401-1-birk.skyum@pm.me>
Exercise the sensor callbacks against a simulated I2C adapter. Check
little-endian RPM decoding, short transfers, malformed byte counts,
the unavailable-temperature sentinel and propagation of bus errors.
Check that the Lenovo compatible takes precedence over its reference-
board fallback, that its sensor attributes are read-only, and that its
probe and PM/remove callbacks do not issue control writes. Keep coverage
of the strict reference-board capability-response validation.
The tests use KUnit and never access a physical EC.
Signed-off-by: Birk Skyum <birk.skyum@pm.me>
---
MAINTAINERS | 1 +
drivers/platform/arm64/Kconfig | 12 +
drivers/platform/arm64/qcom-hamoa-ec-test.c | 294 ++++++++++++++++++++
drivers/platform/arm64/qcom-hamoa-ec.c | 4 +
4 files changed, 311 insertions(+)
create mode 100644 drivers/platform/arm64/qcom-hamoa-ec-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 8518a97b4..58010a3e4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22442,6 +22442,7 @@ L: linux-arm-msm@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/embedded-controller/qcom,hamoa-crd-ec.yaml
F: Documentation/hwmon/qcom-ec.rst
+F: drivers/platform/arm64/qcom-hamoa-ec-test.c
F: drivers/platform/arm64/qcom-hamoa-ec.c
QUALCOMM HEXAGON ARCHITECTURE
diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig
index 4f338c715..d940973be 100644
--- a/drivers/platform/arm64/Kconfig
+++ b/drivers/platform/arm64/Kconfig
@@ -105,4 +105,16 @@ config EC_QCOM_HAMOA
This driver supports Hamoa/Purwa/Glymur reference devices and read-only
fan speed and temperature monitoring on the Lenovo Yoga Slim 7x.
+config EC_QCOM_HAMOA_KUNIT_TEST
+ bool "Tests for the Qualcomm EC driver" if !KUNIT_ALL_TESTS
+ depends on EC_QCOM_HAMOA && KUNIT && OF
+ depends on EC_QCOM_HAMOA=m || KUNIT=y
+ default KUNIT_ALL_TESTS
+ help
+ Test the Qualcomm EC driver's sensor transactions and read-only
+ callbacks using a simulated I2C adapter. No physical EC is accessed.
+ Run these tests in a disposable test kernel, for example under QEMU.
+
+ If unsure, say N.
+
endif # ARM64_PLATFORM_DEVICES
diff --git a/drivers/platform/arm64/qcom-hamoa-ec-test.c b/drivers/platform/arm64/qcom-hamoa-ec-test.c
new file mode 100644
index 000000000..68868ef76
--- /dev/null
+++ b/drivers/platform/arm64/qcom-hamoa-ec-test.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Included by qcom-hamoa-ec.c to test its private transport and callbacks. */
+
+#include <kunit/resource.h>
+#include <kunit/test.h>
+#include <linux/of.h>
+
+struct qcom_ec_test_context {
+ struct i2c_adapter adapter;
+ struct qcom_ec ec;
+ u32 functionality;
+ int transfer_result;
+ int byte_result;
+ u8 reply[4];
+ u8 block_length;
+ unsigned int transactions;
+ unsigned int writes;
+};
+
+static int qcom_ec_test_transfer(struct i2c_adapter *adapter,
+ struct i2c_msg *messages, int count)
+{
+ struct qcom_ec_test_context *ctx = i2c_get_adapdata(adapter);
+
+ ctx->transactions++;
+ /* Only the RPM query's combined write/read transaction is allowed. */
+ if (count != 2 || messages[0].addr != 0x76 || messages[1].addr != 0x76 ||
+ messages[0].flags || messages[1].flags != I2C_M_RD ||
+ messages[0].len != 2 || messages[1].len != 3 ||
+ messages[0].buf[0] != 0x22 || messages[0].buf[1] != 1) {
+ ctx->writes++;
+ return -EPROTO;
+ }
+
+ if (ctx->transfer_result == 2)
+ memcpy(messages[1].buf, ctx->reply, messages[1].len);
+
+ return ctx->transfer_result;
+}
+
+static s32 qcom_ec_test_smbus(struct i2c_adapter *adapter, u16 address,
+ unsigned short flags, char read_write, u8 command,
+ int size, union i2c_smbus_data *data)
+{
+ struct qcom_ec_test_context *ctx = i2c_get_adapdata(adapter);
+
+ ctx->transactions++;
+ if (read_write != I2C_SMBUS_READ) {
+ ctx->writes++;
+ return -EPROTO;
+ }
+ if (address != 0x76 || flags)
+ return -EPROTO;
+
+ if (command == 0x29 && size == I2C_SMBUS_BYTE_DATA) {
+ if (ctx->byte_result < 0)
+ return ctx->byte_result;
+ data->byte = ctx->byte_result;
+ return 0;
+ }
+
+ if ((command == 0x0e || command == 0x42) && size == I2C_SMBUS_I2C_BLOCK_DATA) {
+ data->block[0] = ctx->block_length;
+ memcpy(&data->block[1], ctx->reply, ctx->block_length);
+ return 0;
+ }
+
+ return -EPROTO;
+}
+
+static u32 qcom_ec_test_functionality(struct i2c_adapter *adapter)
+{
+ struct qcom_ec_test_context *ctx = i2c_get_adapdata(adapter);
+
+ return ctx->functionality;
+}
+
+static const struct i2c_algorithm qcom_ec_test_algorithm = {
+ .master_xfer = qcom_ec_test_transfer,
+ .smbus_xfer = qcom_ec_test_smbus,
+ .functionality = qcom_ec_test_functionality,
+};
+
+static void qcom_ec_test_delete_adapter(void *data)
+{
+ i2c_del_adapter(data);
+}
+
+static void qcom_ec_test_unregister_client(void *data)
+{
+ i2c_unregister_device(data);
+}
+
+static int qcom_ec_test_init(struct kunit *test)
+{
+ struct qcom_ec_test_context *ctx;
+ int ret;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+ test->priv = ctx;
+ ctx->functionality = I2C_FUNC_I2C | I2C_FUNC_SMBUS_READ_BYTE_DATA |
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK;
+ ctx->transfer_result = 2;
+ ctx->reply[0] = 2;
+ ctx->byte_result = 36;
+ ctx->adapter.owner = THIS_MODULE;
+ ctx->adapter.algo = &qcom_ec_test_algorithm;
+ strscpy(ctx->adapter.name, "qcom-ec-kunit", sizeof(ctx->adapter.name));
+ i2c_set_adapdata(&ctx->adapter, ctx);
+ ret = i2c_add_adapter(&ctx->adapter);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ret = kunit_add_action_or_reset(test, qcom_ec_test_delete_adapter, &ctx->adapter);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ctx->ec.client = i2c_new_dummy_device(&ctx->adapter, 0x76);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->ec.client);
+ ret = kunit_add_action_or_reset(test, qcom_ec_test_unregister_client, ctx->ec.client);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ctx->ec.variant = &qcom_ec_slim7x;
+ i2c_set_clientdata(ctx->ec.client, &ctx->ec);
+
+ return 0;
+}
+
+static void qcom_ec_rpm_test(struct kunit *test)
+{
+ struct qcom_ec_test_context *ctx = test->priv;
+ static const struct {
+ int transfer_result;
+ u8 count;
+ int error;
+ } failures[] = {
+ { 0, 2, -EIO },
+ { 1, 2, -EIO },
+ { -EREMOTEIO, 2, -EREMOTEIO },
+ { -ETIMEDOUT, 2, -ETIMEDOUT },
+ { 2, 0, -EPROTO },
+ { 2, 1, -EPROTO },
+ { 2, 3, -EPROTO },
+ { 2, 0xff, -EPROTO },
+ };
+ long value = -1;
+ int i;
+
+ KUNIT_EXPECT_EQ(test, qcom_ec_read_fan_rpm(&ctx->ec, &value), 0);
+ KUNIT_EXPECT_EQ(test, value, 0);
+ ctx->reply[1] = 0xef;
+ ctx->reply[2] = 0xbe;
+ KUNIT_EXPECT_EQ(test, qcom_ec_read_fan_rpm(&ctx->ec, &value), 0);
+ KUNIT_EXPECT_EQ(test, value, 0xbeef);
+
+ for (i = 0; i < ARRAY_SIZE(failures); i++) {
+ value = -1;
+ ctx->transfer_result = failures[i].transfer_result;
+ ctx->reply[0] = failures[i].count;
+ KUNIT_EXPECT_EQ_MSG(test, qcom_ec_read_fan_rpm(&ctx->ec, &value),
+ failures[i].error, "case %d", i);
+ KUNIT_EXPECT_EQ(test, value, -1);
+ }
+ KUNIT_EXPECT_EQ(test, ctx->writes, 0);
+}
+
+static void qcom_ec_temperature_test(struct kunit *test)
+{
+ struct qcom_ec_test_context *ctx = test->priv;
+ static const int temperatures[] = { 0, 36, 254, 255, -EREMOTEIO };
+ long value;
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(temperatures); i++) {
+ value = -1;
+ ctx->byte_result = temperatures[i];
+ ret = qcom_ec_hwmon_read(&ctx->ec.client->dev, hwmon_temp,
+ hwmon_temp_input, 0, &value);
+ if (temperatures[i] < 0) {
+ KUNIT_EXPECT_EQ(test, ret, temperatures[i]);
+ KUNIT_EXPECT_EQ(test, value, -1);
+ } else if (temperatures[i] == 255) {
+ KUNIT_EXPECT_EQ(test, ret, -ENODATA);
+ KUNIT_EXPECT_EQ(test, value, -1);
+ } else {
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, value, temperatures[i] * 1000);
+ }
+ }
+ KUNIT_EXPECT_EQ(test, ctx->writes, 0);
+}
+
+static void qcom_ec_read_only_test(struct kunit *test)
+{
+ struct qcom_ec_test_context *ctx = test->priv;
+ struct device *dev = &ctx->ec.client->dev;
+ long value = -1;
+
+ KUNIT_EXPECT_EQ(test, qcom_ec_hwmon_is_visible(NULL, hwmon_fan, hwmon_fan_input, 0),
+ 0444);
+ KUNIT_EXPECT_EQ(test, qcom_ec_hwmon_is_visible(NULL, hwmon_temp, hwmon_temp_input, 0),
+ 0444);
+ KUNIT_EXPECT_EQ(test, qcom_ec_hwmon_is_visible(NULL, hwmon_fan, hwmon_fan_input, 1), 0);
+ KUNIT_EXPECT_EQ(test, qcom_ec_hwmon_is_visible(NULL, hwmon_pwm, hwmon_pwm_input, 0), 0);
+ KUNIT_EXPECT_EQ(test, qcom_ec_hwmon_read(dev, hwmon_fan, hwmon_fan_input, 1, &value),
+ -EOPNOTSUPP);
+ KUNIT_EXPECT_EQ(test, qcom_ec_hwmon_read(dev, hwmon_pwm, hwmon_pwm_input, 0, &value),
+ -EOPNOTSUPP);
+ KUNIT_EXPECT_EQ(test, value, -1);
+ KUNIT_EXPECT_EQ(test, qcom_ec_suspend(dev), 0);
+ KUNIT_EXPECT_EQ(test, qcom_ec_resume(dev), 0);
+ qcom_ec_remove(ctx->ec.client);
+ KUNIT_EXPECT_EQ(test, ctx->transactions, 0);
+}
+
+static void qcom_ec_probe_transport_test(struct kunit *test)
+{
+ struct qcom_ec_test_context *ctx = test->priv;
+
+ ctx->functionality = I2C_FUNC_I2C;
+ KUNIT_EXPECT_EQ(test, qcom_ec_hwmon_probe(&ctx->ec), -EOPNOTSUPP);
+ ctx->functionality = I2C_FUNC_SMBUS_READ_BYTE_DATA;
+ KUNIT_EXPECT_EQ(test, qcom_ec_hwmon_probe(&ctx->ec), -EOPNOTSUPP);
+ KUNIT_EXPECT_EQ(test, ctx->transactions, 0);
+}
+
+static void qcom_ec_probe_sensor_test(struct kunit *test)
+{
+ struct qcom_ec_test_context *ctx = test->priv;
+
+ KUNIT_EXPECT_EQ(test, qcom_ec_hwmon_probe(&ctx->ec), 0);
+ KUNIT_EXPECT_EQ(test, ctx->transactions, 1);
+ KUNIT_EXPECT_EQ(test, ctx->writes, 0);
+ ctx->reply[0] = 0;
+ KUNIT_EXPECT_EQ(test, qcom_ec_hwmon_probe(&ctx->ec), -EPROTO);
+ KUNIT_EXPECT_EQ(test, ctx->writes, 0);
+}
+
+static void qcom_ec_reference_validation_test(struct kunit *test)
+{
+ struct qcom_ec_test_context *ctx = test->priv;
+ u8 response[3];
+
+ ctx->block_length = 3;
+ KUNIT_EXPECT_EQ(test, qcom_ec_read(&ctx->ec, EC_THERMAL_CAP_CMD, 3, response), 0);
+ ctx->block_length = 2;
+ KUNIT_EXPECT_EQ(test, qcom_ec_read(&ctx->ec, EC_THERMAL_CAP_CMD, 3, response), -EIO);
+ ctx->block_length = 0;
+ KUNIT_EXPECT_EQ(test, qcom_ec_read(&ctx->ec, EC_THERMAL_CAP_CMD, 3, response),
+ -EOPNOTSUPP);
+ ctx->block_length = 3;
+ ctx->reply[0] = 0;
+ KUNIT_EXPECT_EQ(test, qcom_ec_read(&ctx->ec, EC_THERMAL_CAP_CMD, 3, response), -EINVAL);
+ ctx->reply[0] = 1;
+ KUNIT_EXPECT_EQ(test, qcom_ec_read(&ctx->ec, EC_THERMAL_CAP_CMD, 3, response), -EINVAL);
+ KUNIT_EXPECT_EQ(test, ctx->writes, 0);
+}
+
+static void qcom_ec_variant_test(struct kunit *test)
+{
+ static const char compatible[] = "lenovo,yoga-slim7x-ec\0qcom,hamoa-crd-ec";
+ struct property property = {
+ .name = "compatible",
+ .length = sizeof(compatible),
+ .value = (void *)compatible,
+ };
+ struct device_node node = { .properties = &property };
+ const struct of_device_id *match;
+
+ match = of_match_node(qcom_ec_of_match, &node);
+ KUNIT_ASSERT_NOT_NULL(test, match);
+ KUNIT_EXPECT_PTR_EQ(test, match->data, &qcom_ec_slim7x);
+ property.value = "qcom,hamoa-crd-ec";
+ property.length = sizeof("qcom,hamoa-crd-ec");
+ match = of_match_node(qcom_ec_of_match, &node);
+ KUNIT_ASSERT_NOT_NULL(test, match);
+ KUNIT_EXPECT_PTR_EQ(test, match->data, &qcom_ec_reference);
+}
+
+static struct kunit_case qcom_ec_test_cases[] = {
+ KUNIT_CASE(qcom_ec_rpm_test),
+ KUNIT_CASE(qcom_ec_temperature_test),
+ KUNIT_CASE(qcom_ec_read_only_test),
+ KUNIT_CASE(qcom_ec_probe_transport_test),
+ KUNIT_CASE(qcom_ec_probe_sensor_test),
+ KUNIT_CASE(qcom_ec_reference_validation_test),
+ KUNIT_CASE(qcom_ec_variant_test),
+ {}
+};
+
+static struct kunit_suite qcom_ec_test_suite = {
+ .name = "qcom-hamoa-ec",
+ .init = qcom_ec_test_init,
+ .test_cases = qcom_ec_test_cases,
+};
+
+kunit_test_suite(qcom_ec_test_suite);
diff --git a/drivers/platform/arm64/qcom-hamoa-ec.c b/drivers/platform/arm64/qcom-hamoa-ec.c
index 3a4ca8a0d..043f6ab7e 100644
--- a/drivers/platform/arm64/qcom-hamoa-ec.c
+++ b/drivers/platform/arm64/qcom-hamoa-ec.c
@@ -596,3 +596,7 @@ module_i2c_driver(qcom_ec_i2c_driver);
MODULE_DESCRIPTION("QCOM Hamoa Embedded Controller");
MODULE_LICENSE("GPL");
+
+#if IS_ENABLED(CONFIG_EC_QCOM_HAMOA_KUNIT_TEST)
+#include "qcom-hamoa-ec-test.c"
+#endif
--
2.53.0
next prev parent reply other threads:[~2026-09-08 1:57 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-08 1:57 [RFC PATCH 0/2] Monitor Yoga Slim 7x EC sensors without taking over cooling Birk Skyum
2026-09-08 1:57 ` [RFC PATCH 1/2] platform: arm64: qcom-hamoa-ec: monitor Slim 7x legacy sensors Birk Skyum
2026-09-08 2:03 ` sashiko-bot
2026-09-08 5:25 ` Guenter Roeck
2026-09-08 1:57 ` Birk Skyum [this message]
2026-09-08 2:07 ` [RFC PATCH 2/2] platform: arm64: qcom-hamoa-ec: test sensor and variant handling sashiko-bot
2026-09-08 2:18 ` Birk Skyum
2026-09-08 5:26 ` Guenter Roeck
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260908015650.84401-3-birk.skyum@pm.me \
--to=birk.skyum@pm.me \
--cc=anvesh.p@oss.qualcomm.com \
--cc=bryan.odonoghue@linaro.org \
--cc=corbet@lwn.net \
--cc=daniel@quora.org \
--cc=hansg@kernel.org \
--cc=ilpo.jarvinen@linux.intel.com \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-hwmon@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=maccraft123mc@gmail.com \
--cc=platform-driver-x86@vger.kernel.org \
--cc=rdunlap@infradead.org \
--cc=sibi.sankar@oss.qualcomm.com \
--cc=skhan@linuxfoundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.