[PATCH 7/7] spi/pl022: make the chip deselect handling thread safe

Viresh Kumar viresh.kumar at st.com
Tue Nov 22 03:28:56 EST 2011


On 11/22/2011 1:55 PM, Linus WALLEIJ wrote:
> From: Virupax Sadashivpetimath <virupax.sadashivpetimath at stericsson.com>
> 
> There is a possibility that the pump_message and giveback
> run in parallel on a SMP system. Both the pump_message and
> giveback threads work on the same SPI message queue.
> Results will be in correct if the pump_message gets to
> work on the queue first.
> 
> when the pump_message works with the queue, it reads the
> head of the queue and removes it from the queue. pump_message
> activates the chip select depending on this message read.
> 
> This leads to giveback working on the modified queue or a
> emptied queue. If the queue is empty or if the next message on
> the queue (which is not the actual next message, as the pump
> message has removed the next message from the queue) is not for
> the same SPI device as that Of the previous one, giveback will
> de-activate the chip select activated by pump_message(),
> which is wrong.
> 
> To solve this problem pump_message is made not to run and
> access the queue until the giveback is done handling the queue.
> I.e. by making the cur_msg NULL after the giveback has read the
> queue.
> 
> Also a state variable has been introduced to keep track of when
> the CS for next message is already activated and avoid to
> double-activate it.
> 
> Signed-off-by: Virupax Sadashivpetimath <virupax.sadashivpetimath at stericsson.com>
> Signed-off-by: Linus Walleij <linus.walleij at linaro.org>
> ---
>  drivers/spi/spi-pl022.c |   72 ++++++++++++++++++++++++-----------------------
>  1 files changed, 37 insertions(+), 35 deletions(-)
> 
> diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c
> index 13988a3..1cff8ad 100644
> --- a/drivers/spi/spi-pl022.c
> +++ b/drivers/spi/spi-pl022.c
> @@ -340,6 +340,10 @@ struct vendor_data {
>   * @cur_msg: Pointer to current spi_message being processed
>   * @cur_transfer: Pointer to current spi_transfer
>   * @cur_chip: pointer to current clients chip(assigned from controller_state)
> + * @next_msg_cs_active: the next message in the queue has been examined
> + *  and it was found that it uses the same chip select as the previous
> + *  message, so we left it active after the previous transfer, and it's
> + *  active already.
>   * @tx: current position in TX buffer to be read
>   * @tx_end: end position in TX buffer to be read
>   * @rx: current position in RX buffer to be written
> @@ -373,6 +377,7 @@ struct pl022 {
>  	struct spi_message		*cur_msg;
>  	struct spi_transfer		*cur_transfer;
>  	struct chip_data		*cur_chip;
> +	bool				next_msg_cs_active;
>  	void				*tx;
>  	void				*tx_end;
>  	void				*rx;
> @@ -445,23 +450,9 @@ static void giveback(struct pl022 *pl022)
>  	struct spi_transfer *last_transfer;
>  	unsigned long flags;
>  	struct spi_message *msg;
> -	void (*curr_cs_control) (u32 command);
> +	pl022->next_msg_cs_active = false;
>  
> -	/*
> -	 * This local reference to the chip select function
> -	 * is needed because we set curr_chip to NULL
> -	 * as a step toward termininating the message.
> -	 */
> -	curr_cs_control = pl022->cur_chip->cs_control;
> -	spin_lock_irqsave(&pl022->queue_lock, flags);
> -	msg = pl022->cur_msg;
> -	pl022->cur_msg = NULL;
> -	pl022->cur_transfer = NULL;
> -	pl022->cur_chip = NULL;
> -	queue_work(pl022->workqueue, &pl022->pump_messages);
> -	spin_unlock_irqrestore(&pl022->queue_lock, flags);
> -
> -	last_transfer = list_entry(msg->transfers.prev,
> +	last_transfer = list_entry(pl022->cur_msg->transfers.prev,
>  					struct spi_transfer,
>  					transfer_list);
>  
> @@ -473,18 +464,13 @@ static void giveback(struct pl022 *pl022)
>  		 */
>  		udelay(last_transfer->delay_usecs);
>  
> -	/*
> -	 * Drop chip select UNLESS cs_change is true or we are returning
> -	 * a message with an error, or next message is for another chip
> -	 */
> -	if (!last_transfer->cs_change)
> -		curr_cs_control(SSP_CHIP_DESELECT);
> -	else {
> +	if (!last_transfer->cs_change) {
>  		struct spi_message *next_msg;
>  
> -		/* Holding of cs was hinted, but we need to make sure
> -		 * the next message is for the same chip.  Don't waste
> -		 * time with the following tests unless this was hinted.
> +		/*
> +		 * cs_change was not set. We can keep the chip select
> +		 * enabled if there is message in the queue and it is
> +		 * for the same spi device.
>  		 *
>  		 * We cannot postpone this until pump_messages, because
>  		 * after calling msg->complete (below) the driver that
> @@ -501,14 +487,26 @@ static void giveback(struct pl022 *pl022)
>  					struct spi_message, queue);
>  		spin_unlock_irqrestore(&pl022->queue_lock, flags);
>  
> -		/* see if the next and current messages point
> -		 * to the same chip
> +		/*
> +		 * see if the next and current messages point
> +		 * to the same spi device.
>  		 */
> -		if (next_msg && next_msg->spi != msg->spi)
> +		if (next_msg && next_msg->spi != pl022->cur_msg->spi)
>  			next_msg = NULL;
> -		if (!next_msg || msg->state == STATE_ERROR)
> -			curr_cs_control(SSP_CHIP_DESELECT);
> +		if (!next_msg || pl022->cur_msg->state == STATE_ERROR)
> +			pl022->cur_chip->cs_control(SSP_CHIP_DESELECT);
> +		else
> +			pl022->next_msg_cs_active = true;
>  	}
> +
> +	spin_lock_irqsave(&pl022->queue_lock, flags);
> +	msg = pl022->cur_msg;
> +	pl022->cur_msg = NULL;
> +	pl022->cur_transfer = NULL;
> +	pl022->cur_chip = NULL;
> +	queue_work(pl022->workqueue, &pl022->pump_messages);
> +	spin_unlock_irqrestore(&pl022->queue_lock, flags);
> +
>  	msg->state = NULL;
>  	if (msg->complete)
>  		msg->complete(msg->context);
> @@ -1350,7 +1348,7 @@ static void pump_transfers(unsigned long data)
>  			 */
>  			udelay(previous->delay_usecs);
>  
> -		/* Drop chip select only if cs_change is requested */
> +		/* Reselect chip select only if cs_change was requested */
>  		if (previous->cs_change)
>  			pl022->cur_chip->cs_control(SSP_CHIP_SELECT);
>  	} else {
> @@ -1389,8 +1387,10 @@ static void do_interrupt_dma_transfer(struct pl022 *pl022)
>  	 */
>  	u32 irqflags = ENABLE_ALL_INTERRUPTS & ~SSP_IMSC_MASK_RXIM;
>  
> -	/* Enable target chip */
> -	pl022->cur_chip->cs_control(SSP_CHIP_SELECT);
> +	/* Enable target chip, if not already active */
> +	if (!pl022->next_msg_cs_active)
> +		pl022->cur_chip->cs_control(SSP_CHIP_SELECT);
> +
>  	if (set_up_next_transfer(pl022, pl022->cur_transfer)) {
>  		/* Error path */
>  		pl022->cur_msg->state = STATE_ERROR;
> @@ -1445,7 +1445,8 @@ static void do_polling_transfer(struct pl022 *pl022)
>  		} else {
>  			/* STATE_START */
>  			message->state = STATE_RUNNING;
> -			pl022->cur_chip->cs_control(SSP_CHIP_SELECT);
> +			if (!pl022->next_msg_cs_active)
> +				pl022->cur_chip->cs_control(SSP_CHIP_SELECT);
>  		}
>  
>  		/* Configuration Changing Per Transfer */
> @@ -1604,6 +1605,7 @@ static int start_queue(struct pl022 *pl022)
>  	pl022->cur_msg = NULL;
>  	pl022->cur_transfer = NULL;
>  	pl022->cur_chip = NULL;
> +	pl022->next_msg_cs_active = false;
>  	spin_unlock_irqrestore(&pl022->queue_lock, flags);
>  
>  	queue_work(pl022->workqueue, &pl022->pump_messages);

Reviewed-by: Viresh Kumar <viresh.kumar at st.com>

-- 
viresh



More information about the linux-arm-kernel mailing list