diff mbox series

[net-next,1/7] sfc: initial debugfs implementation

Message ID 9454bbffe8de24c0afcc6b307057e927ffaec6ca.1702314695.git.ecree.xilinx@gmail.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series sfc: initial debugfs support | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1116 this patch: 1116
netdev/cc_maintainers success CCed 7 of 7 maintainers
netdev/build_clang success Errors and warnings before: 1142 this patch: 1142
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 1147 this patch: 1147
netdev/checkpatch warning CHECK: Macro argument '_name' may be better as '(_name)' to avoid precedence issues CHECK: Please use a blank line after function/struct/union/enum declarations WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 82 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 55 this patch: 55
netdev/source_inline success Was 0 now: 0

Commit Message

edward.cree@amd.com Dec. 11, 2023, 5:18 p.m. UTC
From: Edward Cree <ecree.xilinx@gmail.com>

Just a handful of nodes, including one enum with a string table for
 pretty printing the values.

Reviewed-by: Jonathan Cooper <jonathan.s.cooper@amd.com>
Signed-off-by: Edward Cree <ecree.xilinx@gmail.com>
---
 drivers/net/ethernet/sfc/Makefile       |   1 +
 drivers/net/ethernet/sfc/debugfs.c      | 234 ++++++++++++++++++++++++
 drivers/net/ethernet/sfc/debugfs.h      |  56 ++++++
 drivers/net/ethernet/sfc/ef10.c         |  10 +
 drivers/net/ethernet/sfc/ef100_netdev.c |   7 +
 drivers/net/ethernet/sfc/ef100_nic.c    |   8 +
 drivers/net/ethernet/sfc/efx.c          |  15 +-
 drivers/net/ethernet/sfc/efx_common.c   |   7 +
 drivers/net/ethernet/sfc/net_driver.h   |  29 +++
 9 files changed, 366 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/ethernet/sfc/debugfs.c
 create mode 100644 drivers/net/ethernet/sfc/debugfs.h

Comments

Nelson, Shannon Dec. 15, 2023, 12:05 a.m. UTC | #1
On 12/11/2023 9:18 AM, edward.cree@amd.com wrote:
> 
> From: Edward Cree <ecree.xilinx@gmail.com>
> 

Hi Ed, a few minor nits I thought I'd call out.
Cheers,
sln

> Just a handful of nodes, including one enum with a string table for
>   pretty printing the values.

It would be nice to have a couple of example paths listed here

> 
> Reviewed-by: Jonathan Cooper <jonathan.s.cooper@amd.com>
> Signed-off-by: Edward Cree <ecree.xilinx@gmail.com>
> ---
>   drivers/net/ethernet/sfc/Makefile       |   1 +
>   drivers/net/ethernet/sfc/debugfs.c      | 234 ++++++++++++++++++++++++
>   drivers/net/ethernet/sfc/debugfs.h      |  56 ++++++
>   drivers/net/ethernet/sfc/ef10.c         |  10 +
>   drivers/net/ethernet/sfc/ef100_netdev.c |   7 +
>   drivers/net/ethernet/sfc/ef100_nic.c    |   8 +
>   drivers/net/ethernet/sfc/efx.c          |  15 +-
>   drivers/net/ethernet/sfc/efx_common.c   |   7 +
>   drivers/net/ethernet/sfc/net_driver.h   |  29 +++
>   9 files changed, 366 insertions(+), 1 deletion(-)
>   create mode 100644 drivers/net/ethernet/sfc/debugfs.c
>   create mode 100644 drivers/net/ethernet/sfc/debugfs.h
> 
> diff --git a/drivers/net/ethernet/sfc/Makefile b/drivers/net/ethernet/sfc/Makefile
> index 8f446b9bd5ee..1fbdd04dc2c5 100644
> --- a/drivers/net/ethernet/sfc/Makefile
> +++ b/drivers/net/ethernet/sfc/Makefile
> @@ -12,6 +12,7 @@ sfc-$(CONFIG_SFC_MTD) += mtd.o
>   sfc-$(CONFIG_SFC_SRIOV)        += sriov.o ef10_sriov.o ef100_sriov.o ef100_rep.o \
>                              mae.o tc.o tc_bindings.o tc_counters.o \
>                              tc_encap_actions.o tc_conntrack.o
> +sfc-$(CONFIG_DEBUG_FS) += debugfs.o

Just as you are using #ifdef CONFIG_DEBUG_FS in your debugfs.h, you 
could use it in your debugfs.c and simply add the .o file to your sfc-y 
list here.

> 
>   obj-$(CONFIG_SFC)      += sfc.o
> 
> diff --git a/drivers/net/ethernet/sfc/debugfs.c b/drivers/net/ethernet/sfc/debugfs.c
> new file mode 100644
> index 000000000000..cf800addb4ff
> --- /dev/null
> +++ b/drivers/net/ethernet/sfc/debugfs.c
> @@ -0,0 +1,234 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/****************************************************************************
> + * Driver for Solarflare network controllers and boards
> + * Copyright 2023, Advanced Micro Devices, Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published
> + * by the Free Software Foundation, incorporated herein by reference.
> + */
> +
> +#include "debugfs.h"
> +#include <linux/module.h>
> +#include <linux/debugfs.h>
> +#include <linux/dcache.h>
> +#include <linux/seq_file.h>
> +
> +/* Maximum length for a name component or symlink target */
> +#define EFX_DEBUGFS_NAME_LEN 32
> +
> +/* Top-level debug directory ([/sys/kernel]/debug/sfc) */
> +static struct dentry *efx_debug_root;
> +
> +/* "cards" directory ([/sys/kernel]/debug/sfc/cards) */
> +static struct dentry *efx_debug_cards;
> +
> +/**
> + * efx_init_debugfs_netdev - create debugfs sym-link for net device
> + * @net_dev:           Net device
> + *
> + * Create sym-link named after @net_dev to the debugfs directories for the
> + * corresponding NIC.  The sym-link must be cleaned up using
> + * efx_fini_debugfs_netdev().
> + *
> + * Return: a negative error code or 0 on success.
> + */
> +static int efx_init_debugfs_netdev(struct net_device *net_dev)
> +{
> +       struct efx_nic *efx = efx_netdev_priv(net_dev);
> +       char target[EFX_DEBUGFS_NAME_LEN];
> +       char name[EFX_DEBUGFS_NAME_LEN];
> +
> +       if (snprintf(name, sizeof(name), "nic_%s", net_dev->name) >=
> +                       sizeof(name))
> +               return -ENAMETOOLONG;
> +       if (snprintf(target, sizeof(target), "cards/%s", pci_name(efx->pci_dev))
> +           >= sizeof(target))
> +               return -ENAMETOOLONG;
> +       efx->debug_symlink = debugfs_create_symlink(name,
> +                                                   efx_debug_root, target);
> +       if (IS_ERR_OR_NULL(efx->debug_symlink))
> +               return efx->debug_symlink ? PTR_ERR(efx->debug_symlink) :
> +                                           -ENOMEM;
> +
> +       return 0;
> +}
> +
> +/**
> + * efx_fini_debugfs_netdev - remove debugfs sym-link for net device
> + * @net_dev:           Net device
> + *
> + * Remove sym-link created for @net_dev by efx_init_debugfs_netdev().
> + */
> +void efx_fini_debugfs_netdev(struct net_device *net_dev)
> +{
> +       struct efx_nic *efx = efx_netdev_priv(net_dev);
> +
> +       debugfs_remove(efx->debug_symlink);
> +       efx->debug_symlink = NULL;
> +}
> +
> +/* replace debugfs sym-links on net device rename */
> +void efx_update_debugfs_netdev(struct efx_nic *efx)
> +{
> +       mutex_lock(&efx->debugfs_symlink_mutex);
> +       if (efx->debug_symlink)
> +               efx_fini_debugfs_netdev(efx->net_dev);
> +       efx_init_debugfs_netdev(efx->net_dev);
> +       mutex_unlock(&efx->debugfs_symlink_mutex);
> +}

How necessary is this netdev symlink?  This seems like a bunch of extra 
maintenance.

> +
> +static int efx_debugfs_enum_read(struct seq_file *s, void *d)
> +{
> +       struct efx_debugfs_enum_data *data = s->private;
> +       u64 value = 0;
> +       size_t len;
> +
> +       len = min(data->vlen, sizeof(value));
> +#ifdef BIG_ENDIAN
> +       /* If data->value is narrower than u64, we need to copy into the
> +        * far end of value, as that's where the low bits live.
> +        */
> +       memcpy(((void *)&value) + sizeof(value) - len, data->value, len);
> +#else
> +       memcpy(&value, data->value, len);
> +#endif
> +       seq_printf(s, "%#llx => %s\n", value,
> +                  value < data->max ? data->names[value] : "(invalid)");
> +       return 0;
> +}
> +
> +static int efx_debugfs_enum_open(struct inode *inode, struct file *f)
> +{
> +       struct efx_debugfs_enum_data *data = inode->i_private;
> +
> +       return single_open(f, efx_debugfs_enum_read, data);
> +}
> +
> +static const struct file_operations efx_debugfs_enum_ops = {
> +       .owner = THIS_MODULE,
> +       .open = efx_debugfs_enum_open,
> +       .release = single_release,
> +       .read = seq_read,
> +       .llseek = seq_lseek,
> +};
> +
> +static void efx_debugfs_create_enum(const char *name, umode_t mode,
> +                                   struct dentry *parent,
> +                                   struct efx_debugfs_enum_data *data)
> +{
> +       debugfs_create_file(name, mode, parent, data, &efx_debugfs_enum_ops);
> +}
> +
> +static const char *const efx_interrupt_mode_names[] = {
> +       [EFX_INT_MODE_MSIX]   = "MSI-X",
> +       [EFX_INT_MODE_MSI]    = "MSI",
> +       [EFX_INT_MODE_LEGACY] = "legacy",
> +};
> +
> +#define EFX_DEBUGFS_EFX(_type, _name)  \
> +       debugfs_create_##_type(#_name, 0444, efx->debug_dir, &efx->_name)
> +
> +/* Create basic debugfs parameter files for an Efx NIC */
> +static void efx_init_debugfs_nic_files(struct efx_nic *efx)
> +{
> +       EFX_DEBUGFS_EFX(x32, rx_dma_len);
> +       EFX_DEBUGFS_EFX(x32, rx_buffer_order);
> +       EFX_DEBUGFS_EFX(x32, rx_buffer_truesize);
> +       efx->debug_interrupt_mode.max = ARRAY_SIZE(efx_interrupt_mode_names);
> +       efx->debug_interrupt_mode.names = efx_interrupt_mode_names;
> +       efx->debug_interrupt_mode.vlen = sizeof(efx->interrupt_mode);
> +       efx->debug_interrupt_mode.value = &efx->interrupt_mode;
> +       efx_debugfs_create_enum("interrupt_mode", 0444, efx->debug_dir,
> +                               &efx->debug_interrupt_mode);
> +}
> +
> +/**
> + * efx_init_debugfs_nic - create debugfs directory for NIC
> + * @efx:               Efx NIC
> + *
> + * Create debugfs directory containing parameter-files for @efx.
> + * The directory must be cleaned up using efx_fini_debugfs_nic().
> + *
> + * Return: a negative error code or 0 on success.
> + */
> +int efx_init_debugfs_nic(struct efx_nic *efx)
> +{
> +       /* Create directory */
> +       efx->debug_dir = debugfs_create_dir(pci_name(efx->pci_dev),
> +                                           efx_debug_cards);
> +       if (!efx->debug_dir)
> +               return -ENOMEM;
> +       efx_init_debugfs_nic_files(efx);
> +       return 0;
> +}
> +
> +/**
> + * efx_fini_debugfs_nic - remove debugfs directory for NIC
> + * @efx:               Efx NIC
> + *
> + * Remove debugfs directory created for @efx by efx_init_debugfs_nic().
> + */
> +void efx_fini_debugfs_nic(struct efx_nic *efx)
> +{
> +       debugfs_remove_recursive(efx->debug_dir);
> +       efx->debug_dir = NULL;
> +}
> +
> +/**
> + * efx_init_debugfs - create debugfs directories for sfc driver
> + *
> + * Create debugfs directories "sfc" and "sfc/cards".  This must be
> + * called before any of the other functions that create debugfs
> + * directories.  The directories must be cleaned up using
> + * efx_fini_debugfs().
> + *
> + * Return: a negative error code or 0 on success.
> + */
> +int efx_init_debugfs(void)
> +{
> +       int rc;
> +
> +       /* Create top-level directory */
> +       efx_debug_root = debugfs_create_dir(KBUILD_MODNAME, NULL);
> +       if (!efx_debug_root) {
> +               pr_err("debugfs_create_dir %s failed.\n", KBUILD_MODNAME);
> +               rc = -ENOMEM;
> +               goto err;
> +       } else if (IS_ERR(efx_debug_root)) {
> +               rc = PTR_ERR(efx_debug_root);
> +               pr_err("debugfs_create_dir %s failed, rc=%d.\n",
> +                      KBUILD_MODNAME, rc);
> +               goto err;
> +       }
> +
> +       /* Create "cards" directory */
> +       efx_debug_cards = debugfs_create_dir("cards", efx_debug_root);
> +       if (!efx_debug_cards) {
> +               pr_err("debugfs_create_dir cards failed.\n");
> +               rc = -ENOMEM;
> +               goto err;
> +       } else if (IS_ERR(efx_debug_cards)) {
> +               rc = PTR_ERR(efx_debug_cards);
> +               pr_err("debugfs_create_dir cards failed, rc=%d.\n", rc);
> +               goto err;
> +       }
> +
> +       return 0;
> +
> +err:
> +       efx_fini_debugfs();
> +       return rc;
> +}
> +
> +/**
> + * efx_fini_debugfs - remove debugfs directories for sfc driver
> + *
> + * Remove directories created by efx_init_debugfs().
> + */
> +void efx_fini_debugfs(void)
> +{
> +       debugfs_remove_recursive(efx_debug_root);
> +       efx_debug_cards = NULL;
> +       efx_debug_root = NULL;
> +}
> diff --git a/drivers/net/ethernet/sfc/debugfs.h b/drivers/net/ethernet/sfc/debugfs.h
> new file mode 100644
> index 000000000000..1fe40fbffa5e
> --- /dev/null
> +++ b/drivers/net/ethernet/sfc/debugfs.h
> @@ -0,0 +1,56 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/****************************************************************************
> + * Driver for Solarflare network controllers and boards
> + * Copyright 2023, Advanced Micro Devices, Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published
> + * by the Free Software Foundation, incorporated herein by reference.
> + */
> +
> +#ifndef EFX_DEBUGFS_H
> +#define EFX_DEBUGFS_H
> +#include "net_driver.h"
> +
> +#ifdef CONFIG_DEBUG_FS
> +
> +/**
> + * DOC: Directory layout for sfc debugfs
> + *
> + * At top level ([/sys/kernel]/debug/sfc) are per-netdev symlinks "nic_$name"
> + * and the "cards" directory.  For each PCI device to which the driver has
> + * bound and created a &struct efx_nic, there is a directory &efx_nic.debug_dir
> + * in "cards" whose name is the PCI address of the device; it is to this
> + * directory that the netdev symlink points.
> + */
> +
> +void efx_fini_debugfs_netdev(struct net_device *net_dev);
> +void efx_update_debugfs_netdev(struct efx_nic *efx);
> +
> +int efx_init_debugfs_nic(struct efx_nic *efx);
> +void efx_fini_debugfs_nic(struct efx_nic *efx);
> +
> +int efx_init_debugfs(void);
> +void efx_fini_debugfs(void);
> +
> +#else /* CONFIG_DEBUG_FS */
> +
> +static inline void efx_fini_debugfs_netdev(struct net_device *net_dev) {}
> +
> +static inline void efx_update_debugfs_netdev(struct efx_nic *efx) {}
> +
> +static inline int efx_init_debugfs_nic(struct efx_nic *efx)
> +{
> +       return 0;
> +}
> +static inline void efx_fini_debugfs_nic(struct efx_nic *efx) {}
> +
> +static inline int efx_init_debugfs(void)
> +{
> +       return 0;
> +}
> +static inline void efx_fini_debugfs(void) {}
> +
> +#endif /* CONFIG_DEBUG_FS */
> +
> +#endif /* EFX_DEBUGFS_H */
> diff --git a/drivers/net/ethernet/sfc/ef10.c b/drivers/net/ethernet/sfc/ef10.c
> index 6dfa062feebc..58e18fc92093 100644
> --- a/drivers/net/ethernet/sfc/ef10.c
> +++ b/drivers/net/ethernet/sfc/ef10.c
> @@ -19,6 +19,7 @@
>   #include "workarounds.h"
>   #include "selftest.h"
>   #include "ef10_sriov.h"
> +#include "debugfs.h"
>   #include <linux/in.h>
>   #include <linux/jhash.h>
>   #include <linux/wait.h>
> @@ -580,6 +581,13 @@ static int efx_ef10_probe(struct efx_nic *efx)
>          if (rc)
>                  goto fail3;
> 
> +       /* Populate debugfs */
> +#ifdef CONFIG_DEBUG_FS
> +       rc = efx_init_debugfs_nic(efx);
> +       if (rc)
> +               pci_err(efx->pci_dev, "failed to init device debugfs\n");
> +#endif

I don't think you need the ifdef here because you have the static 
version defined in debugfs.h

> +
>          rc = device_create_file(&efx->pci_dev->dev,
>                                  &dev_attr_link_control_flag);
>          if (rc)
> @@ -693,6 +701,7 @@ static int efx_ef10_probe(struct efx_nic *efx)
>   fail4:
>          device_remove_file(&efx->pci_dev->dev, &dev_attr_link_control_flag);
>   fail3:
> +       efx_fini_debugfs_nic(efx);
>          efx_mcdi_detach(efx);
> 
>          mutex_lock(&nic_data->udp_tunnels_lock);
> @@ -962,6 +971,7 @@ static void efx_ef10_remove(struct efx_nic *efx)
>          device_remove_file(&efx->pci_dev->dev, &dev_attr_link_control_flag);
> 
>          efx_mcdi_detach(efx);
> +       efx_fini_debugfs_nic(efx);
> 
>          memset(nic_data->udp_tunnels, 0, sizeof(nic_data->udp_tunnels));
>          mutex_lock(&nic_data->udp_tunnels_lock);
> diff --git a/drivers/net/ethernet/sfc/ef100_netdev.c b/drivers/net/ethernet/sfc/ef100_netdev.c
> index 7f7d560cb2b4..e844d57754b7 100644
> --- a/drivers/net/ethernet/sfc/ef100_netdev.c
> +++ b/drivers/net/ethernet/sfc/ef100_netdev.c
> @@ -26,10 +26,12 @@
>   #include "tc_bindings.h"
>   #include "tc_encap_actions.h"
>   #include "efx_devlink.h"
> +#include "debugfs.h"
> 
>   static void ef100_update_name(struct efx_nic *efx)
>   {
>          strcpy(efx->name, efx->net_dev->name);
> +       efx_update_debugfs_netdev(efx);
>   }
> 
>   static int ef100_alloc_vis(struct efx_nic *efx, unsigned int *allocated_vis)
> @@ -405,6 +407,11 @@ void ef100_remove_netdev(struct efx_probe_data *probe_data)
>          ef100_pf_unset_devlink_port(efx);
>          efx_fini_tc(efx);
>   #endif
> +#ifdef CONFIG_DEBUG_FS
> +       mutex_lock(&efx->debugfs_symlink_mutex);
> +       efx_fini_debugfs_netdev(efx->net_dev);
> +       mutex_unlock(&efx->debugfs_symlink_mutex);
> +#endif

Can you do the mutex dance inside of efx_fini_debugfs_netdev() and then 
not need the ifdef here?

> 
>          down_write(&efx->filter_sem);
>          efx_mcdi_filter_table_remove(efx);
> diff --git a/drivers/net/ethernet/sfc/ef100_nic.c b/drivers/net/ethernet/sfc/ef100_nic.c
> index 6da06931187d..ad378aa05dc3 100644
> --- a/drivers/net/ethernet/sfc/ef100_nic.c
> +++ b/drivers/net/ethernet/sfc/ef100_nic.c
> @@ -27,6 +27,7 @@
>   #include "tc.h"
>   #include "mae.h"
>   #include "rx_common.h"
> +#include "debugfs.h"
> 
>   #define EF100_MAX_VIS 4096
>   #define EF100_NUM_MCDI_BUFFERS 1
> @@ -1077,6 +1078,12 @@ static int ef100_probe_main(struct efx_nic *efx)
> 
>          /* Post-IO section. */
> 
> +       /* Populate debugfs */
> +#ifdef CONFIG_DEBUG_FS
> +       rc = efx_init_debugfs_nic(efx);
> +       if (rc)
> +               pci_err(efx->pci_dev, "failed to init device debugfs\n");
> +#endif

Shouldn't need the ifdef

>          rc = efx_mcdi_init(efx);
>          if (rc)
>                  goto fail;
> @@ -1213,6 +1220,7 @@ void ef100_remove(struct efx_nic *efx)
> 
>          efx_mcdi_detach(efx);
>          efx_mcdi_fini(efx);
> +       efx_fini_debugfs_nic(efx);
>          if (nic_data)
>                  efx_nic_free_buffer(efx, &nic_data->mcdi_buf);
>          kfree(nic_data);
> diff --git a/drivers/net/ethernet/sfc/efx.c b/drivers/net/ethernet/sfc/efx.c
> index 19f4b4d0b851..9266c7b5b4fd 100644
> --- a/drivers/net/ethernet/sfc/efx.c
> +++ b/drivers/net/ethernet/sfc/efx.c
> @@ -33,6 +33,7 @@
>   #include "selftest.h"
>   #include "sriov.h"
>   #include "efx_devlink.h"
> +#include "debugfs.h"
> 
>   #include "mcdi_port_common.h"
>   #include "mcdi_pcol.h"
> @@ -401,6 +402,11 @@ static void efx_remove_all(struct efx_nic *efx)
>   #endif
>          efx_remove_port(efx);
>          efx_remove_nic(efx);
> +#ifdef CONFIG_DEBUG_FS
> +       mutex_lock(&efx->debugfs_symlink_mutex);
> +       efx_fini_debugfs_netdev(efx->net_dev);
> +       mutex_unlock(&efx->debugfs_symlink_mutex);
> +#endif

Same mutex comment

>   }
> 
>   /**************************************************************************
> @@ -667,6 +673,7 @@ static void efx_update_name(struct efx_nic *efx)
>          strcpy(efx->name, efx->net_dev->name);
>          efx_mtd_rename(efx);
>          efx_set_channel_names(efx);
> +       efx_update_debugfs_netdev(efx);
>   }
> 
>   static int efx_netdev_event(struct notifier_block *this,
> @@ -1319,6 +1326,10 @@ static int __init efx_init_module(void)
> 
>          printk(KERN_INFO "Solarflare NET driver\n");
> 
> +       rc = efx_init_debugfs();
> +       if (rc)
> +               goto err_debugfs;
> +
>          rc = register_netdevice_notifier(&efx_netdev_notifier);
>          if (rc)
>                  goto err_notifier;
> @@ -1344,6 +1355,8 @@ static int __init efx_init_module(void)
>    err_reset:
>          unregister_netdevice_notifier(&efx_netdev_notifier);
>    err_notifier:
> +       efx_fini_debugfs();
> + err_debugfs:
>          return rc;
>   }
> 
> @@ -1355,7 +1368,7 @@ static void __exit efx_exit_module(void)
>          pci_unregister_driver(&efx_pci_driver);
>          efx_destroy_reset_workqueue();
>          unregister_netdevice_notifier(&efx_netdev_notifier);
> -
> +       efx_fini_debugfs();
>   }
> 
>   module_init(efx_init_module);
> diff --git a/drivers/net/ethernet/sfc/efx_common.c b/drivers/net/ethernet/sfc/efx_common.c
> index 175bd9cdfdac..7a9d6b6b66e5 100644
> --- a/drivers/net/ethernet/sfc/efx_common.c
> +++ b/drivers/net/ethernet/sfc/efx_common.c
> @@ -1022,6 +1022,9 @@ int efx_init_struct(struct efx_nic *efx, struct pci_dev *pci_dev)
>          INIT_WORK(&efx->mac_work, efx_mac_work);
>          init_waitqueue_head(&efx->flush_wq);
> 
> +#ifdef CONFIG_DEBUG_FS
> +       mutex_init(&efx->debugfs_symlink_mutex);
> +#endif

Can we do this without the ifdefs in the mainline code?
(okay, I'll stop grinding on that one for now)

>          efx->tx_queues_per_channel = 1;
>          efx->rxq_entries = EFX_DEFAULT_DMAQ_SIZE;
>          efx->txq_entries = EFX_DEFAULT_DMAQ_SIZE;
> @@ -1056,6 +1059,10 @@ void efx_fini_struct(struct efx_nic *efx)
> 
>          efx_fini_channels(efx);
> 
> +#ifdef CONFIG_DEBUG_FS
> +       mutex_destroy(&efx->debugfs_symlink_mutex);
> +#endif
> +
>          kfree(efx->vpd_sn);
> 
>          if (efx->workqueue) {
> diff --git a/drivers/net/ethernet/sfc/net_driver.h b/drivers/net/ethernet/sfc/net_driver.h
> index 27d86e90a3bb..961e2db31c6e 100644
> --- a/drivers/net/ethernet/sfc/net_driver.h
> +++ b/drivers/net/ethernet/sfc/net_driver.h
> @@ -107,6 +107,24 @@ struct hwtstamp_config;
> 
>   struct efx_self_tests;
> 
> +/**
> + * struct efx_debugfs_enum_data - information for pretty-printing enums
> + * @value: pointer to the actual enum
> + * @vlen: sizeof the enum
> + * @names: array of names of enumerated values.  May contain some %NULL entries.
> + * @max: number of entries in @names, typically from ARRAY_SIZE()
> + *
> + * Where a driver struct contains an enum member which we wish to expose in
> + * debugfs, we also embed an instance of this struct, which
> + * efx_debugfs_enum_read() uses to pretty-print the value.
> + */
> +struct efx_debugfs_enum_data {
> +       void *value;
> +       size_t vlen;
> +       const char *const *names;
> +       unsigned int max;
> +};
> +
>   /**
>    * struct efx_buffer - A general-purpose DMA buffer
>    * @addr: host base address of the buffer
> @@ -1123,6 +1141,17 @@ struct efx_nic {
>          u32 rps_next_id;
>   #endif
> 
> +#ifdef CONFIG_DEBUG_FS
> +       /** @debug_dir: NIC debugfs directory */
> +       struct dentry *debug_dir;
> +       /** @debug_symlink: NIC debugfs symlink (``nic_eth%d``) */
> +       struct dentry *debug_symlink;
> +       /** @debug_interrupt_mode: debugfs details for printing @interrupt_mode */
> +       struct efx_debugfs_enum_data debug_interrupt_mode;
> +       /** @debugfs_symlink_mutex: protects debugfs @debug_symlink */
> +       struct mutex debugfs_symlink_mutex;
> +#endif
> +
>          atomic_t active_queues;
>          atomic_t rxq_flush_pending;
>          atomic_t rxq_flush_outstanding;
>
Edward Cree Feb. 8, 2024, 9:25 p.m. UTC | #2
On 15/12/2023 00:05, Nelson, Shannon wrote:
> It would be nice to have a couple of example paths listed here

Sure; added this to v2:
See included DOC: comment for directory structure; leaf nodes are
 rx_dma_len, rx_buffer_order, rx_buffer_truesize and interrupt_mode.
Is that what you had in mind?  Or more like
 "grep -H . /sys/kernel/debug/sfc" output on a running system?

>> +/* replace debugfs sym-links on net device rename */
>> +void efx_update_debugfs_netdev(struct efx_nic *efx)
>> +{
>> +       mutex_lock(&efx->debugfs_symlink_mutex);
>> +       if (efx->debug_symlink)
>> +               efx_fini_debugfs_netdev(efx->net_dev);
>> +       efx_init_debugfs_netdev(efx->net_dev);
>> +       mutex_unlock(&efx->debugfs_symlink_mutex);
>> +}
> 
> How necessary is this netdev symlink?  This seems like a bunch of extra maintenance.

AFAIK we've had it out-of-tree for a very long time and not found it
 to need any real maintenance effort.  And while it's not strictly
 necessary, it is fairly convenient.

>> +       /* Populate debugfs */
>> +#ifdef CONFIG_DEBUG_FS
>> +       rc = efx_init_debugfs_nic(efx);
>> +       if (rc)
>> +               pci_err(efx->pci_dev, "failed to init device debugfs\n");
>> +#endif
> 
> I don't think you need the ifdef here because you have the static version defined in debugfs.h

You're right; I'll fix these.

>> +#ifdef CONFIG_DEBUG_FS
>> +       mutex_lock(&efx->debugfs_symlink_mutex);
>> +       efx_fini_debugfs_netdev(efx->net_dev);
>> +       mutex_unlock(&efx->debugfs_symlink_mutex);
>> +#endif
> 
> Can you do the mutex dance inside of efx_fini_debugfs_netdev() and then not need the ifdef here?

Yes, although I needed to refactor slightly because it's also
 called by efx_update_debugfs_netdev() which is already holding
 the mutex.

>> diff --git a/drivers/net/ethernet/sfc/efx_common.c b/drivers/net/ethernet/sfc/efx_common.c
>> index 175bd9cdfdac..7a9d6b6b66e5 100644
>> --- a/drivers/net/ethernet/sfc/efx_common.c
>> +++ b/drivers/net/ethernet/sfc/efx_common.c
>> @@ -1022,6 +1022,9 @@ int efx_init_struct(struct efx_nic *efx, struct pci_dev *pci_dev)
>>          INIT_WORK(&efx->mac_work, efx_mac_work);
>>          init_waitqueue_head(&efx->flush_wq);
>>
>> +#ifdef CONFIG_DEBUG_FS
>> +       mutex_init(&efx->debugfs_symlink_mutex);
>> +#endif
> 
> Can we do this without the ifdefs in the mainline code?
> (okay, I'll stop grinding on that one for now)

Ifdefs for struct members that may not exist seems to be the
 existing pattern in efx_init_struct and efx_fini_struct, so
 I'd rather leave this here than wrap this single call in an
 efx_init_struct_debugfs function.

Thanks for the review!
diff mbox series

Patch

diff --git a/drivers/net/ethernet/sfc/Makefile b/drivers/net/ethernet/sfc/Makefile
index 8f446b9bd5ee..1fbdd04dc2c5 100644
--- a/drivers/net/ethernet/sfc/Makefile
+++ b/drivers/net/ethernet/sfc/Makefile
@@ -12,6 +12,7 @@  sfc-$(CONFIG_SFC_MTD)	+= mtd.o
 sfc-$(CONFIG_SFC_SRIOV)	+= sriov.o ef10_sriov.o ef100_sriov.o ef100_rep.o \
                            mae.o tc.o tc_bindings.o tc_counters.o \
                            tc_encap_actions.o tc_conntrack.o
+sfc-$(CONFIG_DEBUG_FS)	+= debugfs.o
 
 obj-$(CONFIG_SFC)	+= sfc.o
 
diff --git a/drivers/net/ethernet/sfc/debugfs.c b/drivers/net/ethernet/sfc/debugfs.c
new file mode 100644
index 000000000000..cf800addb4ff
--- /dev/null
+++ b/drivers/net/ethernet/sfc/debugfs.c
@@ -0,0 +1,234 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/****************************************************************************
+ * Driver for Solarflare network controllers and boards
+ * Copyright 2023, Advanced Micro Devices, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation, incorporated herein by reference.
+ */
+
+#include "debugfs.h"
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/dcache.h>
+#include <linux/seq_file.h>
+
+/* Maximum length for a name component or symlink target */
+#define EFX_DEBUGFS_NAME_LEN 32
+
+/* Top-level debug directory ([/sys/kernel]/debug/sfc) */
+static struct dentry *efx_debug_root;
+
+/* "cards" directory ([/sys/kernel]/debug/sfc/cards) */
+static struct dentry *efx_debug_cards;
+
+/**
+ * efx_init_debugfs_netdev - create debugfs sym-link for net device
+ * @net_dev:		Net device
+ *
+ * Create sym-link named after @net_dev to the debugfs directories for the
+ * corresponding NIC.  The sym-link must be cleaned up using
+ * efx_fini_debugfs_netdev().
+ *
+ * Return: a negative error code or 0 on success.
+ */
+static int efx_init_debugfs_netdev(struct net_device *net_dev)
+{
+	struct efx_nic *efx = efx_netdev_priv(net_dev);
+	char target[EFX_DEBUGFS_NAME_LEN];
+	char name[EFX_DEBUGFS_NAME_LEN];
+
+	if (snprintf(name, sizeof(name), "nic_%s", net_dev->name) >=
+			sizeof(name))
+		return -ENAMETOOLONG;
+	if (snprintf(target, sizeof(target), "cards/%s", pci_name(efx->pci_dev))
+	    >= sizeof(target))
+		return -ENAMETOOLONG;
+	efx->debug_symlink = debugfs_create_symlink(name,
+						    efx_debug_root, target);
+	if (IS_ERR_OR_NULL(efx->debug_symlink))
+		return efx->debug_symlink ? PTR_ERR(efx->debug_symlink) :
+					    -ENOMEM;
+
+	return 0;
+}
+
+/**
+ * efx_fini_debugfs_netdev - remove debugfs sym-link for net device
+ * @net_dev:		Net device
+ *
+ * Remove sym-link created for @net_dev by efx_init_debugfs_netdev().
+ */
+void efx_fini_debugfs_netdev(struct net_device *net_dev)
+{
+	struct efx_nic *efx = efx_netdev_priv(net_dev);
+
+	debugfs_remove(efx->debug_symlink);
+	efx->debug_symlink = NULL;
+}
+
+/* replace debugfs sym-links on net device rename */
+void efx_update_debugfs_netdev(struct efx_nic *efx)
+{
+	mutex_lock(&efx->debugfs_symlink_mutex);
+	if (efx->debug_symlink)
+		efx_fini_debugfs_netdev(efx->net_dev);
+	efx_init_debugfs_netdev(efx->net_dev);
+	mutex_unlock(&efx->debugfs_symlink_mutex);
+}
+
+static int efx_debugfs_enum_read(struct seq_file *s, void *d)
+{
+	struct efx_debugfs_enum_data *data = s->private;
+	u64 value = 0;
+	size_t len;
+
+	len = min(data->vlen, sizeof(value));
+#ifdef BIG_ENDIAN
+	/* If data->value is narrower than u64, we need to copy into the
+	 * far end of value, as that's where the low bits live.
+	 */
+	memcpy(((void *)&value) + sizeof(value) - len, data->value, len);
+#else
+	memcpy(&value, data->value, len);
+#endif
+	seq_printf(s, "%#llx => %s\n", value,
+		   value < data->max ? data->names[value] : "(invalid)");
+	return 0;
+}
+
+static int efx_debugfs_enum_open(struct inode *inode, struct file *f)
+{
+	struct efx_debugfs_enum_data *data = inode->i_private;
+
+	return single_open(f, efx_debugfs_enum_read, data);
+}
+
+static const struct file_operations efx_debugfs_enum_ops = {
+	.owner = THIS_MODULE,
+	.open = efx_debugfs_enum_open,
+	.release = single_release,
+	.read = seq_read,
+	.llseek = seq_lseek,
+};
+
+static void efx_debugfs_create_enum(const char *name, umode_t mode,
+				    struct dentry *parent,
+				    struct efx_debugfs_enum_data *data)
+{
+	debugfs_create_file(name, mode, parent, data, &efx_debugfs_enum_ops);
+}
+
+static const char *const efx_interrupt_mode_names[] = {
+	[EFX_INT_MODE_MSIX]   = "MSI-X",
+	[EFX_INT_MODE_MSI]    = "MSI",
+	[EFX_INT_MODE_LEGACY] = "legacy",
+};
+
+#define EFX_DEBUGFS_EFX(_type, _name)	\
+	debugfs_create_##_type(#_name, 0444, efx->debug_dir, &efx->_name)
+
+/* Create basic debugfs parameter files for an Efx NIC */
+static void efx_init_debugfs_nic_files(struct efx_nic *efx)
+{
+	EFX_DEBUGFS_EFX(x32, rx_dma_len);
+	EFX_DEBUGFS_EFX(x32, rx_buffer_order);
+	EFX_DEBUGFS_EFX(x32, rx_buffer_truesize);
+	efx->debug_interrupt_mode.max = ARRAY_SIZE(efx_interrupt_mode_names);
+	efx->debug_interrupt_mode.names = efx_interrupt_mode_names;
+	efx->debug_interrupt_mode.vlen = sizeof(efx->interrupt_mode);
+	efx->debug_interrupt_mode.value = &efx->interrupt_mode;
+	efx_debugfs_create_enum("interrupt_mode", 0444, efx->debug_dir,
+				&efx->debug_interrupt_mode);
+}
+
+/**
+ * efx_init_debugfs_nic - create debugfs directory for NIC
+ * @efx:		Efx NIC
+ *
+ * Create debugfs directory containing parameter-files for @efx.
+ * The directory must be cleaned up using efx_fini_debugfs_nic().
+ *
+ * Return: a negative error code or 0 on success.
+ */
+int efx_init_debugfs_nic(struct efx_nic *efx)
+{
+	/* Create directory */
+	efx->debug_dir = debugfs_create_dir(pci_name(efx->pci_dev),
+					    efx_debug_cards);
+	if (!efx->debug_dir)
+		return -ENOMEM;
+	efx_init_debugfs_nic_files(efx);
+	return 0;
+}
+
+/**
+ * efx_fini_debugfs_nic - remove debugfs directory for NIC
+ * @efx:		Efx NIC
+ *
+ * Remove debugfs directory created for @efx by efx_init_debugfs_nic().
+ */
+void efx_fini_debugfs_nic(struct efx_nic *efx)
+{
+	debugfs_remove_recursive(efx->debug_dir);
+	efx->debug_dir = NULL;
+}
+
+/**
+ * efx_init_debugfs - create debugfs directories for sfc driver
+ *
+ * Create debugfs directories "sfc" and "sfc/cards".  This must be
+ * called before any of the other functions that create debugfs
+ * directories.  The directories must be cleaned up using
+ * efx_fini_debugfs().
+ *
+ * Return: a negative error code or 0 on success.
+ */
+int efx_init_debugfs(void)
+{
+	int rc;
+
+	/* Create top-level directory */
+	efx_debug_root = debugfs_create_dir(KBUILD_MODNAME, NULL);
+	if (!efx_debug_root) {
+		pr_err("debugfs_create_dir %s failed.\n", KBUILD_MODNAME);
+		rc = -ENOMEM;
+		goto err;
+	} else if (IS_ERR(efx_debug_root)) {
+		rc = PTR_ERR(efx_debug_root);
+		pr_err("debugfs_create_dir %s failed, rc=%d.\n",
+		       KBUILD_MODNAME, rc);
+		goto err;
+	}
+
+	/* Create "cards" directory */
+	efx_debug_cards = debugfs_create_dir("cards", efx_debug_root);
+	if (!efx_debug_cards) {
+		pr_err("debugfs_create_dir cards failed.\n");
+		rc = -ENOMEM;
+		goto err;
+	} else if (IS_ERR(efx_debug_cards)) {
+		rc = PTR_ERR(efx_debug_cards);
+		pr_err("debugfs_create_dir cards failed, rc=%d.\n", rc);
+		goto err;
+	}
+
+	return 0;
+
+err:
+	efx_fini_debugfs();
+	return rc;
+}
+
+/**
+ * efx_fini_debugfs - remove debugfs directories for sfc driver
+ *
+ * Remove directories created by efx_init_debugfs().
+ */
+void efx_fini_debugfs(void)
+{
+	debugfs_remove_recursive(efx_debug_root);
+	efx_debug_cards = NULL;
+	efx_debug_root = NULL;
+}
diff --git a/drivers/net/ethernet/sfc/debugfs.h b/drivers/net/ethernet/sfc/debugfs.h
new file mode 100644
index 000000000000..1fe40fbffa5e
--- /dev/null
+++ b/drivers/net/ethernet/sfc/debugfs.h
@@ -0,0 +1,56 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/****************************************************************************
+ * Driver for Solarflare network controllers and boards
+ * Copyright 2023, Advanced Micro Devices, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation, incorporated herein by reference.
+ */
+
+#ifndef EFX_DEBUGFS_H
+#define EFX_DEBUGFS_H
+#include "net_driver.h"
+
+#ifdef CONFIG_DEBUG_FS
+
+/**
+ * DOC: Directory layout for sfc debugfs
+ *
+ * At top level ([/sys/kernel]/debug/sfc) are per-netdev symlinks "nic_$name"
+ * and the "cards" directory.  For each PCI device to which the driver has
+ * bound and created a &struct efx_nic, there is a directory &efx_nic.debug_dir
+ * in "cards" whose name is the PCI address of the device; it is to this
+ * directory that the netdev symlink points.
+ */
+
+void efx_fini_debugfs_netdev(struct net_device *net_dev);
+void efx_update_debugfs_netdev(struct efx_nic *efx);
+
+int efx_init_debugfs_nic(struct efx_nic *efx);
+void efx_fini_debugfs_nic(struct efx_nic *efx);
+
+int efx_init_debugfs(void);
+void efx_fini_debugfs(void);
+
+#else /* CONFIG_DEBUG_FS */
+
+static inline void efx_fini_debugfs_netdev(struct net_device *net_dev) {}
+
+static inline void efx_update_debugfs_netdev(struct efx_nic *efx) {}
+
+static inline int efx_init_debugfs_nic(struct efx_nic *efx)
+{
+	return 0;
+}
+static inline void efx_fini_debugfs_nic(struct efx_nic *efx) {}
+
+static inline int efx_init_debugfs(void)
+{
+	return 0;
+}
+static inline void efx_fini_debugfs(void) {}
+
+#endif /* CONFIG_DEBUG_FS */
+
+#endif /* EFX_DEBUGFS_H */
diff --git a/drivers/net/ethernet/sfc/ef10.c b/drivers/net/ethernet/sfc/ef10.c
index 6dfa062feebc..58e18fc92093 100644
--- a/drivers/net/ethernet/sfc/ef10.c
+++ b/drivers/net/ethernet/sfc/ef10.c
@@ -19,6 +19,7 @@ 
 #include "workarounds.h"
 #include "selftest.h"
 #include "ef10_sriov.h"
+#include "debugfs.h"
 #include <linux/in.h>
 #include <linux/jhash.h>
 #include <linux/wait.h>
@@ -580,6 +581,13 @@  static int efx_ef10_probe(struct efx_nic *efx)
 	if (rc)
 		goto fail3;
 
+	/* Populate debugfs */
+#ifdef CONFIG_DEBUG_FS
+	rc = efx_init_debugfs_nic(efx);
+	if (rc)
+		pci_err(efx->pci_dev, "failed to init device debugfs\n");
+#endif
+
 	rc = device_create_file(&efx->pci_dev->dev,
 				&dev_attr_link_control_flag);
 	if (rc)
@@ -693,6 +701,7 @@  static int efx_ef10_probe(struct efx_nic *efx)
 fail4:
 	device_remove_file(&efx->pci_dev->dev, &dev_attr_link_control_flag);
 fail3:
+	efx_fini_debugfs_nic(efx);
 	efx_mcdi_detach(efx);
 
 	mutex_lock(&nic_data->udp_tunnels_lock);
@@ -962,6 +971,7 @@  static void efx_ef10_remove(struct efx_nic *efx)
 	device_remove_file(&efx->pci_dev->dev, &dev_attr_link_control_flag);
 
 	efx_mcdi_detach(efx);
+	efx_fini_debugfs_nic(efx);
 
 	memset(nic_data->udp_tunnels, 0, sizeof(nic_data->udp_tunnels));
 	mutex_lock(&nic_data->udp_tunnels_lock);
diff --git a/drivers/net/ethernet/sfc/ef100_netdev.c b/drivers/net/ethernet/sfc/ef100_netdev.c
index 7f7d560cb2b4..e844d57754b7 100644
--- a/drivers/net/ethernet/sfc/ef100_netdev.c
+++ b/drivers/net/ethernet/sfc/ef100_netdev.c
@@ -26,10 +26,12 @@ 
 #include "tc_bindings.h"
 #include "tc_encap_actions.h"
 #include "efx_devlink.h"
+#include "debugfs.h"
 
 static void ef100_update_name(struct efx_nic *efx)
 {
 	strcpy(efx->name, efx->net_dev->name);
+	efx_update_debugfs_netdev(efx);
 }
 
 static int ef100_alloc_vis(struct efx_nic *efx, unsigned int *allocated_vis)
@@ -405,6 +407,11 @@  void ef100_remove_netdev(struct efx_probe_data *probe_data)
 	ef100_pf_unset_devlink_port(efx);
 	efx_fini_tc(efx);
 #endif
+#ifdef CONFIG_DEBUG_FS
+	mutex_lock(&efx->debugfs_symlink_mutex);
+	efx_fini_debugfs_netdev(efx->net_dev);
+	mutex_unlock(&efx->debugfs_symlink_mutex);
+#endif
 
 	down_write(&efx->filter_sem);
 	efx_mcdi_filter_table_remove(efx);
diff --git a/drivers/net/ethernet/sfc/ef100_nic.c b/drivers/net/ethernet/sfc/ef100_nic.c
index 6da06931187d..ad378aa05dc3 100644
--- a/drivers/net/ethernet/sfc/ef100_nic.c
+++ b/drivers/net/ethernet/sfc/ef100_nic.c
@@ -27,6 +27,7 @@ 
 #include "tc.h"
 #include "mae.h"
 #include "rx_common.h"
+#include "debugfs.h"
 
 #define EF100_MAX_VIS 4096
 #define EF100_NUM_MCDI_BUFFERS	1
@@ -1077,6 +1078,12 @@  static int ef100_probe_main(struct efx_nic *efx)
 
 	/* Post-IO section. */
 
+	/* Populate debugfs */
+#ifdef CONFIG_DEBUG_FS
+	rc = efx_init_debugfs_nic(efx);
+	if (rc)
+		pci_err(efx->pci_dev, "failed to init device debugfs\n");
+#endif
 	rc = efx_mcdi_init(efx);
 	if (rc)
 		goto fail;
@@ -1213,6 +1220,7 @@  void ef100_remove(struct efx_nic *efx)
 
 	efx_mcdi_detach(efx);
 	efx_mcdi_fini(efx);
+	efx_fini_debugfs_nic(efx);
 	if (nic_data)
 		efx_nic_free_buffer(efx, &nic_data->mcdi_buf);
 	kfree(nic_data);
diff --git a/drivers/net/ethernet/sfc/efx.c b/drivers/net/ethernet/sfc/efx.c
index 19f4b4d0b851..9266c7b5b4fd 100644
--- a/drivers/net/ethernet/sfc/efx.c
+++ b/drivers/net/ethernet/sfc/efx.c
@@ -33,6 +33,7 @@ 
 #include "selftest.h"
 #include "sriov.h"
 #include "efx_devlink.h"
+#include "debugfs.h"
 
 #include "mcdi_port_common.h"
 #include "mcdi_pcol.h"
@@ -401,6 +402,11 @@  static void efx_remove_all(struct efx_nic *efx)
 #endif
 	efx_remove_port(efx);
 	efx_remove_nic(efx);
+#ifdef CONFIG_DEBUG_FS
+	mutex_lock(&efx->debugfs_symlink_mutex);
+	efx_fini_debugfs_netdev(efx->net_dev);
+	mutex_unlock(&efx->debugfs_symlink_mutex);
+#endif
 }
 
 /**************************************************************************
@@ -667,6 +673,7 @@  static void efx_update_name(struct efx_nic *efx)
 	strcpy(efx->name, efx->net_dev->name);
 	efx_mtd_rename(efx);
 	efx_set_channel_names(efx);
+	efx_update_debugfs_netdev(efx);
 }
 
 static int efx_netdev_event(struct notifier_block *this,
@@ -1319,6 +1326,10 @@  static int __init efx_init_module(void)
 
 	printk(KERN_INFO "Solarflare NET driver\n");
 
+	rc = efx_init_debugfs();
+	if (rc)
+		goto err_debugfs;
+
 	rc = register_netdevice_notifier(&efx_netdev_notifier);
 	if (rc)
 		goto err_notifier;
@@ -1344,6 +1355,8 @@  static int __init efx_init_module(void)
  err_reset:
 	unregister_netdevice_notifier(&efx_netdev_notifier);
  err_notifier:
+	efx_fini_debugfs();
+ err_debugfs:
 	return rc;
 }
 
@@ -1355,7 +1368,7 @@  static void __exit efx_exit_module(void)
 	pci_unregister_driver(&efx_pci_driver);
 	efx_destroy_reset_workqueue();
 	unregister_netdevice_notifier(&efx_netdev_notifier);
-
+	efx_fini_debugfs();
 }
 
 module_init(efx_init_module);
diff --git a/drivers/net/ethernet/sfc/efx_common.c b/drivers/net/ethernet/sfc/efx_common.c
index 175bd9cdfdac..7a9d6b6b66e5 100644
--- a/drivers/net/ethernet/sfc/efx_common.c
+++ b/drivers/net/ethernet/sfc/efx_common.c
@@ -1022,6 +1022,9 @@  int efx_init_struct(struct efx_nic *efx, struct pci_dev *pci_dev)
 	INIT_WORK(&efx->mac_work, efx_mac_work);
 	init_waitqueue_head(&efx->flush_wq);
 
+#ifdef CONFIG_DEBUG_FS
+	mutex_init(&efx->debugfs_symlink_mutex);
+#endif
 	efx->tx_queues_per_channel = 1;
 	efx->rxq_entries = EFX_DEFAULT_DMAQ_SIZE;
 	efx->txq_entries = EFX_DEFAULT_DMAQ_SIZE;
@@ -1056,6 +1059,10 @@  void efx_fini_struct(struct efx_nic *efx)
 
 	efx_fini_channels(efx);
 
+#ifdef CONFIG_DEBUG_FS
+	mutex_destroy(&efx->debugfs_symlink_mutex);
+#endif
+
 	kfree(efx->vpd_sn);
 
 	if (efx->workqueue) {
diff --git a/drivers/net/ethernet/sfc/net_driver.h b/drivers/net/ethernet/sfc/net_driver.h
index 27d86e90a3bb..961e2db31c6e 100644
--- a/drivers/net/ethernet/sfc/net_driver.h
+++ b/drivers/net/ethernet/sfc/net_driver.h
@@ -107,6 +107,24 @@  struct hwtstamp_config;
 
 struct efx_self_tests;
 
+/**
+ * struct efx_debugfs_enum_data - information for pretty-printing enums
+ * @value: pointer to the actual enum
+ * @vlen: sizeof the enum
+ * @names: array of names of enumerated values.  May contain some %NULL entries.
+ * @max: number of entries in @names, typically from ARRAY_SIZE()
+ *
+ * Where a driver struct contains an enum member which we wish to expose in
+ * debugfs, we also embed an instance of this struct, which
+ * efx_debugfs_enum_read() uses to pretty-print the value.
+ */
+struct efx_debugfs_enum_data {
+	void *value;
+	size_t vlen;
+	const char *const *names;
+	unsigned int max;
+};
+
 /**
  * struct efx_buffer - A general-purpose DMA buffer
  * @addr: host base address of the buffer
@@ -1123,6 +1141,17 @@  struct efx_nic {
 	u32 rps_next_id;
 #endif
 
+#ifdef CONFIG_DEBUG_FS
+	/** @debug_dir: NIC debugfs directory */
+	struct dentry *debug_dir;
+	/** @debug_symlink: NIC debugfs symlink (``nic_eth%d``) */
+	struct dentry *debug_symlink;
+	/** @debug_interrupt_mode: debugfs details for printing @interrupt_mode */
+	struct efx_debugfs_enum_data debug_interrupt_mode;
+	/** @debugfs_symlink_mutex: protects debugfs @debug_symlink */
+	struct mutex debugfs_symlink_mutex;
+#endif
+
 	atomic_t active_queues;
 	atomic_t rxq_flush_pending;
 	atomic_t rxq_flush_outstanding;