spi: expose spi_master and spi_device statistics via sysfs
authorMartin Sperl <kernel@martin.sperl.org>
Mon, 22 Jun 2015 13:00:36 +0000 (13:00 +0000)
committerMark Brown <broonie@kernel.org>
Tue, 7 Jul 2015 12:33:23 +0000 (13:33 +0100)
per spi-master statistics accessible as:
  /sys/class/spi_master/spi*/statistics/*

per spi-device statistics accessible via:
  /sys/class/spi_master/spi*/spi*.*/statistics/*

The following statistics are exposed as separate "files" inside
these directories:
* messages              number of spi_messages
* transfers             number of spi_transfers
* bytes                 number of bytes transferred
* bytes_rx              number of bytes transmitted
* bytes_tx              number of bytes received
* errors                number of errors encounterd
* timedout              number of messages that have timed out
* spi_async             number of spi_messages submitted using spi_async
* spi_sync              number of spi_messages submitted using spi_sync
* spi_sync_immediate    number of spi_messages submitted using spi_sync,
                        that are handled immediately without a context switch
                        to the spi_pump worker-thread

Signed-off-by: Martin Sperl <kernel@martin.sperl.org>
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/spi/spi.c
include/linux/spi/spi.h

index cf8b91b23a763d60f8253e3ef1e8ccd420e52a0c..07476ca083a046b6cfaff127ccd7895667e15467 100644 (file)
@@ -67,11 +67,141 @@ modalias_show(struct device *dev, struct device_attribute *a, char *buf)
 }
 static DEVICE_ATTR_RO(modalias);
 
+#define SPI_STATISTICS_ATTRS(field, file)                              \
+static ssize_t spi_master_##field##_show(struct device *dev,           \
+                                        struct device_attribute *attr, \
+                                        char *buf)                     \
+{                                                                      \
+       struct spi_master *master = container_of(dev,                   \
+                                                struct spi_master, dev); \
+       return spi_statistics_##field##_show(&master->statistics, buf); \
+}                                                                      \
+static struct device_attribute dev_attr_spi_master_##field = {         \
+       .attr = { .name = file, .mode = S_IRUGO },                      \
+       .show = spi_master_##field##_show,                              \
+};                                                                     \
+static ssize_t spi_device_##field##_show(struct device *dev,           \
+                                        struct device_attribute *attr, \
+                                       char *buf)                      \
+{                                                                      \
+       struct spi_device *spi = container_of(dev,                      \
+                                             struct spi_device, dev);  \
+       return spi_statistics_##field##_show(&spi->statistics, buf);    \
+}                                                                      \
+static struct device_attribute dev_attr_spi_device_##field = {         \
+       .attr = { .name = file, .mode = S_IRUGO },                      \
+       .show = spi_device_##field##_show,                              \
+}
+
+#define SPI_STATISTICS_SHOW_NAME(name, file, field, format_string)     \
+static ssize_t spi_statistics_##name##_show(struct spi_statistics *stat, \
+                                           char *buf)                  \
+{                                                                      \
+       unsigned long flags;                                            \
+       ssize_t len;                                                    \
+       spin_lock_irqsave(&stat->lock, flags);                          \
+       len = sprintf(buf, format_string, stat->field);                 \
+       spin_unlock_irqrestore(&stat->lock, flags);                     \
+       return len;                                                     \
+}                                                                      \
+SPI_STATISTICS_ATTRS(name, file)
+
+#define SPI_STATISTICS_SHOW(field, format_string)                      \
+       SPI_STATISTICS_SHOW_NAME(field, __stringify(field),             \
+                                field, format_string)
+
+SPI_STATISTICS_SHOW(messages, "%lu");
+SPI_STATISTICS_SHOW(transfers, "%lu");
+SPI_STATISTICS_SHOW(errors, "%lu");
+SPI_STATISTICS_SHOW(timedout, "%lu");
+
+SPI_STATISTICS_SHOW(spi_sync, "%lu");
+SPI_STATISTICS_SHOW(spi_sync_immediate, "%lu");
+SPI_STATISTICS_SHOW(spi_async, "%lu");
+
+SPI_STATISTICS_SHOW(bytes, "%llu");
+SPI_STATISTICS_SHOW(bytes_rx, "%llu");
+SPI_STATISTICS_SHOW(bytes_tx, "%llu");
+
 static struct attribute *spi_dev_attrs[] = {
        &dev_attr_modalias.attr,
        NULL,
 };
-ATTRIBUTE_GROUPS(spi_dev);
+
+static const struct attribute_group spi_dev_group = {
+       .attrs  = spi_dev_attrs,
+};
+
+static struct attribute *spi_device_statistics_attrs[] = {
+       &dev_attr_spi_device_messages.attr,
+       &dev_attr_spi_device_transfers.attr,
+       &dev_attr_spi_device_errors.attr,
+       &dev_attr_spi_device_timedout.attr,
+       &dev_attr_spi_device_spi_sync.attr,
+       &dev_attr_spi_device_spi_sync_immediate.attr,
+       &dev_attr_spi_device_spi_async.attr,
+       &dev_attr_spi_device_bytes.attr,
+       &dev_attr_spi_device_bytes_rx.attr,
+       &dev_attr_spi_device_bytes_tx.attr,
+       NULL,
+};
+
+static const struct attribute_group spi_device_statistics_group = {
+       .name  = "statistics",
+       .attrs  = spi_device_statistics_attrs,
+};
+
+static const struct attribute_group *spi_dev_groups[] = {
+       &spi_dev_group,
+       &spi_device_statistics_group,
+       NULL,
+};
+
+static struct attribute *spi_master_statistics_attrs[] = {
+       &dev_attr_spi_master_messages.attr,
+       &dev_attr_spi_master_transfers.attr,
+       &dev_attr_spi_master_errors.attr,
+       &dev_attr_spi_master_timedout.attr,
+       &dev_attr_spi_master_spi_sync.attr,
+       &dev_attr_spi_master_spi_sync_immediate.attr,
+       &dev_attr_spi_master_spi_async.attr,
+       &dev_attr_spi_master_bytes.attr,
+       &dev_attr_spi_master_bytes_rx.attr,
+       &dev_attr_spi_master_bytes_tx.attr,
+       NULL,
+};
+
+static const struct attribute_group spi_master_statistics_group = {
+       .name  = "statistics",
+       .attrs  = spi_master_statistics_attrs,
+};
+
+static const struct attribute_group *spi_master_groups[] = {
+       &spi_master_statistics_group,
+       NULL,
+};
+
+void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
+                                      struct spi_transfer *xfer,
+                                      struct spi_master *master)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&stats->lock, flags);
+
+       stats->transfers++;
+
+       stats->bytes += xfer->len;
+       if ((xfer->tx_buf) &&
+           (xfer->tx_buf != master->dummy_tx))
+               stats->bytes_tx += xfer->len;
+       if ((xfer->rx_buf) &&
+           (xfer->rx_buf != master->dummy_rx))
+               stats->bytes_rx += xfer->len;
+
+       spin_unlock_irqrestore(&stats->lock, flags);
+}
+EXPORT_SYMBOL_GPL(spi_statistics_add_transfer_stats);
 
 /* modalias support makes "modprobe $MODALIAS" new-style hotplug work,
  * and the sysfs version makes coldplug work too.
@@ -249,6 +379,9 @@ struct spi_device *spi_alloc_device(struct spi_master *master)
        spi->dev.bus = &spi_bus_type;
        spi->dev.release = spidev_release;
        spi->cs_gpio = -ENOENT;
+
+       spin_lock_init(&spi->statistics.lock);
+
        device_initialize(&spi->dev);
        return spi;
 }
@@ -689,17 +822,29 @@ static int spi_transfer_one_message(struct spi_master *master,
        bool keep_cs = false;
        int ret = 0;
        unsigned long ms = 1;
+       struct spi_statistics *statm = &master->statistics;
+       struct spi_statistics *stats = &msg->spi->statistics;
 
        spi_set_cs(msg->spi, true);
 
+       SPI_STATISTICS_INCREMENT_FIELD(statm, messages);
+       SPI_STATISTICS_INCREMENT_FIELD(stats, messages);
+
        list_for_each_entry(xfer, &msg->transfers, transfer_list) {
                trace_spi_transfer_start(msg, xfer);
 
+               spi_statistics_add_transfer_stats(statm, xfer, master);
+               spi_statistics_add_transfer_stats(stats, xfer, master);
+
                if (xfer->tx_buf || xfer->rx_buf) {
                        reinit_completion(&master->xfer_completion);
 
                        ret = master->transfer_one(master, msg->spi, xfer);
                        if (ret < 0) {
+                               SPI_STATISTICS_INCREMENT_FIELD(statm,
+                                                              errors);
+                               SPI_STATISTICS_INCREMENT_FIELD(stats,
+                                                              errors);
                                dev_err(&msg->spi->dev,
                                        "SPI transfer failed: %d\n", ret);
                                goto out;
@@ -715,6 +860,10 @@ static int spi_transfer_one_message(struct spi_master *master,
                        }
 
                        if (ms == 0) {
+                               SPI_STATISTICS_INCREMENT_FIELD(statm,
+                                                              timedout);
+                               SPI_STATISTICS_INCREMENT_FIELD(stats,
+                                                              timedout);
                                dev_err(&msg->spi->dev,
                                        "SPI transfer timed out\n");
                                msg->status = -ETIMEDOUT;
@@ -1416,10 +1565,10 @@ static struct class spi_master_class = {
        .name           = "spi_master",
        .owner          = THIS_MODULE,
        .dev_release    = spi_master_release,
+       .dev_groups     = spi_master_groups,
 };
 
 
-
 /**
  * spi_alloc_master - allocate SPI master controller
  * @dev: the controller, possibly using the platform_bus
@@ -1585,6 +1734,8 @@ int spi_register_master(struct spi_master *master)
                        goto done;
                }
        }
+       /* add statistics */
+       spin_lock_init(&master->statistics.lock);
 
        mutex_lock(&board_lock);
        list_add_tail(&master->list, &spi_master_list);
@@ -1939,6 +2090,9 @@ static int __spi_async(struct spi_device *spi, struct spi_message *message)
 
        message->spi = spi;
 
+       SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_async);
+       SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_async);
+
        trace_spi_message_submit(message);
 
        return master->transfer(spi, message);
@@ -2075,6 +2229,9 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message,
        message->context = &done;
        message->spi = spi;
 
+       SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_sync);
+       SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);
+
        if (!bus_locked)
                mutex_lock(&master->bus_lock_mutex);
 
@@ -2102,8 +2259,13 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message,
                /* Push out the messages in the calling context if we
                 * can.
                 */
-               if (master->transfer == spi_queued_transfer)
+               if (master->transfer == spi_queued_transfer) {
+                       SPI_STATISTICS_INCREMENT_FIELD(&master->statistics,
+                                                      spi_sync_immediate);
+                       SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics,
+                                                      spi_sync_immediate);
                        __spi_pump_messages(master, false);
+               }
 
                wait_for_completion(&done);
                status = message->status;
index d673072346f2e0415fd4706c7606d5081f87f686..269e8afd3e2a5826c38282a3577447f3ff42997b 100644 (file)
@@ -23,6 +23,8 @@
 #include <linux/scatterlist.h>
 
 struct dma_chan;
+struct spi_master;
+struct spi_transfer;
 
 /*
  * INTERFACES between SPI master-side drivers and SPI infrastructure.
@@ -30,6 +32,59 @@ struct dma_chan;
  */
 extern struct bus_type spi_bus_type;
 
+/**
+ * struct spi_statistics - statistics for spi transfers
+ * @clock:         lock protecting this structure
+ *
+ * @messages:      number of spi-messages handled
+ * @transfers:     number of spi_transfers handled
+ * @errors:        number of errors during spi_transfer
+ * @timedout:      number of timeouts during spi_transfer
+ *
+ * @spi_sync:      number of times spi_sync is used
+ * @spi_sync_immediate:
+ *                 number of times spi_sync is executed immediately
+ *                 in calling context without queuing and scheduling
+ * @spi_async:     number of times spi_async is used
+ *
+ * @bytes:         number of bytes transferred to/from device
+ * @bytes_tx:      number of bytes sent to device
+ * @bytes_rx:      number of bytes received from device
+ *
+ */
+struct spi_statistics {
+       spinlock_t              lock; /* lock for the whole structure */
+
+       unsigned long           messages;
+       unsigned long           transfers;
+       unsigned long           errors;
+       unsigned long           timedout;
+
+       unsigned long           spi_sync;
+       unsigned long           spi_sync_immediate;
+       unsigned long           spi_async;
+
+       unsigned long long      bytes;
+       unsigned long long      bytes_rx;
+       unsigned long long      bytes_tx;
+
+};
+
+void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
+                                      struct spi_transfer *xfer,
+                                      struct spi_master *master);
+
+#define SPI_STATISTICS_ADD_TO_FIELD(stats, field, count)       \
+       do {                                                    \
+               unsigned long flags;                            \
+               spin_lock_irqsave(&(stats)->lock, flags);       \
+               (stats)->field += count;                        \
+               spin_unlock_irqrestore(&(stats)->lock, flags);  \
+       } while (0)
+
+#define SPI_STATISTICS_INCREMENT_FIELD(stats, field)   \
+       SPI_STATISTICS_ADD_TO_FIELD(stats, field, 1)
+
 /**
  * struct spi_device - Master side proxy for an SPI slave device
  * @dev: Driver model representation of the device.
@@ -60,6 +115,8 @@ extern struct bus_type spi_bus_type;
  * @cs_gpio: gpio number of the chipselect line (optional, -ENOENT when
  *     when not using a GPIO line)
  *
+ * @statistics: statistics for the spi_device
+ *
  * A @spi_device is used to interchange data between an SPI slave
  * (usually a discrete chip) and CPU memory.
  *
@@ -98,6 +155,9 @@ struct spi_device {
        char                    modalias[SPI_NAME_SIZE];
        int                     cs_gpio;        /* chip select gpio */
 
+       /* the statistics */
+       struct spi_statistics   statistics;
+
        /*
         * likely need more hooks for more protocol options affecting how
         * the controller talks to each chip, like:
@@ -296,6 +356,7 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv)
  * @cs_gpios: Array of GPIOs to use as chip select lines; one per CS
  *     number. Any individual value may be -ENOENT for CS lines that
  *     are not GPIOs (driven by the SPI controller itself).
+ * @statistics: statistics for the spi_master
  * @dma_tx: DMA transmit channel
  * @dma_rx: DMA receive channel
  * @dummy_rx: dummy receive buffer for full-duplex devices
@@ -452,6 +513,9 @@ struct spi_master {
        /* gpio chip select */
        int                     *cs_gpios;
 
+       /* statistics */
+       struct spi_statistics   statistics;
+
        /* DMA channels for use with core dmaengine helpers */
        struct dma_chan         *dma_tx;
        struct dma_chan         *dma_rx;