[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250520082435.2255639-5-tzungbi@kernel.org>
Date: Tue, 20 May 2025 08:24:31 +0000
From: Tzung-Bi Shih <tzungbi@...nel.org>
To: bleung@...omium.org,
brendan.higgins@...ux.dev,
davidgow@...gle.com
Cc: tzungbi@...nel.org,
rmoar@...gle.com,
rostedt@...dmis.org,
mhiramat@...nel.org,
naveen@...nel.org,
anil.s.keshavamurthy@...el.com,
davem@...emloft.net,
chrome-platform@...ts.linux.dev,
linux-kselftest@...r.kernel.org,
kunit-dev@...glegroups.com,
linux-trace-kernel@...r.kernel.org,
linux-kernel@...r.kernel.org
Subject: [RFC PATCH 4/7] platform/chrome: kunit: cros_ec_spi: Add tests with ftrace stub
For running the tests:
$ ./tools/testing/kunit/kunit.py run \
--arch=x86_64 \
--kconfig_add CONFIG_FTRACE=y \
--kconfig_add CONFIG_FUNCTION_TRACER=y \
--kconfig_add CONFIG_MODULES=y \
--kconfig_add CONFIG_DEBUG_KERNEL=y \
--kconfig_add CONFIG_KALLSYMS_ALL=y \
--kconfig_add CONFIG_LIVEPATCH=y \
--kconfig_add CONFIG_KUNIT_FTRACE_STUBS=y \
--kconfig_add CONFIG_CHROME_PLATFORMS=y \
--kconfig_add CONFIG_CROS_EC=y \
--kconfig_add CONFIG_SPI=y \
--kconfig_add CONFIG_CROS_EC_SPI=y \
--kconfig_add CONFIG_CROS_KUNIT_EC_SPI_TEST=y \
cros_ec_spi*
Signed-off-by: Tzung-Bi Shih <tzungbi@...nel.org>
---
drivers/platform/chrome/Kconfig | 9 +
drivers/platform/chrome/Makefile | 1 +
drivers/platform/chrome/cros_ec_spi_test.c | 361 +++++++++++++++++++++
3 files changed, 371 insertions(+)
create mode 100644 drivers/platform/chrome/cros_ec_spi_test.c
diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig
index bf10c0625be8..aa13e871a31f 100644
--- a/drivers/platform/chrome/Kconfig
+++ b/drivers/platform/chrome/Kconfig
@@ -335,4 +335,13 @@ config CROS_KUNIT_EC_I2C_TEST
help
Kunit tests for ChromeOS EC over I2C.
+config CROS_KUNIT_EC_SPI_TEST
+ tristate "Kunit tests for ChromeOS EC over SPI" if !KUNIT_ALL_TESTS
+ depends on KUNIT && CROS_EC
+ default KUNIT_ALL_TESTS
+ depends on KUNIT_FTRACE_STUBS
+ depends on CROS_EC_SPI
+ help
+ Kunit tests for ChromeOS EC over SPI.
+
endif # CHROMEOS_PLATFORMS
diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile
index 9808f25aea38..74649b8f21b3 100644
--- a/drivers/platform/chrome/Makefile
+++ b/drivers/platform/chrome/Makefile
@@ -45,3 +45,4 @@ obj-$(CONFIG_WILCO_EC) += wilco_ec/
obj-$(CONFIG_CROS_KUNIT_EC_PROTO_TEST) += cros_kunit_proto_test.o
cros_kunit_proto_test-objs := cros_ec_proto_test_util.o cros_ec_proto_test.o
obj-$(CONFIG_CROS_KUNIT_EC_I2C_TEST) += cros_ec_i2c_test.o
+obj-$(CONFIG_CROS_KUNIT_EC_SPI_TEST) += cros_ec_spi_test.o
diff --git a/drivers/platform/chrome/cros_ec_spi_test.c b/drivers/platform/chrome/cros_ec_spi_test.c
new file mode 100644
index 000000000000..2a021569a726
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_spi_test.c
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kunit tests for ChromeOS Embedded Controller SPI interface.
+ */
+#include <kunit/test.h>
+#include <kunit/ftrace_stub.h>
+
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
+#include <linux/sched.h>
+#include <linux/spi/spi.h>
+
+#include "cros_ec.h"
+
+#define BUFSIZE 128
+#define SPI_BUS_NUM 0x6
+
+struct fake_xt {
+ u8 *buf;
+ size_t len;
+
+ struct list_head list;
+};
+
+struct cros_ec_spi_test_priv {
+ struct class *fake_class;
+ struct device dev;
+ struct spi_controller *fake_ctlr;
+ struct spi_device *fake_spi_device;
+
+ int fake_cros_ec_register_called;
+ struct cros_ec_device *ec_dev;
+
+ int fake_cros_ec_unregister_called;
+
+ struct list_head tx_head;
+ struct list_head rx_head;
+};
+
+static struct fake_xt *queue_fake_xt(struct kunit *test, struct list_head *head, size_t len)
+{
+ struct fake_xt *xt = kunit_kmalloc(test, sizeof(*xt), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, xt);
+
+ xt->buf = kunit_kzalloc(test, len, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, xt->buf);
+ xt->len = len;
+ list_add_tail(&xt->list, head);
+ return xt;
+}
+
+static struct fake_xt *dequeue_fake_xt(struct list_head *head)
+{
+ struct fake_xt *xt = list_first_entry(head, struct fake_xt, list);
+ list_del(&xt->list);
+ return xt;
+}
+
+static int fake_cros_ec_register(struct cros_ec_device *ec_dev)
+{
+ struct kunit *test = current->kunit_test;
+ struct cros_ec_spi_test_priv *priv = test->priv;
+
+ priv->fake_cros_ec_register_called += 1;
+
+ priv->ec_dev = ec_dev;
+ priv->ec_dev->din_size = BUFSIZE;
+ priv->ec_dev->din = kunit_kmalloc(test, priv->ec_dev->din_size, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, priv->ec_dev->din);
+ priv->ec_dev->dout_size = BUFSIZE;
+ priv->ec_dev->dout = kunit_kmalloc(test, priv->ec_dev->dout_size, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, priv->ec_dev->dout);
+ return 0;
+}
+
+static void fake_cros_ec_unregister(struct cros_ec_device *ec_dev)
+{
+ struct kunit *test = current->kunit_test;
+ struct cros_ec_spi_test_priv *priv = test->priv;
+
+ priv->fake_cros_ec_unregister_called += 1;
+}
+
+static int fake_spi_transfer_one_message(struct spi_controller *ctlr, struct spi_message *msg)
+{
+ /* Note: the function's context is another kernel thread. */
+ struct kunit *test = spi_controller_get_devdata(ctlr);
+ struct cros_ec_spi_test_priv *priv = test->priv;
+ struct spi_transfer *t;
+
+ list_for_each_entry(t, &msg->transfers, transfer_list) {
+ if (t->tx_buf) {
+ struct fake_xt *tx = queue_fake_xt(test, &priv->tx_head, t->len);
+ memcpy(tx->buf, t->tx_buf, t->len);
+ }
+
+ if (t->rx_buf && !list_empty(&priv->rx_head)) {
+ struct fake_xt *rx = dequeue_fake_xt(&priv->rx_head);
+ memcpy(t->rx_buf, rx->buf, min(t->len, rx->len));
+ }
+ }
+
+ msg->status = 0;
+ spi_finalize_current_message(ctlr);
+ return 0;
+}
+
+static int find_target_driver(struct device_driver *drv, void *data)
+{
+ struct device_driver **pdrv = data;
+
+ if (strcmp(drv->name, "cros-ec-spi") == 0)
+ *pdrv = drv;
+ return 0;
+}
+
+static int cros_ec_spi_test_init(struct kunit *test)
+{
+ struct cros_ec_spi_test_priv *priv;
+ struct spi_board_info board_info = {};
+ int ret;
+ struct device_driver *drv;
+ enum probe_type orig;
+
+ kunit_activate_ftrace_stub(test, cros_ec_register, fake_cros_ec_register);
+ kunit_activate_ftrace_stub(test, cros_ec_unregister, fake_cros_ec_unregister);
+
+ sized_strscpy(board_info.modalias, "cros-ec-spi", SPI_NAME_SIZE);
+
+ priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, priv);
+ test->priv = priv;
+
+ priv->fake_class = class_create("fake-class");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->fake_class);
+
+ priv->dev.class = priv->fake_class;
+ priv->dev.parent = NULL;
+ dev_set_name(&priv->dev, "cros-ec-spi-test-dev");
+ ret = device_register(&priv->dev);
+ if (ret)
+ put_device(&priv->dev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ priv->fake_ctlr = spi_alloc_host(&priv->dev, 0);
+ KUNIT_ASSERT_NOT_NULL(test, priv->fake_ctlr);
+ spi_controller_set_devdata(priv->fake_ctlr, test);
+
+ priv->fake_ctlr->transfer_one_message = fake_spi_transfer_one_message;
+ priv->fake_ctlr->bus_num = SPI_BUS_NUM;
+ ret = spi_register_controller(priv->fake_ctlr);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ /*
+ * Force to use synchronous probe so that the upcoming SPI device gets
+ * probed correctly and synchronously.
+ */
+ ret = bus_for_each_drv(&spi_bus_type, NULL, &drv, find_target_driver);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ KUNIT_ASSERT_NOT_NULL(test, drv);
+ orig = drv->probe_type;
+ drv->probe_type = PROBE_FORCE_SYNCHRONOUS;
+
+ priv->fake_spi_device = spi_new_device(priv->fake_ctlr, &board_info);
+ /* Restore to original probe type. */
+ drv->probe_type = orig;
+ KUNIT_ASSERT_NOT_NULL(test, priv->fake_spi_device);
+
+ KUNIT_EXPECT_EQ(test, priv->fake_cros_ec_register_called, 1);
+ KUNIT_ASSERT_NOT_NULL(test, priv->ec_dev);
+ KUNIT_EXPECT_EQ(test, priv->fake_cros_ec_unregister_called, 0);
+
+ INIT_LIST_HEAD(&priv->tx_head);
+ INIT_LIST_HEAD(&priv->rx_head);
+
+ return 0;
+}
+
+static void cros_ec_spi_test_exit(struct kunit *test)
+{
+ struct cros_ec_spi_test_priv *priv = test->priv;
+
+ spi_unregister_device(priv->fake_spi_device);
+ KUNIT_EXPECT_EQ(test, priv->fake_cros_ec_unregister_called, 1);
+
+ spi_unregister_controller(priv->fake_ctlr);
+ device_del(&priv->dev);
+ class_destroy(priv->fake_class);
+
+ kunit_deactivate_ftrace_stub(test, cros_ec_register);
+ kunit_deactivate_ftrace_stub(test, cros_ec_unregister);
+}
+
+static int cros_ec_spi_test_cmd_xfer_init(struct kunit *test)
+{
+ struct cros_ec_spi_test_priv *priv;
+
+ cros_ec_spi_test_init(test);
+ priv = test->priv;
+ priv->ec_dev->proto_version = 2;
+ return 0;
+}
+
+static void cros_ec_spi_test_cmd_xfer_normal(struct kunit *test)
+{
+ struct cros_ec_spi_test_priv *priv = test->priv;
+ struct cros_ec_command *msg;
+ struct fake_xt *xt;
+ int ret, i, len;
+ u8 sum;
+
+ msg = kunit_kzalloc(test, sizeof(*msg) + 2 /* max(outsize, insize) */, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, msg);
+ msg->version = 0x1000;
+ msg->command = 0x1001;
+ msg->outsize = 2;
+ msg->insize = 1;
+ msg->data[0] = 0xbe;
+ msg->data[1] = 0xef;
+
+ /*
+ * cros_ec_spi sends out the msg and tries to read further EC_MSG_TX_PROTO_BYTES +
+ * msg->outsize bytes to verify if the EC can proceed the command by looking for
+ * EC_SPI_PAST_END, EC_SPI_RX_BAD_DATA, or EC_SPI_NOT_READY. It's fine even if we
+ * prepare 0 length data.
+ */
+ queue_fake_xt(test, &priv->rx_head, 0);
+
+ len = 1 /* for EC_SPI_FRAME_START */ + EC_MSG_RX_PROTO_BYTES + msg->insize;
+ xt = queue_fake_xt(test, &priv->rx_head, len);
+ xt->buf[0] = EC_SPI_FRAME_START;
+ xt->buf[1] = 0 /* result */;
+ xt->buf[2] = msg->insize;
+ xt->buf[3] = 0xaa;
+ for (sum = 0, i = 1; i < len - 1; ++i)
+ sum += xt->buf[i];
+ xt->buf[len - 1] = sum;
+
+ ret = priv->ec_dev->cmd_xfer(priv->ec_dev, msg);
+ KUNIT_EXPECT_EQ(test, ret, 1 /* insize */);
+ KUNIT_EXPECT_EQ(test, msg->result, 0);
+ KUNIT_EXPECT_EQ(test, msg->data[0], 0xaa);
+
+ KUNIT_EXPECT_FALSE(test, list_empty(&priv->tx_head));
+ xt = dequeue_fake_xt(&priv->tx_head);
+ KUNIT_EXPECT_TRUE(test, list_empty(&priv->tx_head));
+ KUNIT_EXPECT_NOT_NULL(test, xt);
+ KUNIT_EXPECT_NOT_NULL(test, xt->buf);
+ KUNIT_EXPECT_EQ(test, xt->len, EC_MSG_TX_PROTO_BYTES + 2 /* outsize */);
+ KUNIT_EXPECT_EQ(test, xt->buf[0], (u8)(EC_CMD_VERSION0 + 0x1000));
+ KUNIT_EXPECT_EQ(test, xt->buf[1], (u8)0x1001);
+ KUNIT_EXPECT_EQ(test, xt->buf[2], 2 /* outsize */);
+ KUNIT_EXPECT_EQ(test, xt->buf[3], 0xbe);
+ KUNIT_EXPECT_EQ(test, xt->buf[4], 0xef);
+ for (sum = 0, i = 0; i < EC_MSG_TX_HEADER_BYTES + 2 /* outsize */; ++i)
+ sum += xt->buf[i];
+ KUNIT_EXPECT_EQ(test, xt->buf[EC_MSG_TX_HEADER_BYTES + 2 /* outsize */], sum);
+}
+
+static struct kunit_case cros_ec_spi_test_cmd_xfer_cases[] = {
+ KUNIT_CASE(cros_ec_spi_test_cmd_xfer_normal),
+ {}
+};
+
+static struct kunit_suite cros_ec_spi_test_cmd_xfer_suite = {
+ .name = "cros_ec_spi_test_cmd_xfer_suite",
+ .init = cros_ec_spi_test_cmd_xfer_init,
+ .exit = cros_ec_spi_test_exit,
+ .test_cases = cros_ec_spi_test_cmd_xfer_cases,
+};
+
+static int cros_ec_spi_test_pkt_xfer_init(struct kunit *test)
+{
+ struct cros_ec_spi_test_priv *priv;
+
+ cros_ec_spi_test_init(test);
+ priv = test->priv;
+ priv->ec_dev->proto_version = 3;
+ return 0;
+}
+
+static void cros_ec_spi_test_pkt_xfer_normal(struct kunit *test)
+{
+ struct cros_ec_spi_test_priv *priv = test->priv;
+ struct cros_ec_command *msg;
+ struct fake_xt *xt;
+ struct ec_host_request *request;
+ struct ec_host_response *response;
+ int ret, i, len;
+ u8 sum;
+
+ msg = kunit_kzalloc(test, sizeof(*msg) + 2 /* max(outsize, insize) */, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, msg);
+ msg->version = 0x1000;
+ msg->command = 0x1001;
+ msg->outsize = 2;
+ msg->insize = 1;
+ msg->data[0] = 0xbe;
+ msg->data[1] = 0xef;
+
+ /*
+ * cros_ec_spi sends out the msg and tries to read further EC_MSG_TX_PROTO_BYTES +
+ * msg->outsize bytes to verify if the EC can proceed the command by looking for
+ * EC_SPI_PAST_END, EC_SPI_RX_BAD_DATA, or EC_SPI_NOT_READY. It's fine even if we
+ * prepare 0 length data.
+ */
+ queue_fake_xt(test, &priv->rx_head, 0);
+
+ len = 1 /* for EC_SPI_FRAME_START */ + sizeof(*response) + msg->insize;
+ xt = queue_fake_xt(test, &priv->rx_head, len);
+ xt->buf[0] = EC_SPI_FRAME_START;
+ response = (struct ec_host_response *)&xt->buf[1];
+ response->struct_version = EC_HOST_RESPONSE_VERSION;
+ response->result = 0;
+ response->data_len = msg->insize;
+ xt->buf[sizeof(*response) + msg->insize] = 0xaa;
+ for (sum = 0, i = 1; i < len; ++i)
+ sum += xt->buf[i];
+ response->checksum = -sum;
+
+ ret = priv->ec_dev->pkt_xfer(priv->ec_dev, msg);
+ KUNIT_EXPECT_EQ(test, ret, 1 /* insize */);
+ KUNIT_EXPECT_EQ(test, msg->result, 0);
+ KUNIT_EXPECT_EQ(test, msg->data[0], 0xaa);
+
+ KUNIT_EXPECT_FALSE(test, list_empty(&priv->tx_head));
+ xt = dequeue_fake_xt(&priv->tx_head);
+ KUNIT_EXPECT_TRUE(test, list_empty(&priv->tx_head));
+ KUNIT_EXPECT_NOT_NULL(test, xt);
+ KUNIT_EXPECT_NOT_NULL(test, xt->buf);
+ KUNIT_EXPECT_EQ(test, xt->len, sizeof(*request) + 2 /* outsize */);
+ request = (struct ec_host_request *)xt->buf;
+ KUNIT_EXPECT_EQ(test, request->struct_version, EC_HOST_REQUEST_VERSION);
+ KUNIT_EXPECT_EQ(test, request->command_version, (u8)0x1000);
+ KUNIT_EXPECT_EQ(test, request->command, 0x1001);
+ KUNIT_EXPECT_EQ(test, request->data_len, 2 /* outsize */);
+ KUNIT_EXPECT_EQ(test, xt->buf[sizeof(*request)], 0xbe);
+ KUNIT_EXPECT_EQ(test, xt->buf[sizeof(*request) + 1], 0xef);
+ for (sum = 0, i = 0; i < sizeof(*request) + 2 /* outsize */; ++i)
+ sum += xt->buf[i];
+ KUNIT_EXPECT_EQ(test, sum, 0);
+}
+
+static struct kunit_case cros_ec_spi_test_pkt_xfer_cases[] = {
+ KUNIT_CASE(cros_ec_spi_test_pkt_xfer_normal),
+ {}
+};
+
+static struct kunit_suite cros_ec_spi_test_pkt_xfer_suite = {
+ .name = "cros_ec_spi_test_pkt_xfer_suite",
+ .init = cros_ec_spi_test_pkt_xfer_init,
+ .exit = cros_ec_spi_test_exit,
+ .test_cases = cros_ec_spi_test_pkt_xfer_cases,
+};
+
+kunit_test_suites(
+ &cros_ec_spi_test_cmd_xfer_suite,
+ &cros_ec_spi_test_pkt_xfer_suite,
+);
+
+MODULE_LICENSE("GPL");
--
2.49.0.1101.gccaa498523-goog
Powered by blists - more mailing lists