All of lore.kernel.org
 help / color / mirror / Atom feed
From: David Jander <david@protonic.nl>
To: Mark Brown <broonie@kernel.org>
Cc: linux-spi@vger.kernel.org, Andrew Lunn <andrew@lunn.ch>,
	Marc Kleine-Budde <mkl@pengutronix.de>
Subject: Re: [RFC] [PATCH 3/3] drivers: spi: spi.c: Don't use the message queue if possible in spi_sync
Date: Wed, 25 May 2022 16:46:03 +0200	[thread overview]
Message-ID: <20220525164603.57c98a0a@erd992> (raw)
In-Reply-To: <20220525142928.2335378-4-david@protonic.nl>

On Wed, 25 May 2022 16:29:28 +0200
David Jander <david@protonic.nl> wrote:

> The interaction with the controller message queue and its corresponding
> auxiliary flags and variables requires the use of the queue_lock which is
> costly. Since spi_sync will transfer the complete message anyway, and not
> return until it is finished, there is no need to put the message into the
> queue if the queue is empty. This can save a lot of overhead.
> 
> As an example of how significant this is, when using the MCP2518FD SPI CAN
> controller on a i.MX8MM SoC, the time during which the interrupt line
> stays active (during 3 relatively short spi_sync messages), is reduced
> from 98us to 72us by this patch.
> 
> Signed-off-by: David Jander <david@protonic.nl>
> ---
>  drivers/spi/spi.c       | 244 ++++++++++++++++++++++++----------------
>  include/linux/spi/spi.h |  11 +-
>  2 files changed, 157 insertions(+), 98 deletions(-)
> 
> diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
> index 1d50051f3d57..401ac2f4758e 100644
> --- a/drivers/spi/spi.c
> +++ b/drivers/spi/spi.c
> @@ -1558,6 +1558,80 @@ static void spi_idle_runtime_pm(struct spi_controller *ctlr)
>  	}
>  }
>  
> +static int __spi_pump_transfer_message(struct spi_controller *ctlr,
> +		struct spi_message *msg, bool was_busy)
> +{
> +	struct spi_transfer *xfer;
> +	int ret;
> +
> +	if (!was_busy && ctlr->auto_runtime_pm) {
> +		ret = pm_runtime_get_sync(ctlr->dev.parent);
> +		if (ret < 0) {
> +			pm_runtime_put_noidle(ctlr->dev.parent);
> +			dev_err(&ctlr->dev, "Failed to power device: %d\n",
> +				ret);
> +			return ret;
> +		}
> +	}
> +
> +	if (!was_busy)
> +		trace_spi_controller_busy(ctlr);
> +
> +	if (!was_busy && ctlr->prepare_transfer_hardware) {
> +		ret = ctlr->prepare_transfer_hardware(ctlr);
> +		if (ret) {
> +			dev_err(&ctlr->dev,
> +				"failed to prepare transfer hardware: %d\n",
> +				ret);
> +
> +			if (ctlr->auto_runtime_pm)
> +				pm_runtime_put(ctlr->dev.parent);
> +
> +			msg->status = ret;
> +			spi_finalize_message(msg);
> +
> +			return ret;
> +		}
> +	}
> +
> +	trace_spi_message_start(msg);
> +
> +	if (ctlr->prepare_message) {
> +		ret = ctlr->prepare_message(ctlr, msg);
> +		if (ret) {
> +			dev_err(&ctlr->dev, "failed to prepare message: %d\n",
> +				ret);
> +			msg->status = ret;
> +			spi_finalize_message(msg);
> +			return ret;
> +		}
> +		msg->prepared = true;
> +	}
> +
> +	ret = spi_map_msg(ctlr, msg);
> +	if (ret) {
> +		msg->status = ret;
> +		spi_finalize_message(msg);
> +		return ret;
> +	}
> +
> +	if (!ctlr->ptp_sts_supported && !ctlr->transfer_one) {
> +		list_for_each_entry(xfer, &msg->transfers, transfer_list) {
> +			xfer->ptp_sts_word_pre = 0;
> +			ptp_read_system_prets(xfer->ptp_sts);
> +		}
> +	}
> +
> +	ret = ctlr->transfer_one_message(ctlr, msg);
> +	if (ret) {
> +		dev_err(&ctlr->dev,
> +			"failed to transfer one message from queue\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
>  /**
>   * __spi_pump_messages - function which processes spi message queue
>   * @ctlr: controller to process queue for
> @@ -1573,7 +1647,6 @@ static void spi_idle_runtime_pm(struct spi_controller *ctlr)
>   */
>  static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread)
>  {
> -	struct spi_transfer *xfer;
>  	struct spi_message *msg;
>  	bool was_busy = false;
>  	unsigned long flags;
> @@ -1608,6 +1681,7 @@ static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread)
>  			    !ctlr->unprepare_transfer_hardware) {
>  				spi_idle_runtime_pm(ctlr);
>  				ctlr->busy = false;
> +				ctlr->queue_empty = true;
>  				trace_spi_controller_idle(ctlr);
>  			} else {
>  				kthread_queue_work(ctlr->kworker,
> @@ -1634,6 +1708,7 @@ static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread)
>  
>  		spin_lock_irqsave(&ctlr->queue_lock, flags);
>  		ctlr->idling = false;
> +		ctlr->queue_empty = true;
>  		spin_unlock_irqrestore(&ctlr->queue_lock, flags);
>  		return;
>  	}
> @@ -1650,75 +1725,7 @@ static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread)
>  	spin_unlock_irqrestore(&ctlr->queue_lock, flags);
>  
>  	mutex_lock(&ctlr->io_mutex);
> -
> -	if (!was_busy && ctlr->auto_runtime_pm) {
> -		ret = pm_runtime_get_sync(ctlr->dev.parent);
> -		if (ret < 0) {
> -			pm_runtime_put_noidle(ctlr->dev.parent);
> -			dev_err(&ctlr->dev, "Failed to power device: %d\n",
> -				ret);
> -			mutex_unlock(&ctlr->io_mutex);
> -			return;
> -		}
> -	}
> -
> -	if (!was_busy)
> -		trace_spi_controller_busy(ctlr);
> -
> -	if (!was_busy && ctlr->prepare_transfer_hardware) {
> -		ret = ctlr->prepare_transfer_hardware(ctlr);
> -		if (ret) {
> -			dev_err(&ctlr->dev,
> -				"failed to prepare transfer hardware: %d\n",
> -				ret);
> -
> -			if (ctlr->auto_runtime_pm)
> -				pm_runtime_put(ctlr->dev.parent);
> -
> -			msg->status = ret;
> -			spi_finalize_message(msg);
> -
> -			mutex_unlock(&ctlr->io_mutex);
> -			return;
> -		}
> -	}
> -
> -	trace_spi_message_start(msg);
> -
> -	if (ctlr->prepare_message) {
> -		ret = ctlr->prepare_message(ctlr, msg);
> -		if (ret) {
> -			dev_err(&ctlr->dev, "failed to prepare message: %d\n",
> -				ret);
> -			msg->status = ret;
> -			spi_finalize_message(msg);
> -			goto out;
> -		}
> -		msg->prepared = true;
> -	}
> -
> -	ret = spi_map_msg(ctlr, msg);
> -	if (ret) {
> -		msg->status = ret;
> -		spi_finalize_message(msg);
> -		goto out;
> -	}
> -
> -	if (!ctlr->ptp_sts_supported && !ctlr->transfer_one) {
> -		list_for_each_entry(xfer, &msg->transfers, transfer_list) {
> -			xfer->ptp_sts_word_pre = 0;
> -			ptp_read_system_prets(xfer->ptp_sts);
> -		}
> -	}
> -
> -	ret = ctlr->transfer_one_message(ctlr, msg);
> -	if (ret) {
> -		dev_err(&ctlr->dev,
> -			"failed to transfer one message from queue\n");
> -		goto out;
> -	}
> -
> -out:
> +	ret = __spi_pump_transfer_message(ctlr, msg, was_busy);
>  	mutex_unlock(&ctlr->io_mutex);
>  
>  	/* Prod the scheduler in case transfer_one() was busy waiting */
> @@ -1848,6 +1855,7 @@ static int spi_init_queue(struct spi_controller *ctlr)
>  {
>  	ctlr->running = false;
>  	ctlr->busy = false;
> +	ctlr->queue_empty = true;
>  
>  	ctlr->kworker = kthread_create_worker(0, dev_name(&ctlr->dev));
>  	if (IS_ERR(ctlr->kworker)) {
> @@ -1941,11 +1949,15 @@ void spi_finalize_message(struct spi_message *mesg)
>  
>  	mesg->prepared = false;
>  
> -	spin_lock_irqsave(&ctlr->queue_lock, flags);
> -	ctlr->cur_msg = NULL;
> -	ctlr->fallback = false;
> -	kthread_queue_work(ctlr->kworker, &ctlr->pump_messages);
> -	spin_unlock_irqrestore(&ctlr->queue_lock, flags);
> +	if (!mesg->sync) {
> +		spin_lock_irqsave(&ctlr->queue_lock, flags);
> +		WARN(ctlr->cur_msg != mesg,
> +			"Finalizing queued message that is not the current head of queue!");
> +		ctlr->cur_msg = NULL;
> +		ctlr->fallback = false;
> +		kthread_queue_work(ctlr->kworker, &ctlr->pump_messages);
> +		spin_unlock_irqrestore(&ctlr->queue_lock, flags);
> +	}
>  
>  	trace_spi_message_done(mesg);
>  
> @@ -2048,6 +2060,7 @@ static int __spi_queued_transfer(struct spi_device *spi,
>  	msg->status = -EINPROGRESS;
>  
>  	list_add_tail(&msg->queue, &ctlr->queue);
> +	ctlr->queue_empty = false;
>  	if (!ctlr->busy && need_pump)
>  		kthread_queue_work(ctlr->kworker, &ctlr->pump_messages);
>  
> @@ -3937,6 +3950,42 @@ static int spi_async_locked(struct spi_device *spi, struct spi_message *message)
>  
>  }
>  
> +static void __spi_transfer_message_noqueue(struct spi_controller *ctlr, struct spi_message *msg)
> +{
> +	bool was_busy;
> +	int ret;
> +
> +	mutex_lock(&ctlr->io_mutex);
> +
> +	/* If another context is idling the device then wait */
> +	while (ctlr->idling) {
> +		printk(KERN_INFO "spi sync message processing: controller is idling!\n");
> +		usleep_range(10000, 11000);
> +	}

This is dead ugly of course, and it needs to be removed. Not yet sure how,
hence the RFC. Maybe the idle -> not busy transition can be included inside
the io_mutex? That way this while will never be hit and can be removed...

Best regards,

> +	was_busy = READ_ONCE(ctlr->busy);
> +
> +	ret = __spi_pump_transfer_message(ctlr, msg, was_busy);
> +	if (ret)
> +		goto out;
> +
> +	if (!was_busy) {
> +		kfree(ctlr->dummy_rx);
> +		ctlr->dummy_rx = NULL;
> +		kfree(ctlr->dummy_tx);
> +		ctlr->dummy_tx = NULL;
> +		if (ctlr->unprepare_transfer_hardware &&
> +		    ctlr->unprepare_transfer_hardware(ctlr))
> +			dev_err(&ctlr->dev,
> +				"failed to unprepare transfer hardware\n");
> +		spi_idle_runtime_pm(ctlr);
> +	}
> +
> +out:
> +	mutex_unlock(&ctlr->io_mutex);
> +	return;
> +}
> +
>  /*-------------------------------------------------------------------------*/
>  
>  /*
> @@ -3955,51 +4004,52 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message)
>  	DECLARE_COMPLETION_ONSTACK(done);
>  	int status;
>  	struct spi_controller *ctlr = spi->controller;
> -	unsigned long flags;
>  
>  	status = __spi_validate(spi, message);
>  	if (status != 0)
>  		return status;
>  
> -	message->complete = spi_complete;
> -	message->context = &done;
>  	message->spi = spi;
>  
>  	SPI_STATISTICS_INCREMENT_FIELD(ctlr->pcpu_statistics, spi_sync);
>  	SPI_STATISTICS_INCREMENT_FIELD(spi->pcpu_statistics, spi_sync);
>  
>  	/*
> -	 * 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.
> +	 * Checking queue_empty here only guarantees async/sync message
> +	 * ordering when coming from the same context. It does not need to
> +	 * guard against reentrancy from a different context. The io_mutex
> +	 * will catch those cases.
>  	 */
> -	if (ctlr->transfer == spi_queued_transfer) {
> -		spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);
> +	if (READ_ONCE(ctlr->queue_empty)) {
> +		message->sync = true;
> +		message->actual_length = 0;
> +		message->status = -EINPROGRESS;
>  
>  		trace_spi_message_submit(message);
>  
> -		status = __spi_queued_transfer(spi, message, false);
> +		SPI_STATISTICS_INCREMENT_FIELD(ctlr->pcpu_statistics, spi_sync_immediate);
> +		SPI_STATISTICS_INCREMENT_FIELD(spi->pcpu_statistics, spi_sync_immediate);
>  
> -		spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);
> -	} else {
> -		status = spi_async_locked(spi, message);
> +		__spi_transfer_message_noqueue(ctlr, message);
> +
> +		return message->status;
>  	}
>  
> +	/*
> +	 * There are messages in the async queue that could have originated
> +	 * from the same context, so we need to preserve ordering.
> +	 * Therefor we send the message to the async queue and wait until they
> +	 * are completed.
> +	 */
> +	message->complete = spi_complete;
> +	message->context = &done;
> +	status = spi_async_locked(spi, message);
>  	if (status == 0) {
> -		/* Push out the messages in the calling context if we can */
> -		if (ctlr->transfer == spi_queued_transfer) {
> -			SPI_STATISTICS_INCREMENT_FIELD(ctlr->pcpu_statistics,
> -						       spi_sync_immediate);
> -			SPI_STATISTICS_INCREMENT_FIELD(spi->pcpu_statistics,
> -						       spi_sync_immediate);
> -			__spi_pump_messages(ctlr, false);
> -		}
> -
>  		wait_for_completion(&done);
>  		status = message->status;
>  	}
>  	message->context = NULL;
> +
>  	return status;
>  }
>  
> diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
> index 43ec1e262913..9caed8537755 100644
> --- a/include/linux/spi/spi.h
> +++ b/include/linux/spi/spi.h
> @@ -449,6 +449,8 @@ extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 ch
>   * @irq_flags: Interrupt enable state during PTP system timestamping
>   * @fallback: fallback to pio if dma transfer return failure with
>   *	SPI_TRANS_FAIL_NO_START.
> + * @queue_empty: signal green light for opportunistically skipping the queue
> + * 	for spi_sync transfers.
>   *
>   * Each SPI controller can communicate with one or more @spi_device
>   * children.  These make a small bus, sharing MOSI, MISO and SCK signals
> @@ -665,6 +667,9 @@ struct spi_controller {
>  
>  	/* Interrupt enable state during PTP system timestamping */
>  	unsigned long		irq_flags;
> +
> +	/* Flag for enabling opportunistic skipping of the queue in spi_sync */
> +	bool			queue_empty;
>  };
>  
>  static inline void *spi_controller_get_devdata(struct spi_controller *ctlr)
> @@ -974,6 +979,7 @@ struct spi_transfer {
>   * @state: for use by whichever driver currently owns the message
>   * @resources: for resource management when the spi message is processed
>   * @prepared: spi_prepare_message was called for the this message
> + * @sync: this message took the direct sync path skipping the async queue
>   *
>   * A @spi_message is used to execute an atomic sequence of data transfers,
>   * each represented by a struct spi_transfer.  The sequence is "atomic"
> @@ -1025,7 +1031,10 @@ struct spi_message {
>  	struct list_head        resources;
>  
>  	/* spi_prepare_message was called for this message */
> -	bool                    prepared;
> +	bool			prepared;
> +
> +	/* this message is skipping the async queue */
> +	bool			sync;
>  };
>  
>  static inline void spi_message_init_no_memset(struct spi_message *m)


-- 
David Jander

  reply	other threads:[~2022-05-25 14:46 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-05-25 14:29 [RFC] [PATCH 0/3] Optimize spi_sync path David Jander
2022-05-25 14:29 ` [RFC] [PATCH 1/3] drivers: spi: API: spi_finalize_current_message -> spi_finalize_message David Jander
2022-05-25 14:29 ` [RFC] [PATCH 2/3] drivers: spi: spi.c: Move ctlr->cur_msg_prepared to struct spi_message David Jander
2022-05-25 14:29 ` [RFC] [PATCH 3/3] drivers: spi: spi.c: Don't use the message queue if possible in spi_sync David Jander
2022-05-25 14:46   ` David Jander [this message]
2022-06-07 18:30     ` Mark Brown
2022-06-08  7:54       ` David Jander
2022-06-08 11:29         ` Mark Brown
2022-06-09 15:34           ` David Jander
2022-06-09 16:31             ` Mark Brown
2022-06-10  7:27               ` David Jander
2022-06-10 13:41                 ` Mark Brown
2022-06-10 18:17                   ` Mark Brown
2022-06-13  9:05                     ` David Jander
2022-06-13 11:56                       ` Mark Brown
2022-07-15  7:47                         ` Thomas Kopp
2022-07-15  9:02                           ` Thomas Kopp
2022-06-08 13:43   ` Andy Shevchenko
2022-06-08 14:55     ` David Jander
2022-05-30 12:06 ` [RFC] [PATCH 0/3] Optimize spi_sync path Mark Brown

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220525164603.57c98a0a@erd992 \
    --to=david@protonic.nl \
    --cc=andrew@lunn.ch \
    --cc=broonie@kernel.org \
    --cc=linux-spi@vger.kernel.org \
    --cc=mkl@pengutronix.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.