From eb8668b0f7a4041a2fe35856d399d50a9c229144 Mon Sep 17 00:00:00 2001
From: Gregory Nutt <gnutt@nuttx.org>
Date: Fri, 27 Nov 2015 16:39:14 -0600
Subject: [PATCH] net/: Add procfs support for network device status

---
 fs/procfs/fs_skeleton.c         |   6 +-
 include/nuttx/net/net.h         |   4 +-
 net/netdev/Make.defs            |   6 +-
 net/netdev/netdev.h             |  24 ++
 net/netdev/netdev_findbyaddr.c  |  16 --
 net/netdev/netdev_findbyindex.c | 101 +++++++
 net/netdev/netdev_findbyname.c  |  20 --
 net/procfs/Make.defs            |   2 +-
 net/procfs/net_procfs.c         | 292 ++++++++++++++++---
 net/procfs/net_statistics.c     |  84 +-----
 net/procfs/netdev_statistics.c  | 481 ++++++++++++++++++++++++++++++++
 net/procfs/procfs.h             |  54 +++-
 12 files changed, 931 insertions(+), 159 deletions(-)
 create mode 100644 net/netdev/netdev_findbyindex.c
 create mode 100644 net/procfs/netdev_statistics.c

diff --git a/fs/procfs/fs_skeleton.c b/fs/procfs/fs_skeleton.c
index 11bc567fd8..052eb1f0f7 100644
--- a/fs/procfs/fs_skeleton.c
+++ b/fs/procfs/fs_skeleton.c
@@ -178,7 +178,7 @@ static int skel_open(FAR struct file *filep, FAR const char *relpath,
       return -EACCES;
     }
 
-  /* Allocate a container to hold the task and attribute selection */
+  /* Allocate the open file structure */
 
   priv = (FAR struct skel_file_s *)kmm_zalloc(sizeof(struct skel_file_s));
   if (!priv)
@@ -190,7 +190,9 @@ static int skel_open(FAR struct file *filep, FAR const char *relpath,
   /* TODO: Initialize the context specific data here */
 
 
-  /* Save the index as the open-specific state in filep->f_priv */
+  /* Save the open file structure as the open-specific state in
+   * filep->f_priv.
+   */
 
   filep->f_priv = (FAR void *)priv;
   return OK;
diff --git a/include/nuttx/net/net.h b/include/nuttx/net/net.h
index e5995684cd..a5b2afc440 100644
--- a/include/nuttx/net/net.h
+++ b/include/nuttx/net/net.h
@@ -139,7 +139,7 @@ struct socketlist
 /* Callback from netdev_foreach() */
 
 struct net_driver_s; /* Forward reference. Defined in nuttx/net/netdev.h */
-typedef int (*netdev_callback_t)(FAR struct net_driver_s *dev, void *arg);
+typedef int (*netdev_callback_t)(FAR struct net_driver_s *dev, FAR void *arg);
 
 #ifdef CONFIG_NET_NOINTS
 /* Semaphore based locking for non-interrupt based logic.
@@ -1130,7 +1130,7 @@ int netdev_unregister(FAR struct net_driver_s *dev);
  *
  ****************************************************************************/
 
-int netdev_foreach(netdev_callback_t callback, void *arg);
+int netdev_foreach(netdev_callback_t callback, FAR void *arg);
 
 #undef EXTERN
 #ifdef __cplusplus
diff --git a/net/netdev/Make.defs b/net/netdev/Make.defs
index 955745945c..c5262a1aac 100644
--- a/net/netdev/Make.defs
+++ b/net/netdev/Make.defs
@@ -36,9 +36,9 @@
 # Support for operations on network devices
 
 NETDEV_CSRCS += netdev_register.c netdev_ioctl.c netdev_txnotify.c
-NETDEV_CSRCS += netdev_findbyname.c netdev_findbyaddr.c netdev_count.c
-NETDEV_CSRCS += netdev_foreach.c netdev_unregister.c netdev_carrier.c
-NETDEV_CSRCS += netdev_default.c netdev_verify.c
+NETDEV_CSRCS += netdev_findbyname.c netdev_findbyaddr.c netdev_findbyindex.c
+NETDEV_CSRCS += netdev_count.c netdev_foreach.c netdev_unregister.c
+NETDEV_CSRCS += netdev_carrier.c netdev_default.c netdev_verify.c
 
 ifeq ($(CONFIG_NET_RXAVAIL),y)
 NETDEV_CSRCS += netdev_rxnotify.c
diff --git a/net/netdev/netdev.h b/net/netdev/netdev.h
index 9e055effd0..4289dc561a 100644
--- a/net/netdev/netdev.h
+++ b/net/netdev/netdev.h
@@ -188,6 +188,30 @@ FAR struct net_driver_s *netdev_findby_ipv6addr(const net_ipv6addr_t ripaddr);
 #endif
 #endif
 
+/****************************************************************************
+ * Function: netdev_findbyindex
+ *
+ * Description:
+ *   Find a previously registered network device by its position in the
+ *   list of registered devices.  NOTE that this function is not a safe way
+ *   to enumerate network devices:  There could be changes to the list of
+ *   registered device causing a given index to be meaningless (unless, of
+ *   course, the caller keeps the network locked).
+ *
+ * Parameters:
+ *   index - the index of the interface to file
+ *
+ * Returned Value:
+ *  Pointer to driver on success; NULL on failure.  This function can only
+ *  fail if there are fewer registered interfaces than could be indexed.
+ *
+ * Assumptions:
+ *  Called from normal user mode
+ *
+ ****************************************************************************/
+
+FAR struct net_driver_s *netdev_findbyindex(int index);
+
 /****************************************************************************
  * Function: netdev_default
  *
diff --git a/net/netdev/netdev_findbyaddr.c b/net/netdev/netdev_findbyaddr.c
index 1f752a2c64..54cf1cee35 100644
--- a/net/netdev/netdev_findbyaddr.c
+++ b/net/netdev/netdev_findbyaddr.c
@@ -55,22 +55,6 @@
 #include "route/route.h"
 #include "netdev/netdev.h"
 
-/****************************************************************************
- * Pre-processor Definitions
- ****************************************************************************/
-
-/****************************************************************************
- * Private Types
- ****************************************************************************/
-
-/****************************************************************************
- * Private Data
- ****************************************************************************/
-
-/****************************************************************************
- * Public Data
- ****************************************************************************/
-
 /****************************************************************************
  * Private Functions
  ****************************************************************************/
diff --git a/net/netdev/netdev_findbyindex.c b/net/netdev/netdev_findbyindex.c
new file mode 100644
index 0000000000..74ccba04c8
--- /dev/null
+++ b/net/netdev/netdev_findbyindex.c
@@ -0,0 +1,101 @@
+/****************************************************************************
+ * net/netdev/netdev_findbyindex.c
+ *
+ *   Copyright (C) 2015 Gregory Nutt. All rights reserved.
+ *   Author: Gregory Nutt <gnutt@nuttx.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ *    used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#if defined(CONFIG_NET) && CONFIG_NSOCKET_DESCRIPTORS > 0
+
+#include <string.h>
+#include <errno.h>
+
+#include <nuttx/net/netdev.h>
+
+#include "utils/utils.h"
+#include "netdev/netdev.h"
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Function: netdev_findbyindex
+ *
+ * Description:
+ *   Find a previously registered network device by its position in the
+ *   list of registered devices.  NOTE that this function is not a safe way
+ *   to enumerate network devices:  There could be changes to the list of
+ *   registered device causing a given index to be meaningless (unless, of
+ *   course, the caller keeps the network locked).
+ *
+ * Parameters:
+ *   index - the index of the interface to file
+ *
+ * Returned Value:
+ *  Pointer to driver on success; NULL on failure.  This function can only
+ *  fail if there are fewer registered interfaces than could be indexed.
+ *
+ * Assumptions:
+ *  Called from normal user mode
+ *
+ ****************************************************************************/
+
+FAR struct net_driver_s *netdev_findbyindex(int index)
+{
+#ifdef CONFIG_NETDEV_MULTINIC
+  FAR struct net_driver_s *dev;
+  net_lock_t save;
+  int i;
+
+  save = net_lock();
+  for (i = 0, dev = g_netdevices; dev; i++, dev = dev->flink)
+    {
+      if (i == index)
+        {
+          net_unlock(save);
+          return dev;
+        }
+    }
+
+  net_unlock(save);
+  return NULL;
+#else
+  return (index == 0) ? g_netdevices : NULL;
+#endif
+}
+
+#endif /* CONFIG_NET && CONFIG_NSOCKET_DESCRIPTORS */
diff --git a/net/netdev/netdev_findbyname.c b/net/netdev/netdev_findbyname.c
index 93f90a9aab..04c20b27c8 100644
--- a/net/netdev/netdev_findbyname.c
+++ b/net/netdev/netdev_findbyname.c
@@ -48,26 +48,6 @@
 #include "utils/utils.h"
 #include "netdev/netdev.h"
 
-/****************************************************************************
- * Pre-processor Definitions
- ****************************************************************************/
-
-/****************************************************************************
- * Private Types
- ****************************************************************************/
-
-/****************************************************************************
- * Private Data
- ****************************************************************************/
-
-/****************************************************************************
- * Public Data
- ****************************************************************************/
-
-/****************************************************************************
- * Private Functions
- ****************************************************************************/
-
 /****************************************************************************
  * Public Functions
  ****************************************************************************/
diff --git a/net/procfs/Make.defs b/net/procfs/Make.defs
index 49cae7c730..90cef533d2 100644
--- a/net/procfs/Make.defs
+++ b/net/procfs/Make.defs
@@ -39,7 +39,7 @@ ifneq ($(CONFIG_DISABLE_MOUNTPOINT),y)
 ifeq  ($(CONFIG_FS_PROCFS),y)
 ifneq ($(CONFIG_FS_PROCFS_EXCLUDE_NET),y)
 
-NET_CSRCS += net_procfs.c
+NET_CSRCS += net_procfs.c netdev_statistics.c
 
 # General network statistics
 
diff --git a/net/procfs/net_procfs.c b/net/procfs/net_procfs.c
index 0c2bd6025f..f65cf040df 100644
--- a/net/procfs/net_procfs.c
+++ b/net/procfs/net_procfs.c
@@ -47,6 +47,7 @@
 #include <stdbool.h>
 #include <string.h>
 #include <fcntl.h>
+#include <libgen.h>
 #include <assert.h>
 #include <errno.h>
 #include <debug.h>
@@ -55,7 +56,9 @@
 #include <nuttx/fs/fs.h>
 #include <nuttx/fs/procfs.h>
 #include <nuttx/fs/dirent.h>
+#include <nuttx/net/netdev.h>
 
+#include "netdev/netdev.h"
 #include "procfs/procfs.h"
 
 #if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_PROCFS) && \
@@ -64,6 +67,7 @@
 /****************************************************************************
  * Private Function Prototypes
  ****************************************************************************/
+
 /* File system methods */
 
 static int     netprocfs_open(FAR struct file *filep, FAR const char *relpath,
@@ -120,6 +124,7 @@ static int netprocfs_open(FAR struct file *filep, FAR const char *relpath,
                           int oflags, mode_t mode)
 {
   FAR struct netprocfs_file_s *priv;
+  FAR struct net_driver_s *dev;
 
   fvdbg("Open '%s'\n", relpath);
 
@@ -136,15 +141,48 @@ static int netprocfs_open(FAR struct file *filep, FAR const char *relpath,
       return -EACCES;
     }
 
-  /* "net/stat" is the only acceptable value for the relpath */
+  /* "net/stat" is an acceptable value for the relpath only if network layer
+   * statistics are enabled.
+   */
 
-  if (strcmp(relpath, "net/stat") != 0)
+#ifdef CONFIG_NET_STATISTICS
+  if (strcmp(relpath, "net/stat") == 0)
     {
-      fdbg("ERROR: relpath is '%s'\n", relpath);
-      return -ENOENT;
+      /* A NULL network device reference is a clue that we are processing
+       * the network statistics file.
+       */
+
+      dev = NULL;
+    }
+  else
+#endif
+    {
+      FAR char *devname;
+      FAR char *copy;
+
+      /* Otherwise, we need to search the list of registered network devices
+       * to determine if the name corresponds to a network device.
+       */
+
+      copy = strdup(relpath);
+      if (copy == NULL)
+        {
+          fdbg("ERROR: strdup failed\n");
+          return -ENOMEM;
+        }
+
+      devname = basename(copy);
+      dev     = netdev_findbyname(devname);
+      kmm_free(copy);
+
+      if (dev == NULL)
+        {
+          fdbg("ERROR: relpath is '%s'\n", relpath);
+          return -ENOENT;
+        }
     }
 
-  /* Allocate a container to hold the task and attribute selection */
+  /* Allocate the open file structure */
 
   priv = (FAR struct netprocfs_file_s *)kmm_zalloc(sizeof(struct netprocfs_file_s));
   if (!priv)
@@ -153,7 +191,13 @@ static int netprocfs_open(FAR struct file *filep, FAR const char *relpath,
       return -ENOMEM;
     }
 
-  /* Save the index as the open-specific state in filep->f_priv */
+  /* Initialize the open-file structure */
+
+  priv->dev = dev;
+
+  /* Save the open file structure as the open-specific state in
+   * filep->f_priv.
+   */
 
   filep->f_priv = (FAR void *)priv;
   return OK;
@@ -186,7 +230,6 @@ static int netprocfs_close(FAR struct file *filep)
 static ssize_t netprocfs_read(FAR struct file *filep, FAR char *buffer,
                               size_t buflen)
 {
-#ifdef CONFIG_NET_STATISTICS
   FAR struct netprocfs_file_s *priv;
   ssize_t nreturned;
 
@@ -197,9 +240,24 @@ static ssize_t netprocfs_read(FAR struct file *filep, FAR char *buffer,
   priv = (FAR struct netprocfs_file_s *)filep->f_priv;
   DEBUGASSERT(priv);
 
-  /* Read the network layer statistics */
+#ifdef CONFIG_NET_STATISTICS
+  /* A NULL device structure reference is the key that we are showing the
+   * network statistics.
+   */
+
+  if (priv->dev == NULL)
+    {
+      /* Show the network layer statistics */
+
+      nreturned = netprocfs_read_netstats(priv, buffer, buflen);
+    }
+  else
+#endif
+   {
+      /* Otherwise, we are showing device-specific statistics */
 
-  nreturned = net_readstats(priv, buffer, buflen);
+      nreturned = netprocfs_read_devstats(priv, buffer, buflen);
+   }
 
   /* Update the file offset */
 
@@ -209,9 +267,6 @@ static ssize_t netprocfs_read(FAR struct file *filep, FAR char *buffer,
     }
 
   return nreturned;
-#else
-  return 0;
-#endif
 }
 
 /****************************************************************************
@@ -265,10 +320,24 @@ static int netprocfs_opendir(FAR const char *relpath,
                              FAR struct fs_dirent_s *dir)
 {
   FAR struct netprocfs_level1_s *level1;
+  int ndevs;
 
   fvdbg("relpath: \"%s\"\n", relpath ? relpath : "NULL");
   DEBUGASSERT(relpath && dir && !dir->u.procfs);
 
+  /* "net" is the only value of relpath that is a directory */
+
+  if (strcmp(relpath, "net") != 0)
+    {
+      /* REVISIT: We really need to check if the relpath refers to a network
+       * device.  In that case, we need to return -ENOTDIR.  Otherwise, we
+       * should return -ENOENT.
+       */
+
+      fdbg("ERROR: Bad relpath: %s\n", relpath);
+      return -ENOTDIR;
+    }
+
   /* The path refers to the 1st level sbdirectory.  Allocate the level1
    * dirent structure.
    */
@@ -282,10 +351,18 @@ static int netprocfs_opendir(FAR const char *relpath,
       return -ENOMEM;
     }
 
+  /* Count the number of network devices */
+
+  ndevs = netdev_count();
+
   /* Initialze base structure components */
 
   level1->base.level    = 1;
-  level1->base.nentries = 1;
+#ifdef CONFIG_NET_STATISTICS
+  level1->base.nentries = ndevs + 1;
+#else
+  level1->base.nentries = ndevs;
+#endif
   level1->base.index    = 0;
 
   dir->u.procfs = (FAR void *) level1;
@@ -325,46 +402,65 @@ static int netprocfs_closedir(FAR struct fs_dirent_s *dir)
 static int netprocfs_readdir(FAR struct fs_dirent_s *dir)
 {
   FAR struct netprocfs_level1_s *level1;
+  FAR struct net_driver_s *dev;
   int index;
-  int ret;
 
   DEBUGASSERT(dir && dir->u.procfs);
   level1 = dir->u.procfs;
+  DEBUGASSERT(level1->base.level == 1);
 
   /* Have we reached the end of the directory */
 
   index = level1->base.index;
+  DEBUGASSERT(index <= level1->base.nentries);
+
   if (index >= level1->base.nentries)
     {
       /* We signal the end of the directory by returning the special
-       * error -ENOENT
+       * error -ENOENT.
        */
 
       fvdbg("Entry %d: End of directory\n", index);
-      ret = -ENOENT;
+      return -ENOENT;
     }
 
-  /* Currently only one element in the directory */
-
-  else
+#ifdef CONFIG_NET_STATISTICS
+  else if (index == 0)
     {
-      DEBUGASSERT(level1->base.level == 1);
-      DEBUGASSERT(level1->base.index <= 1);
-
-      /* Copy the one supported directory entry */
+      /* Copy the network statistics directory entry */
 
       dir->fd_dir.d_type = DTYPE_FILE;
       strncpy(dir->fd_dir.d_name, "stat", NAME_MAX + 1);
+    }
+  else
+#endif
+    {
+      int devndx = index;
 
-      /* Set up the next directory entry offset.  NOTE that we could use the
-       * standard f_pos instead of our own private index.
+#ifdef CONFIG_NET_STATISTICS
+      /* Subtract one to account for index == 0 which is used for network
+       * status.
        */
 
-      level1->base.index = index + 1;
-      ret = OK;
+      devndx--;
+#endif
+
+      /* Find the device corresponding to this device index */
+
+      dev = netdev_findbyindex(devndx);
+
+      /* Copy the device statistics file entry */
+
+      dir->fd_dir.d_type = DTYPE_FILE;
+      strncpy(dir->fd_dir.d_name, dev->d_ifname, NAME_MAX + 1);
     }
 
-  return ret;
+  /* Set up the next directory entry offset.  NOTE that we could use the
+   * standard f_pos instead of our own private index.
+   */
+
+  level1->base.index = index + 1;
+  return OK;
 }
 
 /****************************************************************************
@@ -394,26 +490,55 @@ static int netprocfs_rewinddir(FAR struct fs_dirent_s *dir)
 
 static int netprocfs_stat(FAR const char *relpath, FAR struct stat *buf)
 {
-  /* "net" and "net/stat" are the only acceptable values for the relpath */
+  /* Check for the directory "net" */
 
   if (strcmp(relpath, "net") == 0)
     {
       buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
     }
-  else if (strcmp(relpath, "net/stat") == 0)
+  else
+#ifdef CONFIG_NET_STATISTICS
+  /* Check for network statistics "net/stat" */
+
+  if (strcmp(relpath, "net/stat") == 0)
     {
       buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
     }
   else
+#endif
     {
-      fdbg("ERROR: relpath is '%s'\n", relpath);
-      return -ENOENT;
+      FAR struct net_driver_s *dev;
+      FAR char *devname;
+      FAR char *copy;
+
+      /* Otherwise, we need to search the list of registered network devices
+       * to determine if the name corresponds to a network device.
+       */
+
+      copy = strdup(relpath);
+      if (copy == NULL)
+        {
+          fdbg("ERROR: strdup failed\n");
+          return -ENOMEM;
+        }
+
+      devname = basename(copy);
+      dev     = netdev_findbyname(devname);
+      kmm_free(copy);
+
+      if (dev == NULL)
+        {
+          fdbg("ERROR: relpath is '%s'\n", relpath);
+          return -ENOENT;
+        }
+
+      buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
     }
 
   /* File/directory size, access block size */
 
   buf->st_size    = 0;
-  buf->st_blksize = 512;
+  buf->st_blksize = 0;
   buf->st_blocks  = 0;
 
   return OK;
@@ -423,5 +548,106 @@ static int netprocfs_stat(FAR const char *relpath, FAR struct stat *buf)
  * Public Functions
  ****************************************************************************/
 
+/****************************************************************************
+ * Name: netprocfs_read_linegen
+ *
+ * Description:
+ *   Read and format procfs data using a line generation table.
+ *
+ * Input Parameters:
+ *   priv   - A reference to the network procfs file structure
+ *   buffer - The user-provided buffer into which device status will be
+ *            returned.
+ *   buflen - The size in bytes of the user provided buffer.
+ *   gentab - Table of line generation functions
+ *   nelems - The number of elements in the table
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; a negated errno value is returned
+ *   on failure.
+ *
+ ****************************************************************************/
+
+ssize_t netprocfs_read_linegen(FAR struct netprocfs_file_s *priv,
+                               FAR char *buffer, size_t buflen,
+                               FAR const linegen_t *gentab, int nelems)
+{
+  size_t xfrsize;
+  ssize_t nreturned;
+
+  fvdbg("buffer=%p buflen=%lu\n", buffer, (unsigned long)buflen);
+
+  /* Is there line data already buffered? */
+
+  nreturned = 0;
+  if (priv->linesize > 0)
+    {
+      /* Yes, how much can we transfer now? */
+
+      xfrsize = priv->linesize;
+      if (xfrsize > buflen)
+        {
+          xfrsize = buflen;
+        }
+
+      /* Transfer the data to the user buffer */
+
+      memcpy(buffer, &priv->line[priv->offset], xfrsize);
+
+      /* Update pointers, sizes, and offsets */
+
+      buffer         += xfrsize;
+      buflen         -= xfrsize;
+
+      priv->linesize -= xfrsize;
+      priv->offset   += xfrsize;
+      nreturned       = xfrsize;
+    }
+
+  /* Loop until the user buffer is full or until all of the network
+   * statistics have been transferred.  At this point we know that
+   * either:
+   *
+   * 1. The user buffer is full, and/or
+   * 2. All of the current line data has been transferred.
+   */
+
+  while (buflen > 0 && priv->lineno < nelems)
+    {
+      int len;
+
+      /* Read the next line into the working buffer */
+
+      len = gentab[priv->lineno](priv);
+
+      /* Update line-related information */
+
+      priv->lineno++;
+      priv->linesize = len;
+      priv->offset = 0;
+
+      /* Transfer data to the user buffer */
+
+      xfrsize = priv->linesize;
+      if (xfrsize > buflen)
+        {
+          xfrsize = buflen;
+        }
+
+      memcpy(buffer, &priv->line[priv->offset], xfrsize);
+
+      /* Update pointers, sizes, and offsets */
+
+      buffer         += xfrsize;
+      buflen         -= xfrsize;
+
+      priv->linesize -= xfrsize;
+      priv->offset   += xfrsize;
+      nreturned      += xfrsize;
+    }
+
+  return nreturned;
+}
+
 #endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_PROCFS &&
         * !CONFIG_FS_PROCFS_EXCLUDE_NET */
diff --git a/net/procfs/net_statistics.c b/net/procfs/net_statistics.c
index e6e977474c..df35f4a165 100644
--- a/net/procfs/net_statistics.c
+++ b/net/procfs/net_statistics.c
@@ -1,5 +1,5 @@
 /****************************************************************************
- * net/procfs/net_statisticsc
+ * net/procfs/net_statistics.c
  *
  *   Copyright (C) 2015 Gregory Nutt. All rights reserved.
  *   Author: Gregory Nutt <gnutt@nuttx.org>
@@ -436,7 +436,7 @@ static int netprocfs_retransmissions(FAR struct netprocfs_file_s *netfile)
  ****************************************************************************/
 
 /****************************************************************************
- * Name: net_readstats
+ * Name: netprocfs_read_netstats
  *
  * Description:
  *   Read and format network layer statistics.
@@ -453,84 +453,10 @@ static int netprocfs_retransmissions(FAR struct netprocfs_file_s *netfile)
  *
  ****************************************************************************/
 
-ssize_t net_readstats(FAR struct netprocfs_file_s *priv, FAR char *buffer,
-                      size_t buflen)
+ssize_t netprocfs_read_netstats(FAR struct netprocfs_file_s *priv,
+                                FAR char *buffer, size_t buflen)
 {
-  size_t xfrsize;
-  ssize_t nreturned;
-
-  fvdbg("buffer=%p buflen=%lu\n", buffer, (unsigned long)buflen);
-
-  /* Is there line data already buffered? */
-
-  nreturned = 0;
-  if (priv->linesize > 0)
-    {
-      /* Yes, how much can we transfer now? */
-
-      xfrsize = priv->linesize;
-      if (xfrsize > buflen)
-        {
-          xfrsize = buflen;
-        }
-
-      /* Transfer the data to the user buffer */
-
-      memcpy(buffer, &priv->line[priv->offset], xfrsize);
-
-      /* Update pointers, sizes, and offsets */
-
-      buffer         += xfrsize;
-      buflen         -= xfrsize;
-
-      priv->linesize -= xfrsize;
-      priv->offset   += xfrsize;
-      nreturned       = xfrsize;
-    }
-
-  /* Loop until the user buffer is full or until all of the network
-   * statistics have been transferred.  At this point we know that
-   * either:
-   *
-   * 1. The user buffer is full, and/or
-   * 2. All of the current line data has been transferred.
-   */
-
-  while (buflen > 0 && priv->lineno < NSTAT_LINES)
-    {
-      int len;
-
-      /* Read the next line into the working buffer */
-
-      len = g_linegen[priv->lineno](priv);
-
-      /* Update line-related information */
-
-      priv->lineno++;
-      priv->linesize = len;
-      priv->offset = 0;
-
-      /* Transfer data to the user buffer */
-
-      xfrsize = priv->linesize;
-      if (xfrsize > buflen)
-        {
-          xfrsize = buflen;
-        }
-
-      memcpy(buffer, &priv->line[priv->offset], xfrsize);
-
-      /* Update pointers, sizes, and offsets */
-
-      buffer         += xfrsize;
-      buflen         -= xfrsize;
-
-      priv->linesize -= xfrsize;
-      priv->offset   += xfrsize;
-      nreturned      += xfrsize;
-    }
-
-  return nreturned;
+  return netprocfs_read_linegen(priv, buffer, buflen, g_linegen, NSTAT_LINES);
 }
 
 #endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_PROCFS &&
diff --git a/net/procfs/netdev_statistics.c b/net/procfs/netdev_statistics.c
new file mode 100644
index 0000000000..b81741a409
--- /dev/null
+++ b/net/procfs/netdev_statistics.c
@@ -0,0 +1,481 @@
+/****************************************************************************
+ * net/procfs/netdev_statistics.c
+ *
+ *   Copyright (C) 2015 Gregory Nutt. All rights reserved.
+ *   Author: Gregory Nutt <gnutt@nuttx.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ *    used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#include <debug.h>
+
+#include <netinet/ether.h>
+
+#include <nuttx/net/netdev.h>
+
+#include "netdev/netdev.h"
+#include "procfs/procfs.h"
+
+#if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_PROCFS) && \
+    !defined(CONFIG_FS_PROCFS_EXCLUDE_NET)
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int netprocfs_linklayer(FAR struct netprocfs_file_s *netfile);
+static int netprocfs_ipaddresses(FAR struct netprocfs_file_s *netfile);
+
+#ifdef CONFIG_NETDEV_STATISTICS
+static int netprocfs_rxstatistics_header(FAR struct netprocfs_file_s *netfile);
+static int netprocfs_rxstatistics(FAR struct netprocfs_file_s *netfile);
+static int netprocfs_rxpackets_header(FAR struct netprocfs_file_s *netfile);
+static int netprocfs_rxpackets(FAR struct netprocfs_file_s *netfile);
+static int netprocfs_txstatistics_header(FAR struct netprocfs_file_s *netfile);
+static int netprocfs_txstatistics(FAR struct netprocfs_file_s *netfile);
+static int netprocfs_errors(FAR struct netprocfs_file_s *netfile);
+#endif /* CONFIG_NETDEV_STATISTICS */
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* Line generating functions */
+
+static const linegen_t g_linegen[] =
+{
+  netprocfs_linklayer,
+  netprocfs_ipaddresses
+#ifdef CONFIG_NETDEV_STATISTICS
+  , netprocfs_rxstatistics_header,
+  netprocfs_rxstatistics,
+  netprocfs_rxpackets_header,
+  netprocfs_rxpackets,
+  netprocfs_txstatistics_header,
+  netprocfs_txstatistics,
+  netprocfs_errors
+#endif /* CONFIG_NETDEV_STATISTICS */
+};
+
+#define NSTAT_LINES (sizeof(g_linegen) / sizeof(linegen_t))
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: netprocfs_linklayer
+ ****************************************************************************/
+
+static int netprocfs_linklayer(FAR struct netprocfs_file_s *netfile)
+{
+  FAR struct net_driver_s *dev;
+  FAR const char *status;
+  int len = 0;
+
+  DEBUGASSERT(netfile != NULL && netfile->dev != NULL);
+  dev = netfile->dev;
+
+  /* Get the interface status:  RUNNING, UP, or DOWN */
+
+  if ((dev->d_flags & IFF_RUNNING) != 0)
+    {
+      status = "RUNNING";
+    }
+  else if ((dev->d_flags & IFF_UP) != 0)
+    {
+      status = "UP";
+    }
+  else
+    {
+      status = "DOWN";
+    }
+
+#if defined(CONFIG_NET_MULTILINK)
+  /* If there are multiple link types being supported, then selected the
+   * output appropriate for the link type associated with this device.
+   */
+
+  switch (dev->d_lltype)
+    {
+#ifdef CONFIG_NET_ETHERNET
+      case NET_LL_ETHERNET:
+        len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                        "%s\tLink encap:Ethernet HWaddr %s",
+                        dev->d_ifname, ether_ntoa(&dev->d_mac));
+        break;
+#endif
+
+#ifdef CONFIG_NET_LOOPBACK
+      case NET_LL_LOOPBACK:
+        len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                        "%s\tLink encap:Local Loopback",
+                        dev->d_ifname);
+        break;
+#endif
+
+#ifdef CONFIG_NET_SLIP
+      case NET_LL_SLIP:
+        len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                        "%s\tLink encap:SLIP", dev->d_ifname);
+        break;
+#endif
+
+#ifdef CONFIG_NET_PPP
+      case NET_LL_PPP:
+        len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                        "%s\tLink encap:P-t-P", dev->d_ifname);
+        break;
+#endif
+
+#ifdef CONFIG_NET_TUN
+      case NET_LL_TUN:
+        len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                        "%s\tLink encap:TUN", dev->d_ifname);
+        break;
+#endif
+
+      default:
+        len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                        "%s\tLink encap:UNSPEC", dev->d_ifname);
+    }
+
+  len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                  " at %s\n", status);
+
+#elif defined(CONFIG_NET_ETHERNET)
+  len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                  "%s\tLink encap:Ethernet HWaddr %s at %s\n",
+                  dev->d_ifname, ether_ntoa(&dev->d_mac), status);
+
+#elif defined(CONFIG_NET_LOOPBACK)
+  len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                  "%s\tLink encap:Local Loopback at %s\n",
+                  dev->d_ifname, status);
+
+#elif defined(CONFIG_NET_SLIP)
+  len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                  "%s\tLink encap:SLIP at %s\n",
+                  dev->d_ifname, status);
+
+#elif defined(CONFIG_NET_PPP)
+  len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                  "%s\tLink encap:P-t-P at %s\n",
+                  dev->d_ifname, status);
+
+#elif defined(CONFIG_NET_TUN)
+  len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                  "%s\tLink encap:TUN at %s\n",
+                  dev->d_ifname, status);
+#endif
+
+  return len;
+}
+
+/****************************************************************************
+ * Name: netprocfs_ipaddresses
+ ****************************************************************************/
+
+static int netprocfs_ipaddresses(FAR struct netprocfs_file_s *netfile)
+{
+  FAR struct net_driver_s *dev;
+#ifdef CONFIG_NET_IPv4
+  struct in_addr addr;
+#endif
+#ifdef CONFIG_NET_IPv6
+  char addrstr[INET6_ADDRSTRLEN];
+  uint8_t preflen;
+#endif
+  int len = 0;
+
+  DEBUGASSERT(netfile != NULL && netfile->dev != NULL);
+  dev = netfile->dev;
+
+#ifdef CONFIG_NET_IPv4
+  /* Show the IPv4 address */
+
+  addr.s_addr = dev->d_ipaddr;
+  len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                  "\tinet addr:%s ", inet_ntoa(addr));
+
+  /* Show the IPv4 default router address */
+
+  addr.s_addr = dev->d_draddr;
+  len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                  "DRaddr:%s ", inet_ntoa(addr));
+
+  /* Show the IPv4 network mask */
+
+  addr.s_addr = dev->d_netmask;
+  len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                  "Mask:%s\n", inet_ntoa(addr));
+
+#if defined(CONFIG_NSH_DHCPC) || defined(CONFIG_NSH_DNS)
+  netlib_get_ipv4dnsaddr(&addr);
+  len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                  "\tDNSaddr:%s\n", inet_ntoa(addr));
+#endif
+#endif
+
+#ifdef CONFIG_NET_IPv6
+  /* Convert the 128 network mask to a human friendly prefix length */
+
+  preflen = netlib_ipv6netmask2prefix(dev->d_ipv6netmask);
+
+  /* Show the assigned IPv6 address */
+
+  if (inet_ntop(AF_INET6, dev->d_ipv6addr, addrstr, INET6_ADDRSTRLEN))
+    {
+      len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                      "\tinet6 addr:%s/%d\n", addrstr, preflen);
+    }
+
+  /* REVISIT: Show the IPv6 default router address */
+
+  if (inet_ntop(AF_INET6, dev->d_ipv6draddr, addrstr, INET6_ADDRSTRLEN))
+    {
+      len += snprintf(&netfile->line[len], NET_LINELEN - len,
+                      "\tinet6 DRaddr:%s/%d\n", addrstr, preflen);
+    }
+
+#if defined(CONFIG_NSH_DHCPCv6) || defined(CONFIG_NSH_DNS)
+#  warning Missing logic
+#endif
+#endif
+
+  len += snprintf(&netfile->line[len], NET_LINELEN - len, "\n");
+  return len;
+}
+
+/****************************************************************************
+ * Name: netprocfs_rxstatistics_header
+ ****************************************************************************/
+
+#ifdef CONFIG_NETDEV_STATISTICS
+static int netprocfs_rxstatistics_header(FAR struct netprocfs_file_s *netfile)
+{
+  DEBUGASSERT(netfile != NULL);
+  return snprintf(netfile->line, NET_LINELEN , "\tRX: %-8s %-8s %-8s\n",
+                  "Received", "Fragment", "Errors");
+}
+#endif /* CONFIG_NETDEV_STATISTICS */
+
+/****************************************************************************
+ * Name: netprocfs_rxstatistics
+ ****************************************************************************/
+
+#ifdef CONFIG_NETDEV_STATISTICS
+static int netprocfs_rxstatistics(FAR struct netprocfs_file_s *netfile)
+{
+  FAR struct netdev_statistics_s *stats;
+  FAR struct net_driver_s *dev;
+
+  DEBUGASSERT(netfile != NULL && netfile->dev != NULL);
+  dev = netfile->dev;
+  stats = &dev->d_statistics;
+
+  return snprintf(netfile->line, NET_LINELEN, "\t    %08lx %08lx %08lx\n",
+                  (unsigned long)stats->rx_packets,
+                  (unsigned long)stats->rx_fragments,
+                  (unsigned long)stats->rx_errors);
+}
+#endif /* CONFIG_NETDEV_STATISTICS */
+
+/****************************************************************************
+ * Name: netprocfs_rxpackets_header
+ ****************************************************************************/
+
+#ifdef CONFIG_NETDEV_STATISTICS
+static int netprocfs_rxpackets_header(FAR struct netprocfs_file_s *netfile)
+{
+  FAR char *fmt;
+
+  DEBUGASSERT(netfile != NULL);
+
+  fmt = "\t    "
+#ifdef CONFIG_NET_IPv4
+        "%-8s "
+#endif
+#ifdef CONFIG_NET_IPv6
+        "%-8s "
+#endif
+#ifdef CONFIG_NET_ARP
+        "%-8s "
+#endif
+        "%-8s\n";
+
+  return snprintf(netfile->line, NET_LINELEN, fmt
+#ifdef CONFIG_NET_IPv4
+        , "IPv4"
+#endif
+#ifdef CONFIG_NET_IPv6
+        , "IPv6"
+#endif
+#ifdef CONFIG_NET_ARP
+        , "ARP"
+#endif
+        , "Dropped");
+}
+#endif /* CONFIG_NETDEV_STATISTICS */
+
+/****************************************************************************
+ * Name: netprocfs_rxpackets
+ ****************************************************************************/
+
+#ifdef CONFIG_NETDEV_STATISTICS
+static int netprocfs_rxpackets(FAR struct netprocfs_file_s *netfile)
+{
+  FAR struct netdev_statistics_s *stats;
+  FAR struct net_driver_s *dev;
+  FAR char *fmt;
+
+  DEBUGASSERT(netfile != NULL && netfile->dev != NULL);
+  dev = netfile->dev;
+  stats = &dev->d_statistics;
+
+  fmt = "\t    "
+#ifdef CONFIG_NET_IPv4
+        "%08lx "
+#endif
+#ifdef CONFIG_NET_IPv6
+        "%08lx "
+#endif
+#ifdef CONFIG_NET_ARP
+        "%08lx "
+#endif
+        "%08lx\n";
+
+  return snprintf(netfile->line, NET_LINELEN, fmt
+#ifdef CONFIG_NET_IPv4
+        , (unsigned long)stats->rx_ipv4
+#endif
+#ifdef CONFIG_NET_IPv6
+        , (unsigned long)stats->rx_ipv6
+#endif
+#ifdef CONFIG_NET_ARP
+        , (unsigned long)stats->rx_arp
+#endif
+        , (unsigned long)stats->rx_dropped);
+}
+#endif /* CONFIG_NETDEV_STATISTICS */
+
+/****************************************************************************
+ * Name: netprocfs_txstatistics_header
+ ****************************************************************************/
+
+#ifdef CONFIG_NETDEV_STATISTICS
+static int netprocfs_txstatistics_header(FAR struct netprocfs_file_s *netfile)
+{
+  DEBUGASSERT(netfile != NULL);
+
+  return snprintf(netfile->line, NET_LINELEN, "\tTX: %-8s %-8s %-8s %-8s\n",
+                 "Queued", "Sent", "Erorts", "Timeouts");
+}
+#endif /* CONFIG_NETDEV_STATISTICS */
+
+/****************************************************************************
+ * Name: netprocfs_txstatistics
+ ****************************************************************************/
+
+#ifdef CONFIG_NETDEV_STATISTICS
+static int netprocfs_txstatistics(FAR struct netprocfs_file_s *netfile)
+{
+  FAR struct netdev_statistics_s *stats;
+  FAR struct net_driver_s *dev;
+
+  DEBUGASSERT(netfile != NULL && netfile->dev != NULL);
+  dev = netfile->dev;
+  stats = &dev->d_statistics;
+
+  return snprintf(netfile->line, NET_LINELEN, "\t    %08lx %08lx %08lx %08lx\n",
+                  (unsigned long)stats->tx_packets,
+                  (unsigned long)stats->tx_done,
+                  (unsigned long)stats->tx_errors,
+                  (unsigned long)stats->tx_timeouts);
+}
+#endif /* CONFIG_NETDEV_STATISTICS */
+
+/****************************************************************************
+ * Name: netprocfs_errors
+ ****************************************************************************/
+
+#ifdef CONFIG_NETDEV_STATISTICS
+static int netprocfs_errors(FAR struct netprocfs_file_s *netfile)
+{
+  FAR struct netdev_statistics_s *stats;
+  FAR struct net_driver_s *dev;
+
+  DEBUGASSERT(netfile != NULL && netfile->dev != NULL);
+  dev = netfile->dev;
+  stats = &dev->d_statistics;
+
+  return snprintf(netfile->line, NET_LINELEN , "\tTotal Errors: %08x\n\n",
+                  (unsigned long)stats->errors);
+}
+#endif /* CONFIG_NETDEV_STATISTICS */
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: netprocfs_read_devstats
+ *
+ * Description:
+ *   Read and format network device statistics.
+ *
+ * Input Parameters:
+ *   priv - A reference to the network procfs file structure
+ *   buffer - The user-provided buffer into which device status will be
+ *            returned.
+ *   bulen  - The size in bytes of the user provided buffer.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; a negated errno value is returned
+ *   on failure.
+ *
+ ****************************************************************************/
+
+ssize_t netprocfs_read_devstats(FAR struct netprocfs_file_s *priv,
+                                FAR char *buffer, size_t buflen)
+{
+  return netprocfs_read_linegen(priv, buffer, buflen, g_linegen, NSTAT_LINES);
+}
+
+#endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_PROCFS &&
+        * !CONFIG_FS_PROCFS_EXCLUDE_NET */
diff --git a/net/procfs/procfs.h b/net/procfs/procfs.h
index c6f059fb8d..feba657a0d 100644
--- a/net/procfs/procfs.h
+++ b/net/procfs/procfs.h
@@ -66,9 +66,11 @@
 
 /* This structure describes one open "file" */
 
+struct net_driver_s;                 /* Forward reference */
 struct netprocfs_file_s
 {
   struct procfs_file_s base;         /* Base open file structure */
+  FAR struct net_driver_s *dev;      /* Current network device */
   uint8_t lineno;                    /* Line number */
   uint8_t linesize;                  /* Number of valid characters in line[] */
   uint8_t offset;                    /* Offset to first valid character in line[] */
@@ -80,6 +82,7 @@ struct netprocfs_file_s
 struct netprocfs_level1_s
 {
   struct procfs_dir_priv_s  base;    /* Base directory private data */
+  char name[NAME_MAX + 1];           /* Name of last node visited */
 };
 
 /* Line generating function type */
@@ -103,7 +106,31 @@ extern "C"
  ****************************************************************************/
 
 /****************************************************************************
- * Name: net_readstats
+ * Name: netprocfs_read_linegen
+ *
+ * Description:
+ *   Read and format procfs data using a line generation table.
+ *
+ * Input Parameters:
+ *   priv   - A reference to the network procfs file structure
+ *   buffer - The user-provided buffer into which device status will be
+ *            returned.
+ *   buflen - The size in bytes of the user provided buffer.
+ *   gentab - Table of line generation functions
+ *   nelems - The number of elements in the table
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; a negated errno value is returned
+ *   on failure.
+ *
+ ****************************************************************************/
+
+ssize_t netprocfs_read_linegen(FAR struct netprocfs_file_s *priv,
+                                FAR char *buffer, size_t buflen,
+                                FAR const linegen_t *gentab, int nelems);
+
+/****************************************************************************
+ * Name: netprocfs_read_netstats
  *
  * Description:
  *   Read and format network layer statistics.
@@ -121,10 +148,31 @@ extern "C"
  ****************************************************************************/
 
 #ifdef CONFIG_NET_STATISTICS
-ssize_t net_readstats(FAR struct netprocfs_file_s *priv, FAR char *buffer,
-                      size_t buflen);
+ssize_t netprocfs_read_netstats(FAR struct netprocfs_file_s *priv,
+                                FAR char *buffer, size_t buflen);
 #endif
 
+/****************************************************************************
+ * Name: netprocfs_read_devstats
+ *
+ * Description:
+ *   Read and format network device statistics.
+ *
+ * Input Parameters:
+ *   priv - A reference to the network procfs file structure
+ *   buffer - The user-provided buffer into which device status will be
+ *            returned.
+ *   bulen  - The size in bytes of the user provided buffer.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; a negated errno value is returned
+ *   on failure.
+ *
+ ****************************************************************************/
+
+ssize_t netprocfs_read_devstats(FAR struct netprocfs_file_s *priv,
+                                FAR char *buffer, size_t buflen);
+
 #undef EXTERN
 #ifdef __cplusplus
 }
-- 
GitLab