diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index cf8b91b23a76..07476ca083a0 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -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; diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index d673072346f2..269e8afd3e2a 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -23,6 +23,8 @@ #include 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;