123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- From: Mark Brown <broonie@kernel.org>
- Date: Tue, 9 Dec 2014 21:38:05 +0000
- Subject: [PATCH] spi: Pump transfers inside calling context for spi_sync()
- If we are using the standard SPI message pump (which all drivers should be
- transitioning over to) then special case the message enqueue and instead of
- starting the worker thread to push messages to the hardware do so in the
- context of the caller if the controller is idle. This avoids a context
- switch in the common case where the controller has a single user in a
- single thread, for short PIO transfers there may be no need to context
- switch away from the calling context to complete the transfer.
- The code is a bit more complex than is desirable in part due to the need
- to handle drivers not using the standard queue and in part due to handling
- the various combinations of bus locking and asynchronous submission in
- interrupt context.
- It is still suboptimal since it will still wake the message pump for each
- transfer in order to schedule idling of the hardware and if multiple
- contexts are using the controller simultaneously a caller may end up
- pumping a message for some random other thread rather than for itself,
- and if the thread ends up deferring due to another context idling the
- hardware then it will just busy wait. It can, however, have the benefit
- of aggregating power up and down of the hardware when a caller performs
- a series of transfers back to back without any need for the use of
- spi_async().
- Signed-off-by: Mark Brown <broonie@kernel.org>
- ---
- --- a/drivers/spi/spi.c
- +++ b/drivers/spi/spi.c
- @@ -882,6 +882,9 @@ EXPORT_SYMBOL_GPL(spi_finalize_current_t
- * needs processing and if so call out to the driver to initialize hardware
- * and transfer each message.
- *
- + * Note that it is called both from the kthread itself and also from
- + * inside spi_sync(); the queue extraction handling at the top of the
- + * function should deal with this safely.
- */
- static void spi_pump_messages(struct kthread_work *work)
- {
- @@ -900,6 +903,13 @@ static void spi_pump_messages(struct kth
- return;
- }
-
- + /* If another context is idling the device then defer */
- + if (master->idling) {
- + queue_kthread_work(&master->kworker, &master->pump_messages);
- + spin_unlock_irqrestore(&master->queue_lock, flags);
- + return;
- + }
- +
- /* Check if the queue is idle */
- if (list_empty(&master->queue) || !master->running) {
- if (!master->busy) {
- @@ -907,7 +917,9 @@ static void spi_pump_messages(struct kth
- return;
- }
- master->busy = false;
- + master->idling = true;
- spin_unlock_irqrestore(&master->queue_lock, flags);
- +
- kfree(master->dummy_rx);
- master->dummy_rx = NULL;
- kfree(master->dummy_tx);
- @@ -921,6 +933,10 @@ static void spi_pump_messages(struct kth
- pm_runtime_put_autosuspend(master->dev.parent);
- }
- trace_spi_master_idle(master);
- +
- + spin_lock_irqsave(&master->queue_lock, flags);
- + master->idling = false;
- + spin_unlock_irqrestore(&master->queue_lock, flags);
- return;
- }
-
- @@ -1166,12 +1182,9 @@ static int spi_destroy_queue(struct spi_
- return 0;
- }
-
- -/**
- - * spi_queued_transfer - transfer function for queued transfers
- - * @spi: spi device which is requesting transfer
- - * @msg: spi message which is to handled is queued to driver queue
- - */
- -static int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg)
- +static int __spi_queued_transfer(struct spi_device *spi,
- + struct spi_message *msg,
- + bool need_pump)
- {
- struct spi_master *master = spi->master;
- unsigned long flags;
- @@ -1186,13 +1199,23 @@ static int spi_queued_transfer(struct sp
- msg->status = -EINPROGRESS;
-
- list_add_tail(&msg->queue, &master->queue);
- - if (!master->busy)
- + if (!master->busy && need_pump)
- queue_kthread_work(&master->kworker, &master->pump_messages);
-
- spin_unlock_irqrestore(&master->queue_lock, flags);
- return 0;
- }
-
- +/**
- + * spi_queued_transfer - transfer function for queued transfers
- + * @spi: spi device which is requesting transfer
- + * @msg: spi message which is to handled is queued to driver queue
- + */
- +static int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg)
- +{
- + return __spi_queued_transfer(spi, msg, true);
- +}
- +
- static int spi_master_initialize_queue(struct spi_master *master)
- {
- int ret;
- @@ -2104,19 +2127,46 @@ static int __spi_sync(struct spi_device
- DECLARE_COMPLETION_ONSTACK(done);
- int status;
- struct spi_master *master = spi->master;
- + unsigned long flags;
- +
- + status = __spi_validate(spi, message);
- + if (status != 0)
- + return status;
-
- message->complete = spi_complete;
- message->context = &done;
- + message->spi = spi;
-
- if (!bus_locked)
- mutex_lock(&master->bus_lock_mutex);
-
- - status = spi_async_locked(spi, message);
- + /* If we're not using the legacy transfer method then we will
- + * try to transfer in the calling context so special case.
- + * This code would be less tricky if we could remove the
- + * support for driver implemented message queues.
- + */
- + if (master->transfer == spi_queued_transfer) {
- + spin_lock_irqsave(&master->bus_lock_spinlock, flags);
- +
- + trace_spi_message_submit(message);
- +
- + status = __spi_queued_transfer(spi, message, false);
- +
- + spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);
- + } else {
- + status = spi_async_locked(spi, message);
- + }
-
- if (!bus_locked)
- mutex_unlock(&master->bus_lock_mutex);
-
- if (status == 0) {
- + /* Push out the messages in the calling context if we
- + * can.
- + */
- + if (master->transfer == spi_queued_transfer)
- + spi_pump_messages(&master->pump_messages);
- +
- wait_for_completion(&done);
- status = message->status;
- }
- --- a/include/linux/spi/spi.h
- +++ b/include/linux/spi/spi.h
- @@ -260,6 +260,7 @@ static inline void spi_unregister_driver
- * @pump_messages: work struct for scheduling work to the message pump
- * @queue_lock: spinlock to syncronise access to message queue
- * @queue: message queue
- + * @idling: the device is entering idle state
- * @cur_msg: the currently in-flight message
- * @cur_msg_prepared: spi_prepare_message was called for the currently
- * in-flight message
- @@ -425,6 +426,7 @@ struct spi_master {
- spinlock_t queue_lock;
- struct list_head queue;
- struct spi_message *cur_msg;
- + bool idling;
- bool busy;
- bool running;
- bool rt;
|