From 65598c13fd66c3b5eac16d5b8eacc704aa17ce40 Mon Sep 17 00:00:00 2001 From: Andrew Gabbasov Date: Tue, 30 Jun 2015 10:48:37 -0500 Subject: spi: Fix per-page mapping of unaligned vmalloc-ed buffer spi_map_buf() processes mapping of vmalloc-ed buffers in a special way, making mapping of every page separately. However, if the buffer is not aligned to page boundary (e.g. sub-array in a vmalloc-ed array), it fills the scatter table with page-size unaligned pieces, that cross page boundaries. This is incorrect and can, for example, cause memory corruption and various crashes when working with ubifs on spi-nor chips (though those drivers are themselves buggy in that they should be providing DMAable memory to the SPI framework). Fix this by using proper scatter table size and intra-page buffer lengths, so that the whole buffer splits into separate scatter table entries on page boundaries. Signed-off-by: Andrew Gabbasov Signed-off-by: Mark Brown --- drivers/spi/spi.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'drivers/spi/spi.c') diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index cf8b91b23a76..27e4f1f116ee 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -476,21 +476,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 +508,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); } -- cgit v1.2.1 From eca2ebc7e007c9e2b8f5ecfcfc74b53fbe68e42b Mon Sep 17 00:00:00 2001 From: Martin Sperl Date: Mon, 22 Jun 2015 13:00:36 +0000 Subject: spi: expose spi_master and spi_device statistics via sysfs 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 Signed-off-by: Mark Brown --- drivers/spi/spi.c | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 3 deletions(-) (limited to 'drivers/spi/spi.c') 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; -- cgit v1.2.1 From c37f45b5f1cdfcdc351d88950b32658c970582ca Mon Sep 17 00:00:00 2001 From: Leilk Liu Date: Thu, 23 Jul 2015 17:10:40 +0800 Subject: spi: support spi without dma channel to use can_dma() For spi without dma channel and use can_dma(), it can use master->dev for struct device. Signed-off-by: Leilk Liu Signed-off-by: Mark Brown --- drivers/spi/spi.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'drivers/spi/spi.c') diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index cf8b91b23a76..f725085a5f07 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -539,8 +539,15 @@ static int __spi_map_msg(struct spi_master *master, struct spi_message *msg) if (!master->can_dma) return 0; - tx_dev = master->dma_tx->device->dev; - rx_dev = master->dma_rx->device->dev; + if (master->dma_tx) + tx_dev = master->dma_tx->device->dev; + else + tx_dev = &master->dev; + + if (master->dma_rx) + rx_dev = master->dma_rx->device->dev; + else + rx_dev = &master->dev; list_for_each_entry(xfer, &msg->transfers, transfer_list) { if (!master->can_dma(master, msg->spi, xfer)) @@ -579,8 +586,15 @@ static int __spi_unmap_msg(struct spi_master *master, struct spi_message *msg) if (!master->cur_msg_mapped || !master->can_dma) return 0; - tx_dev = master->dma_tx->device->dev; - rx_dev = master->dma_rx->device->dev; + if (master->dma_tx) + tx_dev = master->dma_tx->device->dev; + else + tx_dev = &master->dev; + + if (master->dma_rx) + rx_dev = master->dma_rx->device->dev; + else + rx_dev = &master->dev; list_for_each_entry(xfer, &msg->transfers, transfer_list) { if (!master->can_dma(master, msg->spi, xfer)) -- cgit v1.2.1 From 7dc9fbc342deb2e2658ebdecb5ffd7ff57945a66 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 20 Aug 2015 11:52:18 -0700 Subject: spi: Fall back to master maximum speed if no slave speed specified If a slave appears with no maximum transfer speed specified fall back to using the maximum for the master instead. It's questionable if we should let slaves do this but let's be defensive. Signed-off-by: Mark Brown --- drivers/spi/spi.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/spi/spi.c') diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index cf8b91b23a76..637d892b316d 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1860,6 +1860,8 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message) if (!xfer->speed_hz) xfer->speed_hz = spi->max_speed_hz; + if (!xfer->speed_hz) + xfer->speed_hz = master->max_speed_hz; if (master->max_speed_hz && xfer->speed_hz > master->max_speed_hz) -- cgit v1.2.1 From 63ab645f4d8b2dc1351c41751e7ebb1b3f1c99d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Br=C3=BCns?= Date: Sun, 23 Aug 2015 16:06:30 +0200 Subject: spi: check bits_per_word in spi_setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows drivers for devices connected via SPI to check if the controller supports a given bits_per_word value during setup. Currently any BPW value is accepted durings setup, and transfers are rejected later. Signed-off-by: Stefan BrĂ¼ns Signed-off-by: Mark Brown --- drivers/spi/spi.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'drivers/spi/spi.c') diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 637d892b316d..829323ce7cdf 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1740,6 +1740,20 @@ EXPORT_SYMBOL_GPL(spi_busnum_to_master); * other core methods are currently defined as inline functions. */ +static int __spi_validate_bits_per_word(struct spi_master *master, u8 bits_per_word) +{ + if (master->bits_per_word_mask) { + /* Only 32 bits fit in the mask */ + if (bits_per_word > 32) + return -EINVAL; + if (!(master->bits_per_word_mask & + SPI_BPW_MASK(bits_per_word))) + return -EINVAL; + } + + return 0; +} + /** * spi_setup - setup SPI mode and clock rate * @spi: the device whose settings are being modified @@ -1798,6 +1812,9 @@ int spi_setup(struct spi_device *spi) if (!spi->bits_per_word) spi->bits_per_word = 8; + if (__spi_validate_bits_per_word(spi->master, spi->bits_per_word)) + return -EINVAL; + if (!spi->max_speed_hz) spi->max_speed_hz = spi->master->max_speed_hz; @@ -1867,14 +1884,8 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message) xfer->speed_hz > master->max_speed_hz) xfer->speed_hz = master->max_speed_hz; - if (master->bits_per_word_mask) { - /* Only 32 bits fit in the mask */ - if (xfer->bits_per_word > 32) - return -EINVAL; - if (!(master->bits_per_word_mask & - BIT(xfer->bits_per_word - 1))) - return -EINVAL; - } + if (__spi_validate_bits_per_word(master, xfer->bits_per_word)) + return -EINVAL; /* * SPI transfer length should be multiple of SPI word size -- cgit v1.2.1