|
@@ -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;
|
|
|
}
|
|
@@ -476,21 +609,30 @@ static int spi_map_buf(struct spi_master *master, struct device *dev,
|
|
|
enum dma_data_direction dir)
|
|
|
{
|
|
|
const bool vmalloced_buf = is_vmalloc_addr(buf);
|
|
|
- const int desc_len = vmalloced_buf ? PAGE_SIZE : master->max_dma_len;
|
|
|
- const int sgs = DIV_ROUND_UP(len, desc_len);
|
|
|
+ int desc_len;
|
|
|
+ int sgs;
|
|
|
struct page *vm_page;
|
|
|
void *sg_buf;
|
|
|
size_t min;
|
|
|
int i, ret;
|
|
|
|
|
|
+ if (vmalloced_buf) {
|
|
|
+ desc_len = PAGE_SIZE;
|
|
|
+ sgs = DIV_ROUND_UP(len + offset_in_page(buf), desc_len);
|
|
|
+ } else {
|
|
|
+ desc_len = master->max_dma_len;
|
|
|
+ sgs = DIV_ROUND_UP(len, desc_len);
|
|
|
+ }
|
|
|
+
|
|
|
ret = sg_alloc_table(sgt, sgs, GFP_KERNEL);
|
|
|
if (ret != 0)
|
|
|
return ret;
|
|
|
|
|
|
for (i = 0; i < sgs; i++) {
|
|
|
- min = min_t(size_t, len, desc_len);
|
|
|
|
|
|
if (vmalloced_buf) {
|
|
|
+ min = min_t(size_t,
|
|
|
+ len, desc_len - offset_in_page(buf));
|
|
|
vm_page = vmalloc_to_page(buf);
|
|
|
if (!vm_page) {
|
|
|
sg_free_table(sgt);
|
|
@@ -499,6 +641,7 @@ static int spi_map_buf(struct spi_master *master, struct device *dev,
|
|
|
sg_set_page(&sgt->sgl[i], vm_page,
|
|
|
min, offset_in_page(buf));
|
|
|
} else {
|
|
|
+ min = min_t(size_t, len, desc_len);
|
|
|
sg_buf = buf;
|
|
|
sg_set_buf(&sgt->sgl[i], sg_buf, min);
|
|
|
}
|
|
@@ -703,17 +846,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;
|
|
@@ -729,6 +884,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;
|
|
@@ -1430,10 +1589,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
|
|
@@ -1599,6 +1758,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);
|
|
@@ -1966,6 +2127,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);
|
|
@@ -2102,6 +2266,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);
|
|
|
|
|
@@ -2129,8 +2296,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;
|