From nobody Mon Apr 13 14:52:06 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 08550C3F6B0 for ; Wed, 10 Aug 2022 07:21:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231184AbiHJHVx (ORCPT ); Wed, 10 Aug 2022 03:21:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47708 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231243AbiHJHVb (ORCPT ); Wed, 10 Aug 2022 03:21:31 -0400 Received: from mail-yw1-x1149.google.com (mail-yw1-x1149.google.com [IPv6:2607:f8b0:4864:20::1149]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 86DB365CA for ; Wed, 10 Aug 2022 00:21:27 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-31f4e870a17so119051887b3.9 for ; Wed, 10 Aug 2022 00:21:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=cc:to:from:subject:references:mime-version:message-id:in-reply-to :date:from:to:cc; bh=pnFnFHNpmd0n2hgyZvckPaNbwAr0ZSVFKcbnQPJ5x8w=; b=sA1g04We2jt7VZVagMNzZw/38n91pyYFMtE/ynaUC0CI9osMREq/nMAc1LCn40O4IB 1koXTPp3dKGehhA60ML9kwAbNAN8EKfKPryvizinw2XmjZIw7TjGAiC/2BQx2MTPDkN1 OhDBzoQtq00tTfrez5fmwtF+pJMdkAEZpOE5bBYSU6aFcGuPgnXBasr89IZ3fQdz22Ji fNH2LvOnhAr/TfhutgP5jD/duh/puieER5XHuZrUTu4rC8UtusTuR5iclTLxzDmDbUaS AsMAqjw5zXWCOZ4PVEreueyFLC/Os+zEkd1Bh3ERI6sJ4y770xt6bLkfJjwgMp2dosud gqlQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=cc:to:from:subject:references:mime-version:message-id:in-reply-to :date:x-gm-message-state:from:to:cc; bh=pnFnFHNpmd0n2hgyZvckPaNbwAr0ZSVFKcbnQPJ5x8w=; b=sU2CV3WtCt9UJHM08x11n4Pf/5No3O62NmURwb3F5pZXn4RctEJ+LzKOdvXCTqY6iW KHgflxnPaf73i0n1pXKiTCMqa0lV8Ebp7vmNS+N8QN6OgLiIW1koyqUQdeb6RhOdMouV MeZcaIkFRaslRkrHORwVrVEEu7zlB+g2a1nsdLyk+9JNqlNWcMPadALd/vbBWA/VO87l RVqGm4w1VbW/Hpp4A5FfSj8RW+tr1zPrpE0VnKgE7+vSIRuT0TFdYpMMy0PTBhwiySLj wp52ivnHPZ8Q5EGRhGjsTOCyMcUtADIvgRYIga29dy0TuUO8qpIKHfeQGKUh1Mq+kn1G GNfQ== X-Gm-Message-State: ACgBeo2ISCjd+FX7ASRGugAOTUe//dEaq5EirxaLDMaU0UFEj7jzMZAq uegfepwvazFlVpw8ExzgHIQtNOOUBNuOInceQyooG3yxPzwkEZM5yOzFGFJOtr2jb3WUcFa4EUh WWcRWVTcuTXCtQr1/K4upOq6ZdQ3ZLI+8uhJAZhc4qhUb2MQxxWGvcU7Ly5pPABm2/EDmGhk3 X-Google-Smtp-Source: AA6agR5EHCMdHU7aWVMBOdciZ1pbVfeNp4dB5Gb3k3vNfxa2/C9DX2Jd7nlZN5yA6yCsb7fc2Q5TF7KAAYUS X-Received: from suichen.svl.corp.google.com ([2620:15c:2c5:13:820:de6:2fcc:8636]) (user=suichen job=sendgmr) by 2002:a25:311:0:b0:67c:3358:c4e5 with SMTP id 17-20020a250311000000b0067c3358c4e5mr4727632ybd.396.1660116086811; Wed, 10 Aug 2022 00:21:26 -0700 (PDT) Date: Wed, 10 Aug 2022 00:20:41 -0700 In-Reply-To: <20220810072041.57055-1-suichen@google.com> Message-Id: <20220810072041.57055-2-suichen@google.com> Mime-Version: 1.0 References: <20220810072041.57055-1-suichen@google.com> X-Mailer: git-send-email 2.37.1.559.g78731f0fdb-goog Subject: [RFC Patch v5 Resend 1/1] i2c debug counters as sysfs attributes From: Sui Chen To: linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org, wsa@kernel.org, openbmc@lists.ozlabs.org, tali.perry1@gmail.com Cc: joel@jms.id.au, andrew@aj.id.au, benjaminfair@google.com, krellan@google.com, Sui Chen Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This change renames the I2C debug counters as suggested, and makes them available to i2c_adapter's and i2c_client's: - bus_errors - transfers (only applicable to i2c_adapter) - messages (only applicable to i2c_client) - nacks - timeouts - recovery_successes (only applicable to i2c_adapter) - recovery_failures (only applicable to i2c_adapter) The above stats are located in the sysfs path "stats". The kobj for "stats" for a i2c-device or an i2c-adapter are added as children of the device's and adapter's kobj. Did some quick tests with QEMU using a program that saves/replays I2C trace by reading hwmon sensors (the program is located at https://gerrit.openbmc-project.xyz/c/openbmc/openbmc-tools/+/52527) (normal read) root@gsj:~# cat /sys/class/i2c-adapter/i2c-1/stats/{transfers,nacks}\ /sys/class/i2c-adapter/i2c-1/1-005c/stats/{messages,nacks} && \ sleep 1 && ./i2c_bmk_bmc 0 46 0 92 0 /sys/class/hwmon/hwmon0/temp1_input: 0 [FindTraceEntries] t0=3D111.000000 t1=3D113.000000 Found 4 interesting I2C trace entries: i2c_write: i2c-1 #0 a=3D05c f=3D0000 l=3D1 [00] i2c_read: i2c-1 #1 a=3D05c f=3D0001 l=3D2 i2c_reply: i2c-1 #1 a=3D05c f=3D0001 l=3D2 [00-00] i2c_result: i2c-1 n=3D2 ret=3D2 root@gsj:~# cat /sys/class/i2c-adapter/i2c-1/stats/{transfers,nacks} \ /sys/class/i2c-adapter/i2c-1/1-005c/stats/{messages,nacks} 47 <-- 1 more transfer on i2c-1 0 94 <-- 2 more messages sent to 1-005c 0 (deliberately nack) root@gsj:~# sleep 1 && ./i2c_bmk_bmc 0 (injects 1 NACK using QEMU) /sys/class/hwmon/hwmon0/temp1_input: 0 [FindTraceEntries] t0=3D116.000000 t1=3D118.000000 Found 3 interesting I2C trace entries: i2c_write: i2c-1 #0 a=3D05c f=3D0000 l=3D1 [00] i2c_read: i2c-1 #1 a=3D05c f=3D0001 l=3D2 i2c_result: i2c-1 n=3D2 ret=3D-6 root@gsj:~# cat /sys/class/i2c-adapter/i2c-1/stats/{transfers,nacks} \ /sys/class/i2c-adapter/i2c-1/1-005c/stats/{messages,nacks} 47 1 <-- 1 more NACK on i2c-1 94 1 <-- 1 more NACK attributed to 1-005c Signed-off-by: Sui Chen --- drivers/i2c/i2c-core-base.c | 240 +++++++++++++++++++++++++++++++++++- drivers/i2c/i2c-dev.c | 94 ++++++++++++++ include/linux/i2c.h | 41 ++++++ 3 files changed, 374 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index 84f12bf90644..4e7bd849f127 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -270,11 +270,22 @@ EXPORT_SYMBOL_GPL(i2c_generic_scl_recovery); =20 int i2c_recover_bus(struct i2c_adapter *adap) { + if (!adap->stats) + i2c_adapter_create_stats_directory(adap); + if (!adap->bus_recovery_info) return -EBUSY; =20 dev_dbg(&adap->dev, "Trying i2c bus recovery\n"); - return adap->bus_recovery_info->recover_bus(adap); + int ret =3D adap->bus_recovery_info->recover_bus(adap); + + if (ret =3D=3D 0) + ++(adap->stats->recovery_successes); + else + ++(adap->stats->recovery_failures); + + return ret; + } EXPORT_SYMBOL_GPL(i2c_recover_bus); =20 @@ -993,6 +1004,78 @@ int i2c_dev_irq_from_resources(const struct resource = *resources, return 0; } =20 +ssize_t i2c_client_stats_messages_show(struct kobject *kobj, struct kobj_a= ttribute *addr, + char *buf) +{ + return sysfs_emit(buf, "%llu\n", + container_of(kobj, struct i2c_client_stats, kobj)->messages); +} + +struct kobj_attribute i2c_client_stats_messages_attr =3D { + .attr =3D { .name =3D "messages", .mode =3D 0444 }, + .show =3D i2c_client_stats_messages_show, +}; + +ssize_t i2c_client_stats_bus_errors_show(struct kobject *kobj, struct kobj= _attribute *addr, + char *buf) +{ + return sysfs_emit(buf, "%llu\n", + container_of(kobj, struct i2c_client_stats, kobj)->bus_errors); +} + +struct kobj_attribute i2c_client_stats_bus_errors_attr =3D { + .attr =3D { .name =3D "bus_errors", .mode =3D 0444 }, + .show =3D i2c_client_stats_bus_errors_show, +}; + +ssize_t i2c_client_stats_nacks_show(struct kobject *kobj, struct kobj_attr= ibute *addr, + char *buf) +{ + return sysfs_emit(buf, "%llu\n", + container_of(kobj, struct i2c_client_stats, kobj)->nacks); +} + +struct kobj_attribute i2c_client_stats_nacks_attr =3D { + .attr =3D { .name =3D "nacks", .mode =3D 0444 }, + .show =3D i2c_client_stats_nacks_show, +}; + +ssize_t i2c_client_stats_timeouts_show(struct kobject *kobj, struct kobj_a= ttribute *addr, + char *buf) +{ + return sysfs_emit(buf, "%llu\n", + container_of(kobj, struct i2c_client_stats, kobj)->timeouts); +} + +struct kobj_attribute i2c_client_stats_timeouts_attr =3D { + .attr =3D { .name =3D "timeouts", .mode =3D 0444 }, + .show =3D i2c_client_stats_timeouts_show, +}; + +static struct attribute *i2c_client_stats_attrs[] =3D { + &i2c_client_stats_messages_attr.attr, + &i2c_client_stats_bus_errors_attr.attr, + &i2c_client_stats_nacks_attr.attr, + &i2c_client_stats_timeouts_attr.attr, + NULL +}; + +static struct kobj_type i2c_client_stats_ktype =3D { + .sysfs_ops =3D &kobj_sysfs_ops, + .default_attrs =3D i2c_client_stats_attrs, +}; + +/** + * Functions for maintaining the adapter -> client stats lookup structure + */ +static struct i2c_client_stats *__get_i2c_client_stats(struct i2c_adapter_= stats *stats, + int i2c_addr); +static void __insert_i2c_client_stats(struct i2c_adapter_stats *stats, + struct i2c_client_stats *client_stats, + int i2c_addr); +static void __remove_i2c_client_stats(struct i2c_adapter_stats *stats, int= i2c_addr); +static int __count_i2c_client_stats(struct i2c_adapter_stats *stats); + /** * i2c_new_client_device - instantiate an i2c device * @adap: the adapter managing the device @@ -1014,6 +1097,7 @@ i2c_new_client_device(struct i2c_adapter *adap, struc= t i2c_board_info const *inf { struct i2c_client *client; int status; + struct i2c_client_stats *client_stats; =20 client =3D kzalloc(sizeof *client, GFP_KERNEL); if (!client) @@ -1069,6 +1153,20 @@ i2c_new_client_device(struct i2c_adapter *adap, stru= ct i2c_board_info const *inf dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n", client->name, dev_name(&client->dev)); =20 + client_stats =3D kzalloc(sizeof(*client_stats), GFP_KERNEL); + client->stats =3D client_stats; + client_stats->address =3D info->addr; + + kobject_init_and_add(&client_stats->kobj, &i2c_client_stats_ktype, + &client->dev.kobj, "stats"); + + if (!adap->stats) + i2c_adapter_create_stats_directory(adap); + + __insert_i2c_client_stats(adap->stats, client_stats, info->addr); + dev_info(&adap->dev, "Added i2c_client_stats, adapter has %d client stats= now", + __count_i2c_client_stats(adap->stats)); + return client; =20 out_remove_swnode: @@ -1103,6 +1201,12 @@ void i2c_unregister_device(struct i2c_client *client) if (ACPI_COMPANION(&client->dev)) acpi_device_clear_enumerated(ACPI_COMPANION(&client->dev)); device_remove_software_node(&client->dev); + + __remove_i2c_client_stats(client->adapter->stats, client->addr); + dev_info(&client->adapter->dev, "Removed i2c_client_stats, adapter has %d= client stats now", + __count_i2c_client_stats(client->adapter->stats)); + kfree(client->stats); + device_unregister(&client->dev); } EXPORT_SYMBOL_GPL(i2c_unregister_device); @@ -2176,6 +2280,8 @@ int __i2c_transfer(struct i2c_adapter *adap, struct i= 2c_msg *msgs, int num) { unsigned long orig_jiffies; int ret, try; + int i, j, addr; + struct i2c_client_stats *cs; =20 if (WARN_ON(!msgs || num < 1)) return -EINVAL; @@ -2223,6 +2329,50 @@ int __i2c_transfer(struct i2c_adapter *adap, struct = i2c_msg *msgs, int num) trace_i2c_result(adap, num, ret); } =20 + // Per-adapter I2C stats + if (!adap->stats) + i2c_adapter_create_stats_directory(adap); + if (ret < 0) { + if (ret =3D=3D -ENXIO) + ++(adap->stats->nacks); + else if (ret =3D=3D -ETIMEDOUT) + ++(adap->stats->timeouts); + else + ++(adap->stats->bus_errors); + } else if (num > 0) { + ++(adap->stats->transfers); + } + + // Per-address I2C stats + // If no errors, incerement the message count per client + if (ret >=3D 0) { + for (i =3D 0; i < ret; ++i) { + addr =3D msgs[i].addr; + cs =3D __get_i2c_client_stats(adap->stats, addr); + if (!cs) // For muxed devices, their stats are at the leaf level + continue; + if (num > 0) + ++(cs->messages); + } + } else { // Otherwise, attribute the error to each of the distinct addre= sses + for (i =3D 0; i < num; ++i) { + for (j =3D 0; j < i; ++j) { // de-duplicate + if (msgs[i].addr =3D=3D msgs[j].addr) + goto done; + } + cs =3D __get_i2c_client_stats(adap->stats, msgs[i].addr); + if (!cs) + continue; + if (ret =3D=3D -ENXIO) + ++(cs->nacks); + else if (ret =3D=3D -ETIMEDOUT) + ++(cs->timeouts); + else + ++(cs->bus_errors); +done: + {} + } + } return ret; } EXPORT_SYMBOL(__i2c_transfer); @@ -2619,6 +2769,94 @@ void i2c_put_dma_safe_msg_buf(u8 *buf, struct i2c_ms= g *msg, bool xferred) } EXPORT_SYMBOL_GPL(i2c_put_dma_safe_msg_buf); =20 +static struct i2c_client_stats *__get_i2c_client_stats(struct i2c_adapter_= stats *stats, + int i2c_addr) +{ + int addr; + struct rb_node *n, *parent; + struct i2c_client_stats_rbnode *cs_node; + + n =3D stats->i2c_client_stats_root.rb_node; + parent =3D NULL; + while (n) { + parent =3D n; + cs_node =3D rb_entry(parent, struct i2c_client_stats_rbnode, rb_node); + addr =3D cs_node->stats->address; + if (addr > i2c_addr) + n =3D n->rb_left; + else if (addr < i2c_addr) + n =3D n->rb_right; + else + return cs_node->stats; + } + return NULL; +} + +static void __insert_i2c_client_stats(struct i2c_adapter_stats *stats, + struct i2c_client_stats *client_stats, int i2c_addr) +{ + char buf[32]; + int ret, addr; + struct rb_node **link, *parent; + struct i2c_client_stats_rbnode *cs_node; + + ret =3D 1; + link =3D &stats->i2c_client_stats_root.rb_node; + parent =3D NULL; + while (*link) { + parent =3D *link; + cs_node =3D rb_entry(parent, struct i2c_client_stats_rbnode, rb_node); + addr =3D cs_node->stats->address; + if (addr > i2c_addr) + link =3D &(*link)->rb_left; + else if (addr < i2c_addr) + link =3D &(*link)->rb_right; + else + return; // Already exists + } + + cs_node =3D kzalloc(sizeof(*cs_node), GFP_KERNEL); + + cs_node->stats =3D client_stats; + rb_link_node(&cs_node->rb_node, parent, link); + rb_insert_color(&cs_node->rb_node, &stats->i2c_client_stats_root); +} + +static void __remove_i2c_client_stats(struct i2c_adapter_stats *stats, int= i2c_addr) +{ + int addr; + struct rb_node *n, *parent; + struct i2c_client_stats_rbnode *cs_node; + + n =3D stats->i2c_client_stats_root.rb_node; + parent =3D NULL; + while (n) { + parent =3D n; + cs_node =3D rb_entry(parent, struct i2c_client_stats_rbnode, rb_node); + addr =3D cs_node->stats->address; + if (addr > i2c_addr) { + n =3D n->rb_left; + } else if (addr < i2c_addr) { + n =3D n->rb_right; + } else { + rb_erase(&cs_node->rb_node, &stats->i2c_client_stats_root); + return; + } + } +} + +static int __count_i2c_client_stats(struct i2c_adapter_stats *stats) +{ + struct rb_node *n =3D stats->i2c_client_stats_root.rb_node; + int ret =3D 0; + + while (n) { + ++ret; + n =3D rb_next(n); + } + return ret; +} + MODULE_AUTHOR("Simon G. Vogl "); MODULE_DESCRIPTION("I2C-Bus main module"); MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c index 77f576e51652..50141dd42796 100644 --- a/drivers/i2c/i2c-dev.c +++ b/drivers/i2c/i2c-dev.c @@ -767,6 +767,100 @@ static void __exit i2c_dev_exit(void) unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS); } =20 +static struct i2c_adapter *kobj_to_adapter(struct device *pdev) +{ + struct kobject *dev_kobj; + struct device *dev; + + dev_kobj =3D ((struct kobject *)pdev)->parent; + dev =3D container_of(dev_kobj, struct device, kobj); + return to_i2c_adapter(dev); +} + +ssize_t bus_errors_show(struct device *pdev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%llu\n", kobj_to_adapter(pdev)->stats->bus_errors= ); +} +DEVICE_ATTR_RO(bus_errors); + +ssize_t transfers_show(struct device *pdev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%llu\n", kobj_to_adapter(pdev)->stats->transfers); +} +DEVICE_ATTR_RO(transfers); + +ssize_t nacks_show(struct device *pdev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%llu\n", kobj_to_adapter(pdev)->stats->nacks); +} +DEVICE_ATTR_RO(nacks); + +ssize_t recovery_successes_show(struct device *pdev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%llu\n", kobj_to_adapter(pdev)->stats->recovery_s= uccesses); +} +DEVICE_ATTR_RO(recovery_successes); + +ssize_t recovery_failures_show(struct device *pdev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%llu\n", kobj_to_adapter(pdev)->stats->recovery_f= ailures); +} +DEVICE_ATTR_RO(recovery_failures); + +ssize_t timeouts_show(struct device *pdev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%llu\n", kobj_to_adapter(pdev)->stats->timeouts); +} +DEVICE_ATTR_RO(timeouts); + +/** + * i2c_adapter_create_stats_directory - creates folder for I2C statistics. + * @adapter: the i2c_adapter to create the stats directory for. + * + * Return: 0 if successful, 1 if failed. + */ +int i2c_adapter_create_stats_directory(struct i2c_adapter *adapter) +{ + struct i2c_adapter_stats *stats; + int ret =3D 1; + + stats =3D kzalloc(sizeof(*stats), GFP_KERNEL); + if (!stats) { + adapter->stats =3D NULL; + return ret; + } + adapter->stats =3D stats; + adapter->stats->kobj =3D kobject_create_and_add("stats", &adapter->dev.ko= bj); + + ret =3D sysfs_create_file(adapter->stats->kobj, &dev_attr_transfers.attr); + if (ret) + dev_warn(&adapter->dev, "Failed to create sysfs file for tx_complete_cnt= \n"); + + ret =3D sysfs_create_file(adapter->stats->kobj, &dev_attr_bus_errors.attr= ); + if (ret) + dev_warn(&adapter->dev, "Failed to create sysfs file for bus_errors\n"); + + ret =3D sysfs_create_file(adapter->stats->kobj, &dev_attr_nacks.attr); + if (ret) + dev_warn(&adapter->dev, "Failed to create sysfs file for nacks\n"); + + ret =3D sysfs_create_file(adapter->stats->kobj, &dev_attr_recovery_succes= ses.attr); + if (ret) + dev_warn(&adapter->dev, "Failed to create sysfs file for recovery_succes= ses\n"); + + ret =3D sysfs_create_file(adapter->stats->kobj, &dev_attr_recovery_failur= es.attr); + if (ret) + dev_warn(&adapter->dev, "Failed to create sysfs file for recovery_failur= es\n"); + + return ret; +} + MODULE_AUTHOR("Frodo Looijaard "); MODULE_AUTHOR("Simon G. Vogl "); MODULE_DESCRIPTION("I2C /dev entries driver"); diff --git a/include/linux/i2c.h b/include/linux/i2c.h index 3eb60a2e9e61..4547c4c782b6 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -20,6 +20,7 @@ #include /* for Host Notify IRQ */ #include /* for struct device_node */ #include /* for swab16 */ +#include #include =20 extern struct bus_type i2c_bus_type; @@ -297,6 +298,20 @@ struct i2c_driver { }; #define to_i2c_driver(d) container_of(d, struct i2c_driver, driver) =20 +int i2c_adapter_create_stats_directory(struct i2c_adapter *adapter); + +void i2c_adapter_stats_register_counter(struct i2c_adapter *adapter, + const char *counter_name, void *data_source); + +struct i2c_client_stats { + struct kobject kobj; + int address; + u64 messages; + u64 bus_errors; + u64 nacks; + u64 timeouts; +}; + /** * struct i2c_client - represent an I2C slave device * @flags: see I2C_CLIENT_* for possible flags @@ -342,6 +357,7 @@ struct i2c_client { i2c_slave_cb_t slave_cb; /* callback for slave mode */ #endif void *devres_group_id; /* ID of probe devres group */ + struct i2c_client_stats *stats; /* i2c client stats */ }; #define to_i2c_client(d) container_of(d, struct i2c_client, dev) =20 @@ -684,6 +700,30 @@ struct i2c_adapter_quirks { u16 max_comb_2nd_msg_len; }; =20 +/** + * I2C adapter statistics + */ +struct i2c_adapter_stats { + struct kobject *kobj; + + u64 transfers; + u64 bus_errors; + u64 nacks; + u64 recovery_successes; + u64 recovery_failures; + u64 timeouts; + + struct rb_root i2c_client_stats_root; +}; + +struct i2c_client_stats_rbnode { + struct i2c_client_stats *stats; + struct rb_node rb_node; +}; + +struct i2c_client_stats *__get_or_create_i2c_client_stats(struct i2c_adapt= er_stats *stats, + int i2c_addr); + /* enforce max_num_msgs =3D 2 and use max_comb_*_len for length checks */ #define I2C_AQ_COMB BIT(0) /* first combined message must be write */ @@ -738,6 +778,7 @@ struct i2c_adapter { =20 struct irq_domain *host_notify_domain; struct regulator *bus_regulator; + struct i2c_adapter_stats *stats; }; #define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev) =20 --=20 2.37.1.559.g78731f0fdb-goog