[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20260113140430.207990-5-heikki.krogerus@linux.intel.com>
Date: Tue, 13 Jan 2026 15:04:30 +0100
From: Heikki Krogerus <heikki.krogerus@...ux.intel.com>
To: Wolfram Sang <wsa+renesas@...g-engineering.com>
Cc: Jeremy Kerr <jk@...econstruct.com.au>,
Matt Johnston <matt@...econstruct.com.au>,
linux-i2c@...r.kernel.org,
netdev@...r.kernel.org,
linux-kernel@...r.kernel.org
Subject: [PATCH v1 4/4] i2c: Add SMBus ARP target mode test driver
Backend that the hosts can enumerate by using the SMBus
Address Resolution Protocol.
Signed-off-by: Heikki Krogerus <heikki.krogerus@...ux.intel.com>
---
drivers/i2c/Kconfig | 6 ++
drivers/i2c/Makefile | 1 +
drivers/i2c/i2c-target-arp.c | 201 +++++++++++++++++++++++++++++++++++
3 files changed, 208 insertions(+)
create mode 100644 drivers/i2c/i2c-target-arp.c
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index c232054fddd6..4b97a66fc698 100644
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -134,6 +134,12 @@ config I2C_SLAVE_TESTUNIT
multi-master, SMBus Host Notify, etc. Please read
Documentation/i2c/slave-testunit-backend.rst for further details.
+config I2C_TARGET_ARP
+ tristate "SMBus ARP target mode test driver"
+ help
+ This backend makes Linux respond to SMBus Address Resolution Protocol
+ commands.
+
endif
config I2C_DEBUG_CORE
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index 4fdb37b94aa4..3ea292dab065 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -19,5 +19,6 @@ obj-y += algos/ busses/ muxes/
obj-$(CONFIG_I2C_STUB) += i2c-stub.o
obj-$(CONFIG_I2C_SLAVE_EEPROM) += i2c-slave-eeprom.o
obj-$(CONFIG_I2C_SLAVE_TESTUNIT) += i2c-slave-testunit.o
+obj-$(CONFIG_I2C_TARGET_ARP) += i2c-target-arp.o
ccflags-$(CONFIG_I2C_DEBUG_CORE) := -DDEBUG
diff --git a/drivers/i2c/i2c-target-arp.c b/drivers/i2c/i2c-target-arp.c
new file mode 100644
index 000000000000..458749e4681e
--- /dev/null
+++ b/drivers/i2c/i2c-target-arp.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * SMBus Address Resolution Protocol target mode test driver
+ *
+ * Copyright (C) 2026 Intel Corporation
+ */
+#include <linux/bits.h>
+#include <linux/device/devres.h>
+#include <linux/i2c.h>
+#include <linux/i2c-smbus.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/string.h>
+
+#define ARP_ADDRESS_VALID BIT(0)
+#define ARP_ADDRESS_RESOLVED BIT(1)
+
+#define I2C_SMBUS_HOST 0x08
+#define I2C_SMBUS_DEFAULT_ADDR 0x61
+
+#define ARP_CMD_PREPARE_TO_ARP 0x01
+#define ARP_CMD_RESET_DEVICE 0x02
+#define ARP_CMD_GET_UDID 0x03
+#define ARP_CMD_ASSIGN_ADDRESS 0x04
+
+struct get_udid_data {
+ struct i2c_arp_udid udid;
+ u8 target_addr;
+} __packed;
+
+struct arp_target {
+ struct i2c_client target;
+ struct i2c_client *client;
+
+ int read_count;
+ int write_count;
+ unsigned char buf[24];
+
+ struct get_udid_data data;
+ unsigned char flag;
+};
+
+static u8 i2c_arp_pec(struct arp_target *arp, bool read)
+{
+ u8 addr = I2C_SMBUS_DEFAULT_ADDR << 1;
+ u8 len = sizeof(arp->data);
+ u8 pec = 0;
+
+ pec = i2c_smbus_pec(pec, &addr, 1);
+ pec = i2c_smbus_pec(pec, arp->buf, arp->write_count);
+
+ if (read) {
+ addr |= 1;
+ pec = i2c_smbus_pec(pec, &addr, 1);
+ pec = i2c_smbus_pec(pec, &len, 1);
+ pec = i2c_smbus_pec(pec, (void *)&arp->data, len);
+ }
+
+ return pec;
+}
+
+static int i2c_target_arp_cb(struct i2c_client *target, enum i2c_slave_event event, u8 *val)
+{
+ struct arp_target *arp = container_of(target, struct arp_target, target);
+
+ switch (event) {
+ case I2C_SLAVE_READ_PROCESSED:
+ if (arp->flag)
+ break;
+
+ if (arp->read_count == sizeof(arp->data))
+ *val = i2c_arp_pec(arp, 1);
+ else
+ *val = ((u8 *)&arp->data)[arp->read_count];
+
+ arp->read_count++;
+ break;
+ case I2C_SLAVE_READ_REQUESTED:
+ if (arp->flag)
+ break;
+
+ *val = sizeof(arp->data);
+ break;
+ case I2C_SLAVE_STOP:
+ switch (arp->buf[0]) {
+ case ARP_CMD_PREPARE_TO_ARP:
+ arp->flag = 0;
+ break;
+ case ARP_CMD_ASSIGN_ADDRESS:
+ /* If the UDID matches, this address is for us. */
+ if (!memcmp(&arp->buf[2], &arp->data.udid, sizeof(arp->data.udid)))
+ arp->flag = ARP_ADDRESS_VALID | ARP_ADDRESS_RESOLVED;
+ break;
+ default:
+ break;
+ }
+
+ arp->read_count = 0;
+ arp->write_count = 0;
+ break;
+ case I2C_SLAVE_WRITE_REQUESTED:
+ arp->read_count = 0;
+ arp->write_count = 0;
+ break;
+ case I2C_SLAVE_WRITE_RECEIVED:
+ arp->buf[arp->write_count] = *val;
+ arp->write_count++;
+ break;
+ }
+
+ return 0;
+}
+
+static void i2c_notify_arp_controller(struct i2c_client *client)
+{
+ union i2c_smbus_data data = { };
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA))
+ return;
+
+ ret = i2c_smbus_xfer(client->adapter, I2C_SMBUS_HOST, client->flags,
+ I2C_SMBUS_WRITE, client->addr,
+ I2C_SMBUS_WORD_DATA, &data);
+ if (ret)
+ dev_warn(&client->dev, "Failed to Notify ARP Controller (%d)\n", ret);
+}
+
+static int i2c_target_arp_probe(struct i2c_client *client)
+{
+ struct arp_target *arp;
+ int ret;
+
+ arp = devm_kzalloc(&client->dev, sizeof(*arp), GFP_KERNEL);
+ if (!arp)
+ return -ENOMEM;
+
+ arp->client = client;
+ i2c_set_clientdata(client, arp);
+
+ arp->target = *client;
+ arp->target.addr = I2C_SMBUS_DEFAULT_ADDR;
+ arp->target.flags |= I2C_CLIENT_SLAVE;
+
+ /*
+ * The vendor and device ID are left undefined. Some hosts may not
+ * support this. If that's the case, supply values for them.
+ *
+ * arp->data.udid.vendor = 0xXXXX;
+ * arp->data.udid.device = 0xXXXX;
+ *
+ * The capabilities are also 0. That makes this a fixed address device
+ * without support for PEC.
+ */
+ arp->data.udid.version = BIT(3); /* UDID Version 1 */
+ arp->data.udid.interface = SMBUS_INTERFACE_SMBUS_V2_0;
+ arp->data.target_addr = client->addr;
+
+ /*
+ * NOTE: This target is only for the ARP. After the address has been
+ * assigned, another target should be registered for the actual device,
+ * but this test code does _not_ register it.
+ */
+ ret = i2c_slave_register(&arp->target, i2c_target_arp_cb);
+ if (ret)
+ return ret;
+
+ i2c_notify_arp_controller(client);
+
+ return 0;
+}
+
+static void i2c_target_arp_remove(struct i2c_client *client)
+{
+ struct arp_target *arp;
+
+ if (IS_ERR_OR_NULL(client))
+ return;
+
+ arp = i2c_get_clientdata(client);
+ i2c_slave_unregister(&arp->target);
+}
+
+static const struct i2c_device_id i2c_target_arp_id[] = {
+ { "target-arp" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, i2c_target_arp_id);
+
+static struct i2c_driver i2c_target_arp_driver = {
+ .driver = {
+ .name = "i2c-target-arp",
+ },
+ .probe = i2c_target_arp_probe,
+ .remove = i2c_target_arp_remove,
+ .id_table = i2c_target_arp_id,
+};
+module_i2c_driver(i2c_target_arp_driver);
+
+MODULE_DESCRIPTION("SMBus ARP target mode test driver");
+MODULE_LICENSE("GPL");
--
2.50.1
Powered by blists - more mailing lists