mirror of
				https://github.com/Ysurac/openmptcprouter.git
				synced 2025-03-09 15:40:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			117 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			117 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From dbca2148f4c21454da461f1208b05d3a5887d133 Mon Sep 17 00:00:00 2001
 | |
| From: Phil Elwell <phil@raspberrypi.org>
 | |
| Date: Tue, 20 Feb 2018 11:48:15 +0000
 | |
| Subject: [PATCH 246/277] sc16is7xx: Fix for multi-channel stall
 | |
| 
 | |
| The SC16IS752 is a dual-channel device. The two channels are largely
 | |
| independent, but the IRQ signals are wired together as an open-drain,
 | |
| active low signal which will be driven low while either of the
 | |
| channels requires attention, which can be for significant periods of
 | |
| time until operations complete and the interrupt can be acknowledged.
 | |
| In that respect it is should be treated as a true level-sensitive IRQ.
 | |
| 
 | |
| The kernel, however, needs to be able to exit interrupt context in
 | |
| order to use I2C or SPI to access the device registers (which may
 | |
| involve sleeping).  Therefore the interrupt needs to be masked out or
 | |
| paused in some way.
 | |
| 
 | |
| The usual way to manage sleeping from within an interrupt handler
 | |
| is to use a threaded interrupt handler - a regular interrupt routine
 | |
| does the minimum amount of work needed to triage the interrupt before
 | |
| waking the interrupt service thread. If the threaded IRQ is marked as
 | |
| IRQF_ONESHOT the kernel will automatically mask out the interrupt
 | |
| until the thread runs to completion. The sc16is7xx driver used to
 | |
| use a threaded IRQ, but a patch switched to using a kthread_worker
 | |
| in order to set realtime priorities on the handler thread and for
 | |
| other optimisations. The end result is non-threaded IRQ that
 | |
| schedules some work then returns IRQ_HANDLED, making the kernel
 | |
| think that all IRQ processing has completed.
 | |
| 
 | |
| The work-around to prevent a constant stream of interrupts is to
 | |
| mark the interrupt as edge-sensitive rather than level-sensitive,
 | |
| but interpreting an active-low source as a falling-edge source
 | |
| requires care to prevent a total cessation of interrupts. Whereas
 | |
| an edge-triggering source will generate a new edge for every interrupt
 | |
| condition a level-triggering source will keep the signal at the
 | |
| interrupting level until it no longer requires attention; in other
 | |
| words, the host won't see another edge until all interrupt conditions
 | |
| are cleared. It is therefore vital that the interrupt handler does not
 | |
| exit with an outstanding interrupt condition, otherwise the kernel
 | |
| will not receive another interrupt unless some other operation causes
 | |
| the interrupt state on the device to be cleared.
 | |
| 
 | |
| The existing sc16is7xx driver has a very simple interrupt "thread"
 | |
| (kthread_work job) that processes interrups on each channel in turn
 | |
| until there are no more. If both channels are active and the first
 | |
| channel starts interrupting while the handler for the second channel
 | |
| is running then it will not be detected and an IRQ stall ensues. This
 | |
| could be handled easily if there was a shared IRQ status register, or
 | |
| a convenient way to determine if the IRQ had been deasserted for any
 | |
| length of time, but both appear to be lacking.
 | |
| 
 | |
| Avoid this problem (or at least make it much less likely to happen)
 | |
| by reducing the granularity of per-channel interrupt processing
 | |
| to one condition per iteration, only exiting the overall loop when
 | |
| both channels are no longer interrupting.
 | |
| 
 | |
| Signed-off-by: Phil Elwell <phil@raspberrypi.org>
 | |
| ---
 | |
|  drivers/tty/serial/sc16is7xx.c | 22 ++++++++++++++++------
 | |
|  1 file changed, 16 insertions(+), 6 deletions(-)
 | |
| 
 | |
| diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c
 | |
| index ca54ce074a5f..e8b928877e66 100644
 | |
| --- a/drivers/tty/serial/sc16is7xx.c
 | |
| +++ b/drivers/tty/serial/sc16is7xx.c
 | |
| @@ -662,7 +662,7 @@ static void sc16is7xx_handle_tx(struct uart_port *port)
 | |
|  		uart_write_wakeup(port);
 | |
|  }
 | |
|  
 | |
| -static void sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
 | |
| +static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
 | |
|  {
 | |
|  	struct uart_port *port = &s->p[portno].port;
 | |
|  
 | |
| @@ -671,7 +671,7 @@ static void sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
 | |
|  
 | |
|  		iir = sc16is7xx_port_read(port, SC16IS7XX_IIR_REG);
 | |
|  		if (iir & SC16IS7XX_IIR_NO_INT_BIT)
 | |
| -			break;
 | |
| +			return false;
 | |
|  
 | |
|  		iir &= SC16IS7XX_IIR_ID_MASK;
 | |
|  
 | |
| @@ -693,16 +693,26 @@ static void sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
 | |
|  					    port->line, iir);
 | |
|  			break;
 | |
|  		}
 | |
| -	} while (1);
 | |
| +	} while (0);
 | |
| +	return true;
 | |
|  }
 | |
|  
 | |
|  static void sc16is7xx_ist(struct kthread_work *ws)
 | |
|  {
 | |
|  	struct sc16is7xx_port *s = to_sc16is7xx_port(ws, irq_work);
 | |
| -	int i;
 | |
|  
 | |
| -	for (i = 0; i < s->devtype->nr_uart; ++i)
 | |
| -		sc16is7xx_port_irq(s, i);
 | |
| +	while (1)
 | |
| +	{
 | |
| +		bool keep_polling = false;
 | |
| +		int i;
 | |
| +
 | |
| +		for (i = 0; i < s->devtype->nr_uart; ++i)
 | |
| +		{
 | |
| +			keep_polling |= sc16is7xx_port_irq(s, i);
 | |
| +		}
 | |
| +		if (!keep_polling)
 | |
| +			break;
 | |
| +	}
 | |
|  }
 | |
|  
 | |
|  static irqreturn_t sc16is7xx_irq(int irq, void *dev_id)
 | |
| -- 
 | |
| 2.16.1
 | |
| 
 |