📖

petalinux(zynq-7000)のUART受信データをIOCTLで読み込む

2025/02/19に公開

困ったこと

RS-485で通信ラインで複数のノードが通信する

それはRequet and Responseの通信で、Zynq-7000がMaster、他方がSlave

Zynq-7000が他方のノードに対してRequestをして、一定時間経過(ResponseがZynq-7000の受信バッファに全てたまる時間)FIONREAD(TIOCINQ)にて受信データ数を確認してから受信するプログラムを作成

実際、動かすと,一定時間経過後、受信データ数が0のままになる場合があった。

(受信したデータは次のRequest and ResponseのResponseで一緒に読み込まれてしまう)

問題

38400bpsの通信で、送信9byte 受信19byteの通信においてオシロスコープで見た感じresponseがRequestから10msec程度で返しているが、request送信から20msec待ってもFIONREADでは受信バイトが0のままだった

受信し終わるのを待つ時間の20msecを25msecにすれば、受信データ数が0になる回数は減らせるが、受信してからFIONREADに反映されるのに時間がかかるのも気持ち悪いので解消できないかと思った

調べて考えた原因

Zynq-7000のUARTにデータを受信したあとttyのバッファ(queue_work())にデータをstoreしにいくが
その処理がかなり重そうに見える(lockをかけたりマルチCPUの他方のCPU待ちをしたりのttyの処理があるためだと思う)
なのでttyのバッファにstoreしにいく処理がなかなか終わらず、20msec待ってもFIONREADが0のままなのではないか

変更内容

ttyのバッファ経由にするのが原因っぽいので、経由せずに受信データを取得できるようIOCTLで受信できるように変更

以下変更のpatch

From ccb8ee4bd2eaf744cd315fbef3e8fd4e0f579989 Mon Sep 17 00:00:00 2001
From: "Yoshiharu.Iwanaga" <i87g4486@yahoo.co.jp>
Date: Sat, 15 Feb 2025 13:53:07 +0000
Subject: [PATCH] xilinx_uartps_add_ioctl_directMode

---
 drivers/tty/serial/xilinx_uartps.c | 91 +++++++++++++++++++++++++++++-
 1 file changed, 90 insertions(+), 1 deletion(-)

diff --git a/drivers/tty/serial/xilinx_uartps.c b/drivers/tty/serial/xilinx_uartps.c
index a519c6cc9bee..2acecbc01af7 100644
--- a/drivers/tty/serial/xilinx_uartps.c
+++ b/drivers/tty/serial/xilinx_uartps.c
@@ -42,7 +42,7 @@ module_param(rx_trigger_level, uint, 0444);
 MODULE_PARM_DESC(rx_trigger_level, "Rx trigger level, 1-63 bytes");
 
 /* Rx Timeout */
-static int rx_timeout = 10;
+static int rx_timeout = 10; // rx timeout is 10bit counter, rx_timeout set this counter's bit9-bit2
 module_param(rx_timeout, uint, 0444);
 MODULE_PARM_DESC(rx_timeout, "Rx timeout, 1-255");
 
@@ -179,6 +179,8 @@ MODULE_PARM_DESC(rx_timeout, "Rx timeout, 1-255");
 #define CDNS_UART_SR_TXFULL	0x00000010 /* TX FIFO full */
 #define CDNS_UART_SR_RXTRIG	0x00000001 /* Rx Trigger */
 #define CDNS_UART_SR_TACTIVE	0x00000800 /* TX state machine active */
+#define CDNS_UART_SR_RACTIVE	0x00000400 /* TX state machine active */
+
 
 /* baud dividers min/max values */
 #define CDNS_UART_BDIV_MIN	4
@@ -214,6 +216,11 @@ struct cdns_uart {
 	bool			rs485_tx_started;
 	struct hrtimer		tx_timer;
 	struct reset_control	*rstc;
+
+	bool isModeDirect;
+	bool isRXtimeouted;
+	u8   rxBuff[512]; // fifo size
+	u8   rxBuffWrPos;
 };
 struct cdns_platform_data {
 	u32 quirks;
@@ -246,6 +253,38 @@ static void cdns_uart_handle_rx(void *dev_id, unsigned int isrstatus)
 	char status = TTY_NORMAL;
 	bool is_rxbs_support;
 
+	if( cdns_uart->isModeDirect == true )
+	{
+		if( cdns_uart->isRXtimeouted == true )
+		{
+			cdns_uart->isRXtimeouted = false;
+			cdns_uart->rxBuffWrPos = 0;
+		}
+		if ( (isrstatus & CDNS_UART_IXR_PARITY) != 0) {
+			port->icount.parity++;
+			return;
+		}
+		if ( (isrstatus & CDNS_UART_IXR_FRAMING) != 0) {
+			port->icount.frame++;
+			return;
+		}
+		if ( (isrstatus & CDNS_UART_IXR_OVERRUN) != 0) {
+			port->icount.overrun++;
+		}
+		while ((readl( port->membase + CDNS_UART_SR) &
+			CDNS_UART_SR_RXEMPTY) != CDNS_UART_SR_RXEMPTY) {
+			data = readl( port->membase + CDNS_UART_FIFO);
+			cdns_uart->rxBuff[ cdns_uart->rxBuffWrPos] = data;
+			if( cdns_uart->rxBuffWrPos < sizeof( cdns_uart->rxBuff) )
+				cdns_uart->rxBuffWrPos++;
+		}
+		if( (isrstatus & CDNS_UART_IXR_TOUT) != 0 )
+		{
+			cdns_uart->isRXtimeouted = true;
+		}
+		return;
+	}
+
 	is_rxbs_support = cdns_uart->quirks & CDNS_UART_RXBS_SUPPORT;
 
 	while ((readl(port->membase + CDNS_UART_SR) &
@@ -1249,6 +1288,51 @@ static void cdns_uart_pm(struct uart_port *port, unsigned int state,
 	}
 }
 
+static int cdns_uart_ioctl(struct uart_port *port, unsigned int cmd,
+			   unsigned long arg)
+{
+	struct cdns_uart *cdns_uart = port->private_data;
+	int ret = -ENOIOCTLCMD;
+
+	switch (cmd) {
+	case 0x547F:
+		if( cdns_uart->isModeDirect == true)
+		{
+			int i32regSR = readl(port->membase + CDNS_UART_SR);
+			bool isRxFIFOempty = (i32regSR & CDNS_UART_SR_RXEMPTY) ? true : false;
+			bool isRxActive = (i32regSR & CDNS_UART_SR_RACTIVE) ? true : false;
+
+			if( cdns_uart->isRXtimeouted == false) // RX receive is not receive of progress
+			{
+				int rtnVal = ( cdns_uart->rxBuffWrPos > 0) ?  0x10000/* receive progress */ : 0;
+				rtnVal |= (isRxFIFOempty == false) ? 0x20000 : 0;
+				rtnVal |= (isRxActive == true) ? 0x40000 : 0;
+				return rtnVal;
+			}
+			else
+			{
+				u32 buffSize = 0;;
+				//u32 reg_sr = readl(port->membase + CDNS_UART_SR);
+	
+				if( copy_from_user(&buffSize, (void __user *)arg, sizeof(u32)))
+					return -EFAULT;
+	
+				int copySize = (cdns_uart->rxBuffWrPos < buffSize) ? cdns_uart->rxBuffWrPos : buffSize;
+	
+				if(copy_to_user( (void __user *)arg, &cdns_uart->rxBuff[0], copySize))
+					return -EFAULT;
+
+				cdns_uart->isRXtimeouted = false;
+				cdns_uart->rxBuffWrPos = 0;
+				return copySize;//readl(port->membase + CDNS_UART_SR);
+			};
+			//printk( "xuartps1p 0x547F %08x\n", readl(port->membase + CDNS_UART_SR));
+		}
+		break;
+	}
+	return ret;
+}
+
 static const struct uart_ops cdns_uart_ops = {
 	.set_mctrl	= cdns_uart_set_mctrl,
 	.get_mctrl	= cdns_uart_get_mctrl,
@@ -1266,6 +1350,7 @@ static const struct uart_ops cdns_uart_ops = {
 	.request_port	= cdns_uart_request_port,
 	.release_port	= cdns_uart_release_port,
 	.config_port	= cdns_uart_config_port,
+	.ioctl 			= cdns_uart_ioctl,
 #ifdef CONFIG_CONSOLE_POLL
 	.poll_get_char	= cdns_uart_poll_get_char,
 	.poll_put_char	= cdns_uart_poll_put_char,
@@ -1805,6 +1890,10 @@ static int cdns_uart_probe(struct platform_device *pdev)
 		goto err_out_clk_notifier;
 	}
 
+	cdns_uart_data->isModeDirect = ( port->line == 1 ) ? true : false;
+	cdns_uart_data->rxBuffWrPos = 0;
+	cdns_uart_data->isRXtimeouted = false;
+
 	pm_runtime_use_autosuspend(&pdev->dev);
 	pm_runtime_set_autosuspend_delay(&pdev->dev, UART_AUTOSUSPEND_TIMEOUT);
 	pm_runtime_set_active(&pdev->dev);

以下IOCTLを使う側のプログラム

	dbuf[0] = 127;
	i32Tmp = ioctl( com_h, 0x547F, &dbuf[0]);
	if( i32Tmp < 0)
	{
		fprintf( stderr, "COM_recv ioctl error1 %s\n", ctime( &now ));
		return(-1);
	}
	if( i32Tmp >= 0x10000 )
	{
		int waitCnt = 10;
		while( waitCnt )
		{
			usleep( 1000/* usec */);
			i32Tmp = ioctl( com_h, 0x547F, &dbuf[0]);
			if( i32Tmp < 0)
			{
				fprintf( stderr, "COM_recv ioctl error2 %s\n", ctime( &now ));
				return(-1);
			}
			if( i32Tmp < 0x10000 )
			{
				break;
			}
			waitCnt--;
		}
		if( waitCnt == 0 )
		{
			fprintf( stderr, "COM_recv ioctl error3 %s\n", ctime( &now ));
			return(-1);
		}
		fprintf( stderr, "COM_recv ioctl progress waitCnt %d %s\n", waitCnt, ctime( &now ));
	}

Discussion