From nobody Sat Feb 7 17:04:42 2026 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.16]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 740D0320A0B; Wed, 21 Jan 2026 09:23:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.16 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768987417; cv=none; b=XvO+fPiXvETgVS5YBTm07Vk6QxTwQKT6hpUXvcRBMGGmMAVXESaCJu0YBcEIEapAiG4hfu/yTMTw9v/KMwxAsQZ39w06txxZ3YtbPT+utQyfmJ6q6ew9LyUcsE3tLs33dqjb8cHBWqKsh3tzeIXJWB2zP0JI8UJhZj5mfhG/L5Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768987417; c=relaxed/simple; bh=a2xq5oXriHaXP1+xN8VbdV6QdURL7RZJ3nXcXno2opo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XM5BusQvQmvqvXUqTTKS9iW3asEenZ0IxGS2OCiegeb3zp3Dsym447zq0DoVkZuZS5tBSSiHcwIRbrBxKqjG3x9SwC4RmDljyHIdyOWFpHneevNW9I+A0qOSa4bdU3ztw8cWUcxFoqu1gBqKhBO80eXO6RIpS+d4XCLby+JYhpA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.intel.com; spf=pass smtp.mailfrom=linux.intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=k0K4Dap6; arc=none smtp.client-ip=192.198.163.16 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="k0K4Dap6" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1768987414; x=1800523414; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=a2xq5oXriHaXP1+xN8VbdV6QdURL7RZJ3nXcXno2opo=; b=k0K4Dap6dhL/uKIYBz2LZiIhYKZKrfM3bf2G0Vbn8ciUgb6RJ2IR625N gQ76yKXHcTFPRy/w2Y0kIwtox+B4Je+MRMle+j0vEZdU6fgGK6khWxihY EYP94m7llvmwcHfzMbjA31VGQUhMAsB9vyXjyG6WkFUSoaCQ9cayqp2Ak l7QyAOFu7D0zpWDh/bE+McQwXFcW+kU488c+b2vRCyJbVDLLrC0nbL8dC xikHcuZ8844UZaWfblVs+gT85X8ygiy0bPW2pxxyoWMjRu9fSonZnxfpt MnBCpXvQpBgooH/T6+PZTiSfu64/qTkaBIDO0qIrxB9TZpKNDxwcqQSYF A==; X-CSE-ConnectionGUID: hlv7Rp8hSymh/P6ZGGMblA== X-CSE-MsgGUID: PeDHoiXaSO6Ltosalzbiow== X-IronPort-AV: E=McAfee;i="6800,10657,11677"; a="57773274" X-IronPort-AV: E=Sophos;i="6.21,242,1763452800"; d="scan'208";a="57773274" Received: from orviesa003.jf.intel.com ([10.64.159.143]) by fmvoesa110.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 21 Jan 2026 01:23:34 -0800 X-CSE-ConnectionGUID: t1FOR8xISFiwdaTXFEHLWw== X-CSE-MsgGUID: 73GhjiXRQpGlPlUQxSSuzg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.21,242,1763452800"; d="scan'208";a="210560984" Received: from black.igk.intel.com ([10.91.253.5]) by orviesa003.jf.intel.com with ESMTP; 21 Jan 2026 01:23:32 -0800 From: Heikki Krogerus To: Wolfram Sang Cc: Jeremy Kerr , Matt Johnston , linux-i2c@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 1/4] i2c: SMBus Address Resolution Protocol implementation for host side Date: Wed, 21 Jan 2026 10:23:25 +0100 Message-ID: <20260121092328.2308705-2-heikki.krogerus@linux.intel.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20260121092328.2308705-1-heikki.krogerus@linux.intel.com> References: <20260121092328.2308705-1-heikki.krogerus@linux.intel.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" SMBus Address Resolution Protocol (ARP) is a standard (but optional) method to detect and identify devices on SMBus. The ARP is higher level message protocol that can be fully emulated. ARP will therefore always be supported, and the ARP-capable devices will be scanned automatically for every adapter that is registered. The ARP devices are identified with the Unique Device Identifier (UDID). The UDID can be used for device matching in drivers with a new struct smbus_device_id. The function i2c_handle_smbus_host_notify() is also modified so that the hotplugged ARP devices that send Notify ARP Controller command can be detected. Link: https://www.smbus.org/specs/smbus20.pdf Signed-off-by: Heikki Krogerus --- drivers/i2c/Makefile | 2 +- drivers/i2c/i2c-core-arp.c | 334 ++++++++++++++++++++++++++++++ drivers/i2c/i2c-core-base.c | 47 ++++- drivers/i2c/i2c-core.h | 8 + include/linux/i2c-smbus.h | 67 ++++++ include/linux/i2c.h | 10 + include/linux/mod_devicetable.h | 13 ++ scripts/mod/devicetable-offsets.c | 8 + scripts/mod/file2alias.c | 24 +++ 9 files changed, 503 insertions(+), 10 deletions(-) create mode 100644 drivers/i2c/i2c-core-arp.c diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index d27de18de46f..4fdb37b94aa4 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -5,7 +5,7 @@ =20 obj-$(CONFIG_I2C_BOARDINFO) +=3D i2c-boardinfo.o obj-$(CONFIG_I2C) +=3D i2c-core.o -i2c-core-y :=3D i2c-core-base.o i2c-core-smbus.o +i2c-core-y :=3D i2c-core-base.o i2c-core-smbus.o i2c-core-arp.o i2c-core-$(CONFIG_ACPI) +=3D i2c-core-acpi.o i2c-core-$(CONFIG_I2C_SLAVE) +=3D i2c-core-slave.o i2c-core-$(CONFIG_OF) +=3D i2c-core-of.o diff --git a/drivers/i2c/i2c-core-arp.c b/drivers/i2c/i2c-core-arp.c new file mode 100644 index 000000000000..d9ea033c97fc --- /dev/null +++ b/drivers/i2c/i2c-core-arp.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SMBus Address Resolution Protocol + * + * Copyright (C) 2026 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i2c-core.h" + +#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_address; +} __packed; + +struct arp_work { + struct work_struct work; + struct i2c_adapter *adapter; + u8 target; +}; + +struct i2c_smbus_arp { + struct list_head clients; + struct mutex lock; /* ARP controller lock */ +}; + +/* SMBus Specification Appendix C. */ +static const DECLARE_BITMAP(reserved_addrs, (1 << 7)) =3D { + [BIT_WORD(0)] =3D GENMASK(12, 0) | /* SMBus v1.1 */ + BIT_MASK(0x28) | /* PMBus ZONE READ */ + BIT_MASK(0x2c) | /* Reserved */ + BIT_MASK(0x2d) | /* Reserved */ + BIT_MASK(0x55), /* PMBus ZONE WRITE */ + + [BIT_WORD(64)] =3D BIT_MASK(0x40) | /* Reserved */ + BIT_MASK(0x41) | /* Reserved */ + BIT_MASK(0x42) | /* Reserved */ + BIT_MASK(0x43) | /* Reserved */ + BIT_MASK(0x44) | /* Reserved */ + BIT_MASK(0x48) | /* Prototype Address */ + BIT_MASK(0x49) | /* Prototype Address */ + BIT_MASK(0x4a) | /* Prototype Address */ + BIT_MASK(0x4b) | /* Prototype Address */ + BIT_MASK(0x61) | /* SMBus Default Address */ + BIT_MASK(0x78) | /* 10-bit target addressing */ + BIT_MASK(0x79) | /* 10-bit target addressing */ + BIT_MASK(0x7a) | /* 10-bit target addressing */ + BIT_MASK(0x7b) | /* 10-bit target addressing */ + BIT_MASK(0x7c) | /* Reserved */ + BIT_MASK(0x7d) | /* Reserved */ + BIT_MASK(0x7e) | /* Reserved */ + BIT_MASK(0x7f) /* Reserved */ +}; + +const struct smbus_device_id *i2c_smbus_match_id(const struct i2c_client *= client, + const struct smbus_device_id *id) +{ + struct i2c_arp_udid *udid; + + if (!(id && client && client->udid)) + return NULL; + + udid =3D client->udid; + + while (id->vendor) { + if ((id->vendor =3D=3D SMBUS_ANY_ID || id->vendor =3D=3D udid->vendor) && + (id->device =3D=3D SMBUS_ANY_ID || id->device =3D=3D udid->device) && + /* The interface is split into "version" and "protocol". */ + (id->interface =3D=3D SMBUS_ANY_ID || + /* minimum version must be supported */ + (((id->interface & 0xf) <=3D (udid->interface & 0xf)) && + /* the protocol must match */ + ((id->interface & 0xf0) =3D=3D (udid->interface & 0xf0)))) && + (id->subvendor =3D=3D SMBUS_ANY_ID || id->subvendor =3D=3D udid->sub= vendor) && + (id->subdevice =3D=3D SMBUS_ANY_ID || id->subdevice =3D=3D udid->sub= device) && + (id->vendor_specific_id =3D=3D SMBUS_ANY_VENDOR_SPECIFIC_ID || + id->vendor_specific_id =3D=3D udid->vendor_specific_id)) + return id; + id++; + } + + return NULL; +} + +static int i2c_smbus_arp_get_udid(struct i2c_adapter *adapter, u8 target, + struct get_udid_data *get_udid) +{ + union i2c_smbus_data data; + u8 command; + int ret; + + if (target) + command =3D (target << 1) | 1; + else + command =3D ARP_CMD_GET_UDID; + + ret =3D i2c_smbus_xfer(adapter, I2C_SMBUS_DEFAULT_ADDR, + I2C_CLIENT_PEC, I2C_SMBUS_READ, + command, I2C_SMBUS_BLOCK_DATA, &data); + if (ret) + return ret; + + memcpy(get_udid, &data.block[1], sizeof(*get_udid)); + + return 0; +} + +static int i2c_smbus_arp_verify_address(struct i2c_adapter *adapter, + struct get_udid_data *get_udid) +{ + u8 addr_type =3D FIELD_GET(ARP_CAP_ADDRESS_TYPE_MASK, + get_udid->udid.capabilities); + u8 addr =3D get_udid->target_address; + + if (addr_type =3D=3D ARP_CAP_ADDRESS_TYPE_FIXED) + return 0; + + /* Find a free address if necessary. */ + if (addr =3D=3D 0xff || i2c_check_addr_busy(adapter, addr)) { + for_each_clear_bit(addr, reserved_addrs, 128) + if (!i2c_check_addr_busy(adapter, addr)) + break; + if (addr =3D=3D 128) + return -EBUSY; + + get_udid->target_address =3D addr; + } + + return 0; +} + +static int i2c_smbus_arp_assign_address(struct i2c_adapter *adapter, + struct get_udid_data *get_udid) +{ + union i2c_smbus_data data; + int ret; + + ret =3D i2c_smbus_arp_verify_address(adapter, get_udid); + if (ret) + return ret; + + data.block[0] =3D sizeof(*get_udid); + memcpy(&data.block[1], get_udid, data.block[0]); + + ret =3D i2c_smbus_xfer(adapter, I2C_SMBUS_DEFAULT_ADDR, + I2C_CLIENT_PEC, I2C_SMBUS_WRITE, + ARP_CMD_ASSIGN_ADDRESS, I2C_SMBUS_BLOCK_DATA, &data); + if (ret) + return -EAGAIN; + + return 0; +} + +static void i2c_smbus_arp_remove_client(void *udid) +{ + kfree(udid); +} + +static int i2c_smbus_arp_new_client(struct i2c_adapter *adapter, + struct get_udid_data *data) +{ + struct i2c_board_info info =3D { }; + struct i2c_client *client; + int ret; + + info.udid =3D kmemdup(&data->udid, sizeof(data->udid), GFP_KERNEL); + if (!info.udid) + return -ENOMEM; + + info.addr =3D data->target_address; + info.flags =3D I2C_CLIENT_HOST_NOTIFY; + + if (data->udid.capabilities & ARP_CAP_PEC_SUPPORTED) + info.flags |=3D I2C_CLIENT_PEC; + + sprintf(info.type, "%d:arp-%zu", i2c_adapter_id(adapter), + list_count_nodes(&adapter->arp->clients)); + info.dev_name =3D info.type; + + client =3D i2c_new_client_device(adapter, &info); + if (IS_ERR(client)) { + kfree(info.udid); + return PTR_ERR(client); + } + + ret =3D devm_add_action_or_reset(&client->dev, i2c_smbus_arp_remove_clien= t, info.udid); + if (ret) + return ret; + + list_add_tail(&client->detected, &adapter->arp->clients); + + return 0; +} + +static void i2c_smbus_arp_work(struct work_struct *work) +{ + struct arp_work *awork =3D container_of(work, struct arp_work, work); + struct i2c_adapter *adapter =3D awork->adapter; + u8 target =3D awork->target; + struct get_udid_data data; + int ret; + + mutex_lock(&adapter->arp->lock); + + do { + if (i2c_smbus_arp_get_udid(adapter, target, &data)) + break; + + ret =3D i2c_smbus_arp_assign_address(adapter, &data); + if (ret =3D=3D -EAGAIN) + continue; + if (ret < 0) { + dev_warn(&adapter->dev, "out of addresses\n"); + break; + } + + if (i2c_smbus_arp_new_client(adapter, &data)) + break; + } while (!target); + + mutex_unlock(&adapter->arp->lock); + kfree(awork); +} + +/** + * i2c_smbus_arp_detect - Schedule detection and registration of ARP devic= es + * @adapter: ARP Controller + * @target_address: Address for directed ARP commands + * + * Registers ARP-capable devices attached to @adapter. If @target_address = is + * supplied, directed Get UDID command will be used. Otherwise, if + * @target_address is 0, the general Get UDID command is used until there = are no + * more responses. + * + * Returns 0 on success or errno. + */ +int i2c_smbus_arp_detect(struct i2c_adapter *adapter, u8 target_address) +{ + struct arp_work *awork; + + if (!adapter->arp) + return -ENXIO; + + awork =3D kzalloc(sizeof(*awork), GFP_KERNEL); + if (!awork) + return -ENOMEM; + + INIT_WORK(&awork->work, i2c_smbus_arp_work); + awork->target =3D target_address; + awork->adapter =3D adapter; + + queue_work(system_long_wq, &awork->work); + + return 0; +} + +/** + * i2c_smbus_arp_probe - Declare adapter as ARP controller + * @adapter: ARP Controller + * + * Declare @adapter as the ARP controller with the Prepare to ARP command,= and + * then detect all available ARP devices with the general Get UDID command. + * + * Returns 0 on success or errno. + */ +int i2c_smbus_arp_probe(struct i2c_adapter *adapter) +{ + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBU= S_BLOCK_DATA)) + return 0; + + adapter->arp =3D devm_kzalloc(&adapter->dev, sizeof(*adapter->arp), GFP_K= ERNEL); + if (!adapter->arp) + return -ENOMEM; + + mutex_init(&adapter->arp->lock); + INIT_LIST_HEAD(&adapter->arp->clients); + + /* Broadcast "Prepare to ARP" command. */ + ret =3D i2c_smbus_xfer(adapter, I2C_SMBUS_DEFAULT_ADDR, I2C_CLIENT_PEC, + I2C_SMBUS_WRITE, ARP_CMD_PREPARE_TO_ARP, + I2C_SMBUS_BYTE, NULL); + if (ret) + return 0; + + return i2c_smbus_arp_detect(adapter, 0); +} + +/** + * i2c_smbus_arp_remove - Unregister all ARP devices + * @adapter: ARP Controller + * + * Unregister all ARP devices attached to @adapter. + */ +void i2c_smbus_arp_remove(struct i2c_adapter *adapter) +{ + struct i2c_client *client, *next; + + if (!adapter->arp) + return; + + mutex_lock(&adapter->arp->lock); + + list_for_each_entry_safe(client, next, &adapter->arp->clients, detected) { + list_del(&client->detected); + i2c_unregister_device(client); + } + + mutex_unlock(&adapter->arp->lock); +} diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index f0fb0cfd56e0..6e96ac186921 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -120,19 +120,23 @@ EXPORT_SYMBOL_GPL(i2c_match_id); const void *i2c_get_match_data(const struct i2c_client *client) { struct i2c_driver *driver =3D to_i2c_driver(client->dev.driver); + const struct smbus_device_id *smbus_match; const struct i2c_device_id *match; const void *data; =20 data =3D device_get_match_data(&client->dev); - if (!data) { - match =3D i2c_match_id(driver->id_table, client); - if (!match) - return NULL; + if (data) + return data; =20 - data =3D (const void *)match->driver_data; - } + smbus_match =3D i2c_smbus_match_id(client, driver->smbus_id_table); + if (smbus_match) + return (const void *)smbus_match->driver_data; + + match =3D i2c_match_id(driver->id_table, client); + if (match) + return (const void *)match->driver_data; =20 - return data; + return NULL; } EXPORT_SYMBOL(i2c_get_match_data); =20 @@ -152,6 +156,10 @@ static int i2c_device_match(struct device *dev, const = struct device_driver *drv) =20 driver =3D to_i2c_driver(drv); =20 + /* SMBus Unique Device Identifier match */ + if (i2c_smbus_match_id(client, driver->smbus_id_table)) + return 1; + /* Finally an I2C match */ if (i2c_match_id(driver->id_table, client)) return 1; @@ -172,6 +180,13 @@ static int i2c_device_uevent(const struct device *dev,= struct kobj_uevent_env *e if (rc !=3D -ENODEV) return rc; =20 + if (client->udid) + return add_uevent_var(env, "MODALIAS=3Dsmbus:v%04xd%04xi%04xsv%04xsd%04x= vsi%08x", + client->udid->vendor, client->udid->device, + client->udid->interface, + client->udid->subvendor, client->udid->subdevice, + client->udid->vendor_specific_id); + return add_uevent_var(env, "MODALIAS=3D%s%s", I2C_MODULE_PREFIX, client->= name); } =20 @@ -684,6 +699,13 @@ modalias_show(struct device *dev, struct device_attrib= ute *attr, char *buf) if (len !=3D -ENODEV) return len; =20 + if (client->udid) + return sysfs_emit(buf, "smbus:v%04xd%04xi%04xsv%04xsd%04xvsi%08x\n", + client->udid->vendor, client->udid->device, + client->udid->interface, + client->udid->subvendor, client->udid->subdevice, + client->udid->vendor_specific_id); + return sprintf(buf, "%s%s\n", I2C_MODULE_PREFIX, client->name); } static DEVICE_ATTR_RO(modalias); @@ -822,7 +844,7 @@ static int i2c_check_mux_children(struct device *dev, v= oid *addrp) return result; } =20 -static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr) +int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr) { struct i2c_adapter *parent =3D i2c_parent_is_i2c_adapter(adapter); int result =3D 0; @@ -973,6 +995,7 @@ i2c_new_client_device(struct i2c_adapter *adap, struct = i2c_board_info const *inf client->dev.platform_data =3D info->platform_data; client->flags =3D info->flags; client->addr =3D info->addr; + client->udid =3D info->udid; =20 client->init_irq =3D info->irq; if (!client->init_irq) @@ -1508,7 +1531,7 @@ int i2c_handle_smbus_host_notify(struct i2c_adapter *= adap, unsigned short addr) =20 irq =3D irq_find_mapping(adap->host_notify_domain, addr); if (irq <=3D 0) - return -ENXIO; + return i2c_smbus_arp_detect(adap, addr); =20 generic_handle_irq_safe(irq); =20 @@ -1602,6 +1625,11 @@ static int i2c_register_adapter(struct i2c_adapter *= adap) bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); mutex_unlock(&core_lock); =20 + /* Detect ARP clients. */ + res =3D i2c_smbus_arp_probe(adap); + if (res) + dev_err(&adap->dev, "ARP device registration failed\n"); + return 0; =20 out_reg: @@ -1766,6 +1794,7 @@ void i2c_del_adapter(struct i2c_adapter *adap) return; } =20 + i2c_smbus_arp_remove(adap); i2c_acpi_remove_space_handler(adap); /* Tell drivers about this removal */ mutex_lock(&core_lock); diff --git a/drivers/i2c/i2c-core.h b/drivers/i2c/i2c-core.h index 4797ba88331c..c8f7d8e49621 100644 --- a/drivers/i2c/i2c-core.h +++ b/drivers/i2c/i2c-core.h @@ -60,6 +60,8 @@ static inline int __i2c_check_suspended(struct i2c_adapte= r *adap) return 0; } =20 +int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr); + #ifdef CONFIG_ACPI void i2c_acpi_register_devices(struct i2c_adapter *adap); =20 @@ -106,3 +108,9 @@ static inline int i2c_setup_smbus_alert(struct i2c_adap= ter *adap) return 0; } #endif + +const struct smbus_device_id *i2c_smbus_match_id(const struct i2c_client *= client, + const struct smbus_device_id *id); +int i2c_smbus_arp_detect(struct i2c_adapter *adapter, u8 target_address); +int i2c_smbus_arp_probe(struct i2c_adapter *adapter); +void i2c_smbus_arp_remove(struct i2c_adapter *adapter); diff --git a/include/linux/i2c-smbus.h b/include/linux/i2c-smbus.h index dc1bd2ab4c13..c08b35d2858a 100644 --- a/include/linux/i2c-smbus.h +++ b/include/linux/i2c-smbus.h @@ -51,4 +51,71 @@ static inline void i2c_register_spd_write_disable(struct= i2c_adapter *adap) { } static inline void i2c_register_spd_write_enable(struct i2c_adapter *adap)= { } #endif =20 +/** + * struct i2c_arp_udid - ARP Unique Device Identifier + * @capabilities: + * @version: + * @vendor: + * @device: + * @interface: + * @subvendor: + * @subdevice: + * @vendor_specific_id: + */ +struct i2c_arp_udid { + u8 capabilities; +#define ARP_CAP_PEC_SUPPORTED BIT(0) +#define ARP_CAP_ADDRESS_TYPE_MASK GENMASK(7, 6) +#define ARP_CAP_ADDRESS_TYPE_FIXED 0 +#define ARP_CAP_ADDRESS_TYPE_DYNAMIC_PERSISTENT 1 +#define ARP_CAP_ADDRESS_TYPE_DYNAMIC_VOLATILE 2 +#define ARP_CAP_ADDRESS_TYPE_RANDOM_NUMBER 3 + u8 version; + u16 vendor; + u16 device; + u16 interface; + u16 subvendor; + u16 subdevice; + u32 vendor_specific_id; +} __packed; + +/** + * SMBUS_DEVICE - macro used to describe a specific SMBus device + * @v: the 16 bit UDID Vendor ID + * @d: the 16 bit UDID Device ID + * + * This macro is used to create a struct smbus_device_id that matches a + * specific device. The interface, subvendor and subdevice fields will be = set to + * SMBUS_ANY_ID. + */ +#define SMBUS_DEVICE(v, d) \ + .vendor =3D (v), .device =3D (d), \ + .interface =3D SMBUS_ANY_ID, \ + .subvendor =3D SMBUS_ANY_ID, .subdevice =3D SMBUS_ANY_ID, \ + .vendor_specific_id =3D SMBUS_ANY_VENDOR_SPECIFIC_ID, + +/** + * SMBUS_INTERFACE - macro used to describe a specific SMBus interface + * @i: the 16 bit UDID interface + * + * This macro is used to create a struct smbus_device_id that matches a + * specific interface. The vendor, device, subvendor and subdevice fields = will be + * set to SMBUS_ANY_ID. + */ +#define SMBUS_INTERFACE(i) \ + .vendor =3D SMBUS_ANY_ID, .device =3D SMBUS_ANY_ID, \ + .interface =3D (i), \ + .subvendor =3D SMBUS_ANY_ID, .subdevice =3D SMBUS_ANY_ID, \ + .vendor_specific_id =3D SMBUS_ANY_VENDOR_SPECIFIC_ID, + +/* The version field in the interface - these are used as the minimum. */ +#define SMBUS_INTERFACE_SMBUS_V2_0 0x4 +#define SMBUS_INTERFACE_SMBUS_V3_0 0x5 + +/* The interfaces defined in the SMBus specification. */ +#define SMBUS_INTERFACE_OEM SMBUS_INTERFACE(BIT(4) | SMBUS_INTERFACE_SMBUS= _V2_0) +#define SMBUS_INTERFACE_ASF SMBUS_INTERFACE(BIT(5) | SMBUS_INTERFACE_SMBUS= _V2_0) +#define SMBUS_INTERFACE_IPMI SMBUS_INTERFACE(BIT(6) | SMBUS_INTERFACE_SMBU= S_V2_0) +#define SMBUS_INTERFACE_ZONE SMBUS_INTERFACE(BIT(7) | SMBUS_INTERFACE_SMBU= S_V3_0) + #endif /* _LINUX_I2C_SMBUS_H */ diff --git a/include/linux/i2c.h b/include/linux/i2c.h index 20fd41b51d5c..1f0cd285e711 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -34,6 +34,8 @@ struct i2c_adapter; struct i2c_client; struct i2c_driver; struct i2c_device_identity; +struct i2c_smbus_arp; +struct i2c_arp_udid; union i2c_smbus_data; struct i2c_board_info; enum i2c_slave_event; @@ -242,6 +244,7 @@ enum i2c_driver_flags { * @command: Callback for bus-wide signaling (optional) * @driver: Device driver model driver * @id_table: List of I2C devices supported by this driver + * @smbus_id_table: SMBus ARP devices supported by this driver * @detect: Callback for device detection * @address_list: The I2C addresses to probe (for detect) * @clients: List of detected clients we created (for i2c-core use only) @@ -295,6 +298,7 @@ struct i2c_driver { =20 struct device_driver driver; const struct i2c_device_id *id_table; + const struct smbus_device_id *smbus_id_table; =20 /* Device detection callback for automatic device creation */ int (*detect)(struct i2c_client *client, struct i2c_board_info *info); @@ -311,6 +315,7 @@ struct i2c_driver { * @addr: Address used on the I2C bus connected to the parent adapter. * @name: Indicates the type of the device, usually a chip name that's * generic enough to hide second-sourcing and compatible revisions. + * @udid: SMBus ARP Unique Device Identifier * @adapter: manages the bus segment hosting this I2C device * @dev: Driver model device node for the slave. * @init_irq: IRQ that was set at initialization @@ -343,6 +348,7 @@ struct i2c_client { /* addresses are stored in the */ /* _LOWER_ 7 bits */ char name[I2C_NAME_SIZE]; + struct i2c_arp_udid *udid; /* Unique Device Identifier */ struct i2c_adapter *adapter; /* the adapter we sit on */ struct device dev; /* the device structure */ int init_irq; /* irq set at initialization */ @@ -410,6 +416,7 @@ static inline bool i2c_detect_slave_mode(struct device = *dev) { return false; } * @resources: resources associated with the device * @num_resources: number of resources in the @resources array * @irq: stored in i2c_client.irq + * @udid: SMBus ARP Unique Device Identifier * * I2C doesn't actually support hardware probing, although controllers and * devices may be able to use I2C_SMBUS_QUICK to tell whether or not there= 's @@ -433,6 +440,7 @@ struct i2c_board_info { const struct resource *resources; unsigned int num_resources; int irq; + struct i2c_arp_udid *udid; }; =20 /** @@ -755,6 +763,8 @@ struct i2c_adapter { struct mutex userspace_clients_lock; struct list_head userspace_clients; =20 + struct i2c_smbus_arp *arp; + struct i2c_bus_recovery_info *bus_recovery_info; const struct i2c_adapter_quirks *quirks; =20 diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetabl= e.h index 24eb5a88a5c5..9d75cdb81e25 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -480,6 +480,19 @@ struct i2c_device_id { kernel_ulong_t driver_data; /* Data private to the driver */ }; =20 +#define SMBUS_ANY_ID 0xffff +#define SMBUS_ANY_VENDOR_SPECIFIC_ID 0xffffffff + +struct smbus_device_id { + __u16 vendor; + __u16 device; + __u16 interface; + __u16 subvendor; + __u16 subdevice; + __u32 vendor_specific_id; + kernel_ulong_t driver_data; /* Data private to the driver */ +}; + /* pci_epf */ =20 #define PCI_EPF_NAME_SIZE 20 diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-of= fsets.c index b4178c42d08f..cd53628e1470 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -147,6 +147,14 @@ int main(void) DEVID(i2c_device_id); DEVID_FIELD(i2c_device_id, name); =20 + DEVID(smbus_device_id); + DEVID_FIELD(smbus_device_id, vendor); + DEVID_FIELD(smbus_device_id, device); + DEVID_FIELD(smbus_device_id, interface); + DEVID_FIELD(smbus_device_id, subvendor); + DEVID_FIELD(smbus_device_id, subdevice); + DEVID_FIELD(smbus_device_id, vendor_specific_id); + DEVID(i3c_device_id); DEVID_FIELD(i3c_device_id, match_flags); DEVID_FIELD(i3c_device_id, dcr); diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index 4e99393a35f1..66efff6d3623 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -840,6 +840,29 @@ static void do_i2c_entry(struct module *mod, void *sym= val) module_alias_printf(mod, false, I2C_MODULE_PREFIX "%s", *name); } =20 +/* Looks like: smbus:vNdNiNsvNsdNvsiN*/ +static void do_smbus_entry(struct module *mod, void *symval) +{ + char alias[256] =3D {}; + + DEF_FIELD(symval, smbus_device_id, vendor); + DEF_FIELD(symval, smbus_device_id, device); + DEF_FIELD(symval, smbus_device_id, interface); + DEF_FIELD(symval, smbus_device_id, subvendor); + DEF_FIELD(symval, smbus_device_id, subdevice); + DEF_FIELD(symval, smbus_device_id, vendor_specific_id); + + ADD(alias, "v", vendor !=3D SMBUS_ANY_ID, vendor); + ADD(alias, "d", device !=3D SMBUS_ANY_ID, device); + ADD(alias, "i", interface !=3D SMBUS_ANY_ID, interface); + ADD(alias, "sv", subvendor !=3D SMBUS_ANY_ID, subvendor); + ADD(alias, "sd", subdevice !=3D SMBUS_ANY_ID, subdevice); + ADD(alias, "vsi", vendor_specific_id !=3D SMBUS_ANY_VENDOR_SPECIFIC_ID, + vendor_specific_id); + + module_alias_printf(mod, true, "smbus:%s", alias); +} + static void do_i3c_entry(struct module *mod, void *symval) { char alias[256] =3D {}; @@ -1439,6 +1462,7 @@ static const struct devtable devtable[] =3D { {"vmbus", SIZE_hv_vmbus_device_id, do_vmbus_entry}, {"rpmsg", SIZE_rpmsg_device_id, do_rpmsg_entry}, {"i2c", SIZE_i2c_device_id, do_i2c_entry}, + {"smbus", SIZE_smbus_device_id, do_smbus_entry}, {"i3c", SIZE_i3c_device_id, do_i3c_entry}, {"slim", SIZE_slim_device_id, do_slim_entry}, {"spi", SIZE_spi_device_id, do_spi_entry}, --=20 2.50.1 From nobody Sat Feb 7 17:04:42 2026 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.16]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AFD50449EC1; Wed, 21 Jan 2026 09:23:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.16 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768987419; cv=none; b=d/Bm5HYV6nWk749awnAiyWgyHZwrpqdI5ATGdsEH7P0daayQoSnZP5/ZVVhgxE2ooVV+j9XQNPXtsiRtxa566QkshmILJM1ZevXX/2OAZvjZDdocOgezmt9FSSd7fI8yMorwJ+eBlV+Sj/KC02EsjQISH52i3pks3NdYrRTDuks= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768987419; c=relaxed/simple; bh=oKrZdqtv0rn5ebODG9t8T6zh953Yk7Gz/RbvHkfVf7c=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mPPGbIy0Yqd0VuoUKAx2qkeG9Xo6VmyTvNwO0Fqviip1D+uOnyAxalpEEoN1SvF6JFX8g3gngtHr4Uxk/TtDhpv6VKltPNJtIOW5IeTAaTq32T1RFfOzEiGUvzdku/NvCEDrTAbN+0mMzl8J2jf2etI7KtaZkLMEwIv/a45yq40= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.intel.com; spf=pass smtp.mailfrom=linux.intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=I4gdju9U; arc=none smtp.client-ip=192.198.163.16 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="I4gdju9U" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1768987416; x=1800523416; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=oKrZdqtv0rn5ebODG9t8T6zh953Yk7Gz/RbvHkfVf7c=; b=I4gdju9UXx1TKUGmVKsBygZFl1G73rWmke9vUJIbYdc7Ls26yJxu3icV paZWyU/4EqbffKNENuUeqyFnll2sCWy1oIcKRBUkAZYUuPu9oCjrChjMJ qHJogPcJmnjkHg8fa0u+r/FTtC0kdi9BqjL1QtPN5VTNkaJTdacgK5JtJ YWcfLvzALkFixA7vCBl/KEmwrdqo8e9AvK5V9RFxuNLMLJsgn8tZqxMI6 K18++jP9F3hYdLjmcwRZvzWRBF5uWJeQQR3koQiksuZ69SP9RbVdt664p B0jYwhuDECBhzTbsuh99zpovb6EwoQw6OdVpEbn66aoZCM3QLbMQXPV8H Q==; X-CSE-ConnectionGUID: CmC0GRH2RwSKHl4e5h/eAw== X-CSE-MsgGUID: fGSYUl9uRxiNNOnO/aeJLA== X-IronPort-AV: E=McAfee;i="6800,10657,11677"; a="57773278" X-IronPort-AV: E=Sophos;i="6.21,242,1763452800"; d="scan'208";a="57773278" Received: from orviesa003.jf.intel.com ([10.64.159.143]) by fmvoesa110.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 21 Jan 2026 01:23:36 -0800 X-CSE-ConnectionGUID: pwWJY6lhSJenAR5EyWk7MA== X-CSE-MsgGUID: rAVn8JSmQjuK8XcrnB2N6g== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.21,242,1763452800"; d="scan'208";a="210560995" Received: from black.igk.intel.com ([10.91.253.5]) by orviesa003.jf.intel.com with ESMTP; 21 Jan 2026 01:23:35 -0800 From: Heikki Krogerus To: Wolfram Sang Cc: Jeremy Kerr , Matt Johnston , linux-i2c@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 2/4] i2c: Sysfs attribute files for the Unique Device Identifier fields Date: Wed, 21 Jan 2026 10:23:26 +0100 Message-ID: <20260121092328.2308705-3-heikki.krogerus@linux.intel.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20260121092328.2308705-1-heikki.krogerus@linux.intel.com> References: <20260121092328.2308705-1-heikki.krogerus@linux.intel.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" In order to utilise the SMBus Address Resolution Protocol's (ARP) Unique Device Identifier (UDID) also in user space, the UDID details need to be exposed. With ARP the address is also dynamically assigned (and may be reset) so it also needs to be exposed to the user space with its own file. The UDID details are only visible with ARP devices, but the address file is made always visible for all I2C client devices. Signed-off-by: Heikki Krogerus --- Documentation/ABI/testing/sysfs-bus-i2c | 53 ++++++++++++ drivers/i2c/i2c-core-base.c | 107 +++++++++++++++++++++++- 2 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c diff --git a/Documentation/ABI/testing/sysfs-bus-i2c b/Documentation/ABI/te= sting/sysfs-bus-i2c new file mode 100644 index 000000000000..b9ebfc2e1b9d --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-i2c @@ -0,0 +1,53 @@ +What: /sys/bus/i2c/devices/.../address +Date: September 2025 +Contact: Heikki Krogerus +Description: + This file has the target address the I2C client responds to. + +What: /sys/bus/i2c/devices/.../vendor +Date: September 2025 +Contact: Heikki Krogerus +Description: + This file is only visible for ARP devices. It returns the Vendor + ID field from the SMBus ARP Unique Device Identifier. It is the + device manufacturer's ID assigned by the SBS Implementers Forum + or the PCI SIG. + +What: /sys/bus/i2c/devices/.../device +Date: September 2025 +Contact: Heikki Krogerus +Description: + This file is only visible for ARP devices. It returns the Device + ID field from the SMBus ARP Unique Device Identifier. The + device ID is assigned by the device manufacturer. + +What: /sys/bus/i2c/devices/.../interface +Date: September 2025 +Contact: Heikki Krogerus +Description: + This file is only visible for ARP devices. It returns the + Interface field from the SMBus ARP Unique Device Identifier. + +What: /sys/bus/i2c/devices/.../subsystem_vendor +Date: September 2025 +Contact: Heikki Krogerus +Description: + This file is only visible for ARP devices. It returns the + Subsystem Vendor ID field from the SMBus ARP Unique Device + Identifier. + +What: /sys/bus/i2c/devices/.../subsystem_device +Date: September 2025 +Contact: Heikki Krogerus +Description: + This file is only visible for ARP devices. It returns the + Subsystem Device ID field from the SMBus ARP Unique Device + Identifier. + +What: /sys/bus/i2c/devices/.../vendor_specific_id +Date: September 2025 +Contact: Heikki Krogerus +Description: + This file is only visible for ARP devices. It returns the + Vendor Specific ID field from the SMBus ARP Unique Device + Identifier. diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index 6e96ac186921..4e2422cd0448 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -685,6 +685,13 @@ name_show(struct device *dev, struct device_attribute = *attr, char *buf) } static DEVICE_ATTR_RO(name); =20 +static ssize_t +address_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0x%02x\n", to_i2c_client(dev)->addr); +} +static DEVICE_ATTR_RO(address); + static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -712,11 +719,109 @@ static DEVICE_ATTR_RO(modalias); =20 static struct attribute *i2c_dev_attrs[] =3D { &dev_attr_name.attr, + &dev_attr_address.attr, /* modalias helps coldplug: modprobe $(cat .../modalias) */ &dev_attr_modalias.attr, NULL }; -ATTRIBUTE_GROUPS(i2c_dev); + +static const struct attribute_group i2c_dev_group =3D { + .attrs =3D i2c_dev_attrs, +}; + +static ssize_t +capabilities_show(struct device *dev, struct device_attribute *attr, char = *buf) +{ + struct i2c_client *client =3D to_i2c_client(dev); + + return sysfs_emit(buf, "0x%02x\n", client->udid->capabilities); +} +static DEVICE_ATTR_RO(capabilities); + +static ssize_t +vendor_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *client =3D to_i2c_client(dev); + + return sysfs_emit(buf, "0x%04x\n", client->udid->vendor); +} +static DEVICE_ATTR_RO(vendor); + +static ssize_t +device_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *client =3D to_i2c_client(dev); + + return sysfs_emit(buf, "0x%04x\n", client->udid->device); +} +static DEVICE_ATTR_RO(device); + +static ssize_t +interface_show(struct device *dev, struct device_attribute *attr, char *bu= f) +{ + struct i2c_client *client =3D to_i2c_client(dev); + + return sysfs_emit(buf, "0x%04x\n", client->udid->interface); +} +static DEVICE_ATTR_RO(interface); + +static ssize_t +subsystem_vendor_show(struct device *dev, struct device_attribute *attr, c= har *buf) +{ + struct i2c_client *client =3D to_i2c_client(dev); + + return sysfs_emit(buf, "0x%04x\n", client->udid->subvendor); +} +static DEVICE_ATTR_RO(subsystem_vendor); + +static ssize_t +subsystem_device_show(struct device *dev, struct device_attribute *attr, c= har *buf) +{ + struct i2c_client *client =3D to_i2c_client(dev); + + return sysfs_emit(buf, "0x%04x\n", client->udid->subdevice); +} +static DEVICE_ATTR_RO(subsystem_device); + +static ssize_t +vendor_specific_id_show(struct device *dev, struct device_attribute *attr,= char *buf) +{ + struct i2c_client *client =3D to_i2c_client(dev); + + return sysfs_emit(buf, "0x%08x\n", client->udid->vendor_specific_id); +} +static DEVICE_ATTR_RO(vendor_specific_id); + +static struct attribute *udid_attrs[] =3D { + &dev_attr_capabilities.attr, + &dev_attr_vendor.attr, + &dev_attr_device.attr, + &dev_attr_interface.attr, + &dev_attr_subsystem_vendor.attr, + &dev_attr_subsystem_device.attr, + &dev_attr_vendor_specific_id.attr, + NULL +}; + +static umode_t +udid_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + if (to_i2c_client(kobj_to_dev(kobj))->udid) + return attr->mode; + + return 0; +} + +static const struct attribute_group udid_group =3D { + .is_visible =3D udid_is_visible, + .attrs =3D udid_attrs, +}; + +static const struct attribute_group *i2c_dev_groups[] =3D { + &i2c_dev_group, + &udid_group, + NULL +}; =20 const struct bus_type i2c_bus_type =3D { .name =3D "i2c", --=20 2.50.1 From nobody Sat Feb 7 17:04:42 2026 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.16]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7C2F146AF31; Wed, 21 Jan 2026 09:23:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.16 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768987420; cv=none; b=Bs+07JCJV/mi5DRLhMp5qsqY2r99cBhI0x0shGqpgho1BKhNBafDXW0pKYRQyNNyOs9MAFiaePTHRIhSNy9akvSKPeYvXBi7Xy/gybOUjufPqW3Bp6K48nMp2aLkhRYa02MGIXBbZmtttS9UZIfuCwo117uzzzycjrW5BDryyKE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768987420; c=relaxed/simple; bh=G1GyKVSX9uKD79xYXrL1AswlRkuNmQ2QzEepyMFCmHs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=hCxqfL42BE1c3AoWOjynsYK7y80zm96rKTQgQnaPa+I9JUHCA93dK49J3iferE46IhbhRXTq1POB4Acn408Hoox+cQEIvvgnDLZPd+oYzyQAQw9QmH1CJl1Jqu24mSsuyCfiMjshX5VmYTpNPmTE+JptbFtwG9KyUfJBcNQkDMc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.intel.com; spf=pass smtp.mailfrom=linux.intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=R22BqTqV; arc=none smtp.client-ip=192.198.163.16 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="R22BqTqV" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1768987418; x=1800523418; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=G1GyKVSX9uKD79xYXrL1AswlRkuNmQ2QzEepyMFCmHs=; b=R22BqTqVFMy195+eHXC5iTdyZVcMhYYWCPtG7LUAzIKKZ+kcyG4w8mje jyqgmFLdWNdD6akaki1+gwqGxx3GsclGg4ayR54d5jeY6IqKEiIvYqqnO tXF7mhVpVzvFP489taHRvRgnHntVsigex/SZdbrwr75I615vy7vsw8QdE 6hGRATbkmNn7o1e/1qvoBXviJPmaRtV3EzieUwVkHP8WE4TGEwgqXh6OZ K+FwZygeJXRfbudmot3nseiuiUl2soIbYra+z9rKSmEkaZKARClUN6mX7 rly14/sbXfS0C88aRJpLdt0NRSECZYCuzgTVZQa061OxdZ0FjuSB+jh+d Q==; X-CSE-ConnectionGUID: UV1IEhP7QSeXcOd18fWRXQ== X-CSE-MsgGUID: eihg6aN5T0eQ34apfD3wug== X-IronPort-AV: E=McAfee;i="6800,10657,11677"; a="57773282" X-IronPort-AV: E=Sophos;i="6.21,242,1763452800"; d="scan'208";a="57773282" Received: from orviesa003.jf.intel.com ([10.64.159.143]) by fmvoesa110.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 21 Jan 2026 01:23:38 -0800 X-CSE-ConnectionGUID: Uoph8pElSlGD9fUCxwwe5A== X-CSE-MsgGUID: 9HzlT/PCQROiXYhEDVPByQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.21,242,1763452800"; d="scan'208";a="210561003" Received: from black.igk.intel.com ([10.91.253.5]) by orviesa003.jf.intel.com with ESMTP; 21 Jan 2026 01:23:37 -0800 From: Heikki Krogerus To: Wolfram Sang Cc: Jeremy Kerr , Matt Johnston , linux-i2c@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 3/4] mctp i2c: Enable SMBus ARP discovery Date: Wed, 21 Jan 2026 10:23:27 +0100 Message-ID: <20260121092328.2308705-4-heikki.krogerus@linux.intel.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20260121092328.2308705-1-heikki.krogerus@linux.intel.com> References: <20260121092328.2308705-1-heikki.krogerus@linux.intel.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Since the SMBus Address Resolution Protocol (ARP) is now supported with all I2C host adapters, every ARP-capable MCTP device will get automatically enumerated. Those devices just need to be bind to this driver. The SMBus ARP-capable MCTP devices are identified by checking the interface (MCTP SMBus/I2C Transport Binding Specification section 6.5). The interface must match the ASF protocol, so all devices that use the ASF protocol as their interface will be probed by this driver. Link: https://www.dmtf.org/sites/default/files/standards/documents/DSP0237_= 1.2.0.pdf Signed-off-by: Heikki Krogerus Acked-by: Jakub Kicinski --- drivers/net/mctp/mctp-i2c.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/mctp/mctp-i2c.c b/drivers/net/mctp/mctp-i2c.c index 8043b57bdf25..47d7f6b5212e 100644 --- a/drivers/net/mctp/mctp-i2c.c +++ b/drivers/net/mctp/mctp-i2c.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -1105,6 +1106,12 @@ static const struct i2c_device_id mctp_i2c_id[] =3D { }; MODULE_DEVICE_TABLE(i2c, mctp_i2c_id); =20 +static const struct smbus_device_id mctp_smbus_id[] =3D { + { SMBUS_INTERFACE_ASF }, + {} +}; +MODULE_DEVICE_TABLE(smbus, mctp_smbus_id); + static const struct of_device_id mctp_i2c_of_match[] =3D { { .compatible =3D "mctp-i2c-controller" }, {}, @@ -1119,6 +1126,7 @@ static struct i2c_driver mctp_i2c_driver =3D { .probe =3D mctp_i2c_probe, .remove =3D mctp_i2c_remove, .id_table =3D mctp_i2c_id, + .smbus_id_table =3D mctp_smbus_id, }; =20 static __init int mctp_i2c_mod_init(void) --=20 2.50.1 From nobody Sat Feb 7 17:04:42 2026 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.16]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4981D47B42C; Wed, 21 Jan 2026 09:23:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.16 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768987423; cv=none; b=C1qvf7xPjSZPihBhX/NEyNXWQAQtFsnqHb2REtjJUKq0jAZbR+9pdVgFjp28JOv9KnFQVDOqSYlYXboDGxRe3fB+Rg1Sh9hy0ovdG2G5HR4ibfuUVDHHz4yHN9Dd41QyPSodEf+ho9JPjpqZ9IsmDN8huAVUGLKYD6PaJPsTglQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768987423; c=relaxed/simple; bh=xNWCmW31pGZmF7lTIVjzCHLl1hqt60gUK/dvIqhGBIo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=p8zzN4Prbj+yATyB0T+RSUH63qmOLdEjGVagBtjw9RBHHV9ssAnOnBsRrRgmIpfxVe6Y1bMlbU6dNJRRKDV1EHYonWtYZsf06V4tzrJLtHQAB6Xf0lZUwBZzIU7dUYz6wbbqMihJT9ere4VL7CzxyTYl3uQDCSKjor62Ywy+NsQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.intel.com; spf=pass smtp.mailfrom=linux.intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=LtCcDuK7; arc=none smtp.client-ip=192.198.163.16 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="LtCcDuK7" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1768987421; x=1800523421; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=xNWCmW31pGZmF7lTIVjzCHLl1hqt60gUK/dvIqhGBIo=; b=LtCcDuK7a7b2sva9u5w7oVRCcJxEx0fCqr3RF/8RFhgF3yguDoHqkqda WzGKVnau6kXCaNIgv/viBnq8/KzRGyegCulb1NHCATQheHtxyaBDpTDlb PU/6w6wuesXexZaLDTUP/4sjqMAjzzot8N5un3A1ZGvwPBU+EDl+k2F2K XSiiyshzgYQxbjzlyXXYYnF5Wf7NtvRzxdfSkBJKJ1qUPcKKuT/AQqAAx oqceeCm3C9LkvQgZpAGMTS2Rxz7uHjcyT9pIY3YtyhYizo/7C6S1rSeuM tXQSvtZAv27Ek/k3utSuj5Y5uey6th1w2KTDUJ+C5jeBBtwJcRXiBGt28 Q==; X-CSE-ConnectionGUID: V6LQ2jrXQAyJ7yExUbJj/A== X-CSE-MsgGUID: U0s324AETJetjDpFoIXXqw== X-IronPort-AV: E=McAfee;i="6800,10657,11677"; a="57773286" X-IronPort-AV: E=Sophos;i="6.21,242,1763452800"; d="scan'208";a="57773286" Received: from orviesa003.jf.intel.com ([10.64.159.143]) by fmvoesa110.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 21 Jan 2026 01:23:40 -0800 X-CSE-ConnectionGUID: Ydd0iDNfTzmreqfJY5KpAg== X-CSE-MsgGUID: MLLY5h18S8W26brRs1IpWg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.21,242,1763452800"; d="scan'208";a="210561009" Received: from black.igk.intel.com ([10.91.253.5]) by orviesa003.jf.intel.com with ESMTP; 21 Jan 2026 01:23:39 -0800 From: Heikki Krogerus To: Wolfram Sang Cc: Jeremy Kerr , Matt Johnston , linux-i2c@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 4/4] i2c: Add SMBus ARP target mode test driver Date: Wed, 21 Jan 2026 10:23:28 +0100 Message-ID: <20260121092328.2308705-5-heikki.krogerus@linux.intel.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20260121092328.2308705-1-heikki.krogerus@linux.intel.com> References: <20260121092328.2308705-1-heikki.krogerus@linux.intel.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Backend that the hosts can enumerate by using the SMBus Address Resolution Protocol. Signed-off-by: Heikki Krogerus --- 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. =20 +config I2C_TARGET_ARP + tristate "SMBus ARP target mode test driver" + help + This backend makes Linux respond to SMBus Address Resolution Protocol + commands. + endif =20 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 +=3D algos/ busses/ muxes/ obj-$(CONFIG_I2C_STUB) +=3D i2c-stub.o obj-$(CONFIG_I2C_SLAVE_EEPROM) +=3D i2c-slave-eeprom.o obj-$(CONFIG_I2C_SLAVE_TESTUNIT) +=3D i2c-slave-testunit.o +obj-$(CONFIG_I2C_TARGET_ARP) +=3D i2c-target-arp.o =20 ccflags-$(CONFIG_I2C_DEBUG_CORE) :=3D -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 +#include +#include +#include +#include +#include +#include + +#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 =3D I2C_SMBUS_DEFAULT_ADDR << 1; + u8 len =3D sizeof(arp->data); + u8 pec =3D 0; + + pec =3D i2c_smbus_pec(pec, &addr, 1); + pec =3D i2c_smbus_pec(pec, arp->buf, arp->write_count); + + if (read) { + addr |=3D 1; + pec =3D i2c_smbus_pec(pec, &addr, 1); + pec =3D i2c_smbus_pec(pec, &len, 1); + pec =3D i2c_smbus_pec(pec, (void *)&arp->data, len); + } + + return pec; +} + +static int i2c_target_arp_cb(struct i2c_client *target, enum i2c_slave_eve= nt event, u8 *val) +{ + struct arp_target *arp =3D container_of(target, struct arp_target, target= ); + + switch (event) { + case I2C_SLAVE_READ_PROCESSED: + if (arp->flag) + break; + + if (arp->read_count =3D=3D sizeof(arp->data)) + *val =3D i2c_arp_pec(arp, 1); + else + *val =3D ((u8 *)&arp->data)[arp->read_count]; + + arp->read_count++; + break; + case I2C_SLAVE_READ_REQUESTED: + if (arp->flag) + break; + + *val =3D sizeof(arp->data); + break; + case I2C_SLAVE_STOP: + switch (arp->buf[0]) { + case ARP_CMD_PREPARE_TO_ARP: + arp->flag =3D 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 =3D ARP_ADDRESS_VALID | ARP_ADDRESS_RESOLVED; + break; + default: + break; + } + + arp->read_count =3D 0; + arp->write_count =3D 0; + break; + case I2C_SLAVE_WRITE_REQUESTED: + arp->read_count =3D 0; + arp->write_count =3D 0; + break; + case I2C_SLAVE_WRITE_RECEIVED: + arp->buf[arp->write_count] =3D *val; + arp->write_count++; + break; + } + + return 0; +} + +static void i2c_notify_arp_controller(struct i2c_client *client) +{ + union i2c_smbus_data data =3D { }; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return; + + ret =3D 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 =3D devm_kzalloc(&client->dev, sizeof(*arp), GFP_KERNEL); + if (!arp) + return -ENOMEM; + + arp->client =3D client; + i2c_set_clientdata(client, arp); + + arp->target =3D *client; + arp->target.addr =3D I2C_SMBUS_DEFAULT_ADDR; + arp->target.flags |=3D 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 =3D 0xXXXX; + * arp->data.udid.device =3D 0xXXXX; + * + * The capabilities are also 0. That makes this a fixed address device + * without support for PEC. + */ + arp->data.udid.version =3D BIT(3); /* UDID Version 1 */ + arp->data.udid.interface =3D SMBUS_INTERFACE_SMBUS_V2_0; + arp->data.target_addr =3D 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 =3D 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 =3D i2c_get_clientdata(client); + i2c_slave_unregister(&arp->target); +} + +static const struct i2c_device_id i2c_target_arp_id[] =3D { + { "target-arp" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, i2c_target_arp_id); + +static struct i2c_driver i2c_target_arp_driver =3D { + .driver =3D { + .name =3D "i2c-target-arp", + }, + .probe =3D i2c_target_arp_probe, + .remove =3D i2c_target_arp_remove, + .id_table =3D i2c_target_arp_id, +}; +module_i2c_driver(i2c_target_arp_driver); + +MODULE_DESCRIPTION("SMBus ARP target mode test driver"); +MODULE_LICENSE("GPL"); --=20 2.50.1