Xuartctl

From embeddedTS Manuals
Revision as of 17:52, 17 January 2022 by Lionel (talk | contribs) (Links auto-updated for 2022 re-branding ( https://files.embeddedarm.com/ts-arm-sbc/ts-7500-linux/sources/xuartcore.h →‎ https://files.embeddedTS.com/ts-arm-sbc/ts-7500-linux/sources/xuartcore.h https://files.embeddedarm.com/ts-arm-sbc/ts-7500-linux/sources/xuartcore.c →‎ https://files.embeddedTS.com/ts-arm-sbc/ts-7500-linux/sources/xuartcore.c https://files.embeddedarm.com/ts-arm-sbc/ts-7500-linux/sources/libxuartctl.c →‎ https://files.embeddedTS.com/ts-arm-sbc/ts-7500-linux/sources/lib...)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
xuartctl
Xuartctl-diagram.png

xuartctl is a userspace driver utility to manage the UARTs that go through the FPGA.

Overview

The XUARTs are ttl serial ports implemented in the FPGA. These communicate with the userspace driver xuartctl. Each XUART core in the FPGA can handle up to 8 XUARTs (or less). The 8 serial ports have a single shared 4kByte receive FIFO which makes real time interrupt latency response less of a concern and in actual implementation, the serial ports are simply polled at 100Hz and don't even use an IRQ. Even with all 8 ports running at 230400 baud, it is not possible to overflow the receive FIFO in 1/100th of a second. The "xuartctl --server" daemon is started by default in the INITRD linuxrc file which sets up listening TCP/IP ports for all 8 XUART channels on ports 7350-7357. An application may simply connect to these ports via localhost (or via the network) and use the serial ports as if they were network services.

WARNING: Since these are not kernel drivers, utlities like stty or setserial will not work to control the UARTS.

Depending on the product you are using, there can be a varying number of XUARTs between 0 and 8. To see the XUARTs in the default FPGA, you can run xuartctl on its own:

 # xuartctl
 xuarts_detected=7
 xuartctl_server_pid=343
 xuartctl_wakeups=2784217
 xuartctl_txbytes=9
 xuartctl_rxbytes=10335090

Usage

The xuartctl utility is split into 2 parts. The first part is the main server. This server must run on the board and provides the communication between the FPGA and the TCP ports. This can be created by running:

xuartctl --server

You can also specify the speed and mode arguments to make them the default for the port servers. The port servers are created by simply specifying a port after the main server has been started:

xuartctl --server --port=0 --speed=115200

Help

Usage: xuartctl [OPTION] ...
       xuartctl --port=PORT [OPTION] ... -- [COMMAND] [ARGS]
Technologic Systems XUART core userspace driver utility.
Example: xuartctl --server
         xuartctl --port=192.168.0.50:7350 --speed=9600 -- /bin/sh -i
         xuartctl --port=0 --test

  -i, --irq=N             Use IRQ N as XUART IRQ (32)
  -r, --regstart=ADD      Use ADD address as regstart (0x600ff100)
  -m, --memstart=ADD      Use ADD address as memstart (0x60000000)
  -s, --speed=BAUD        Use BAUD as default baudrate (115200)
  -o, --mode=MODE         Use MODE as default mode (8n1)
  -d, --server            Daemonize and run as server
  -I, --bind=IPADDR       Bind server to IPADDR
  -p, --port=PORT         Connect to local or remote XUART port
  -t, --test              Run loopback and latency test
  -h, --help              This help

Supported Modes

Mode Notes
8n1 (default mode)
8n2
dmx baudrate argument is ignored, uses 250 kbaud
8e1
8o1
8e2
8o2
7n1
7n2
7e1
7o1
7e2
7o2
9n1 The pseudo-tty is incompatible with this mode. This mode must be used with raw.
raw Allows RAW 16 bit communication with the main server

Programming with XUARTS

Programming for the XUARTS is essentially the same as any other UART. Either of these are great resources for serial programming:

There are a few differences with these UARTs. The flush operation is not supported by the psuedo terminal layer. Also the mode and baud rate cannot be changed in code. You need to call xuartctl to set up the mode and baud rate before running your application.

Before you start your application from an init script or the linuxrc script you would first set up the XUART. On the TS-7553 the XBee port is XUART 3.

eval $(xuartctl --server --port 3 --speed 9600 2>&1); ln -s $ttyname /dev/ttyxuart3

# Now that the xuart device is created you can start your application:
myapp &

This example below is used to detect an XBEE module.

#include <stdio.h>
#include <stdlib.h>

#define XBEEDEVICE "/dev/ttyxuart3"

int main(int argc, char **argv)
{
    int fd, c, res;
    struct termios oldtio, newtio;
    char buf[255] = {0};
    
    fd = open(XBEEDEVICE, O_RDWR | O_NOCTTY ); 
    if(fd <0) 
    {
            perror(XBEEDEVICE); 
            return -1;
    }
    
    tcgetattr(fd, &oldtio); /* save current port settings */
    
    bzero(&newtio, sizeof(newtio));
    newtio.c_cflag = CRTSCTS | CS8 | CLOCAL | CREAD;
    newtio.c_iflag = IGNPAR;
    newtio.c_oflag = 0;
    /* set input mode (non-canonical, no echo,...) */
    newtio.c_lflag = 0;
     
    newtio.c_cc[VTIME]    = 0;   /* inter-character timer unused */
    newtio.c_cc[VMIN]     = 2;   /* blocking read until 2 chars received */
    
    tcflush(fd, TCIFLUSH);
    tcsetattr(fd, TCSANOW, &newtio);

    // Sending +++ within 1 second sets the XBee into command mode
    write(fd, "+++", 3);

    res = read(fd, buf, 255);
    buf[res] = 0;

    if(0 == strncmp(buf, "OK", 2))
    {
            printf("XBee Detected\n");
    }
    else
    {
            printf("Could not find XBee\n");
    }

    tcsetattr(fd, TCSANOW, &oldtio);

    return 0;
}

Examples

Run Local TCP Only

By default when the main XUART server is created it opens ports so they are accessible from other systems. To bind locally, create the main server by running this:

   xuartctl --server --bind=127.0.0.1

Change The Default Mode and Baud Rate For All Ports

The default mode is "8N1" and default baud rate is 115200-- should the default need to be changed, modifying the linuxrc line that invokes the XUART server can be changed from:

   xuartctl --server

to

   xuartctl --server --speed=9600 --mode=7e1

Configure baud rate for specific port

xuartctl --port 0 --speed 115200 --server

Configure Consistent XUARTS On Startup

During startup (usually linuxrc for fastboot, or /etc/rc.local for debian) you can add these lines to initialize the port servers so they are consistent.

## If you're using this in rc.local, uncomment the next line so the xuartctl binary can be found
# PATH=/usr/local/bin:/usr/bin:/bin
eval $(xuartctl --server --port 0 --speed 9600 2>&1); ln -sf $ttyname /dev/ttyxuart0
eval $(xuartctl --server --port 1 --speed 115200 2>&1); ln -sf $ttyname /dev/ttyxuart1
eval $(xuartctl --server --port 2 --speed 38400 2>&1); ln -sf $ttyname /dev/ttyxuart2

Connect to a Remote Main XUART Server

If the xuartctl --server is running on 192.168.0.50, you could run this from any remote system:

# :7350 is XUART0, 7351 is XUART1, etc.
xuartctl --server --port=192.168.0.50:7350 --speed=9600

Just as you would see from the local device, this command will create a /dev/pts/<Allocated Number> device that you can use to communicate with the XUART remotely.

Removing the --server flag will connect stdin/stdout to the current terminal rather than spawning a PTY device.

FAQ

Why are the xuarts implemented in userspace instead of a kernel driver?

On previous FPGA devices we did implement device drivers. This adds magnitudes of difficulty for debugging, and makes a port to a new kernel a much larger task as all of the drivers need to be updated for newer kernel APIs. Even when this is instead presented as a kernel driver, you would lose the ability to use non-standard modes and baud rates. As linux has very limited structure in it's serial drivers for non-standard baudrates and modes, we could only create very convoluted methods for changing these settings. We have done tests with userspace vs kernel drivers and there is only an extremely minimal speed gain in moving to the kernel especially for the amount of maintenance it requires.

How can I lower my latency?

Our --test option will test the latency when you have a loopback (RX to TX) connected. As most serial drivers, by default xuartctl will poll at 100hz.

This test is run on a TS-7550:

# xuartctl --port 0 --test
loopback_test_ok=1
latency_test_tps=90
latency_us=5412.1

The latency is actually slightly better when you connect from another system to create the port or run the test. From an x86 workstation to the same TS-7550:

user $ xuartctl --port 192.168.2.197:7350 --test
loopback_test_ok=1
latency_test_tps=100
latency_us=4913.2

If you don't mind sacrificing some CPU time to lower the latency, you can specify the IRQ. See your board manual for the IRQ number, but for example on the TS-75XX/TS-4500 series it will use IRQ 29.

# xuartctl --server --irq=29
# xuartctl --port 0 --test
loopback_test_ok=1
latency_test_tps=575
latency_us=781.3

And remotely:

user $ xuartctl --port 192.168.2.197:7350 --test
loopback_test_ok=1
latency_test_tps=607
latency_us=736.9

How do I kill the old xuart process controlling one port?

When you run xuartctl on a port that is already running, it will replace that instance without affecting the other configured ports.

xuartctl works in the booted environment, but not in my startup scripts

Make sure the main server is started when you try to start a port server. If you add your scripts in /etc/rc.local or your fastboot linuxrc, you should be fine. However if it doesn't start you can create the main server instance with:

xuartctl --server

When I brought up my first port it appeared as /dev/pts/1, but normally shows up as /dev/pts/0

The psuedo terminal devices will show up in order they were brought up, and other devices can claim them as xuartctl does. Commonly telnet or ssh will use a pts device for their terminal. If you want this to have some consistancy, it is usually best to link the files to the output of xuartctl's ttyname.

See here for more information

How is the exact baud rate calculated?

Externally the xuart will take any realistic (less than 2.3MBaud) integer for a baud rate, however internally there is some math, and for high speeds somewhat significant difference. If you have a high speed custom baud rate here is the calculation to ensure you are expecting a baud rate that the xuart will actually produce:

int scalar = 100000000 / (baudrate * 8) - 1

Use your baudrate to get the scalar, then use integer truncation to calculate the true scalar value, then use the above formula again to solve for baudrate using the scalar integer. The resulting baudrate will be the actual data rate presented by the xuart.

Raw Mode

Raw mode will set the port settings for a TCP server to allow raw communication. The port server will use this mode to abstract the other modes as pty devices, but in raw mode you can communicate with 9n1 or even use 10 bits with no parity.

Note: This interface provides a lower level access to the hardware than is commonly needed for applications. Most development should be done using the pts servers created by xuartctl.

libxuartctl

To simplify access to the raw TCP connection we have created libxuartctl.

This provides a minimalistic C interface. This example shows using libxuartctl with 9n0:

#include <stdio.h>
#include <errno.h>
#include <assert.h>

#include "libxuartctl.h"

#ifndef TEMP_FAILURE_RETRY
# define TEMP_FAILURE_RETRY(expression) \
  (__extension__                                                              \
    ({ long int __result;                                                     \
       do __result = (long int) (expression);                                 \
       while (__result == -1L && errno == EINTR);                             \
       __result; }))
#endif

int main(void) {
	int sk, n, x, loop;
	unsigned short recvbuf[4];
	unsigned short samplebuf[4] = 
		{0x0055,0x00aa,0x0155,0x01aa};
	

	sk = xuart_connect("127.0.0.1:7350", "--mode=9n1,raw --speed=9600");
	assert(sk != -1);

	for (x = 0; x < 4; x++) {
		n = 0;
		xuart_breakwrite(sk, 11, 0);
		xuart_idlewrite(sk, 11, 0);
		while(0 == n) {
			n = TEMP_FAILURE_RETRY(xuart_rawwrite(sk, samplebuf+x,
			  2, 9));
		}
		xuart_breakwrite(sk, 11, 0);
		xuart_breakwrite(sk, 11, 0);
		xuart_idlewrite(sk, 11, 0);
		n = 0;
		while(0 == n) {
			n = TEMP_FAILURE_RETRY(xuart_rawread(sk, recvbuf+x,
			  2, 9));
		}
		xuart_breakwrite(sk, 11, 0);
		xuart_breakwrite(sk, 11, 0);
		xuart_idlewrite(sk, 11, 0);
		xuart_idlewrite(sk, 11, 0);
		n = 0;
		while(0 == n) {
			n = TEMP_FAILURE_RETRY(xuart_breakread(sk, 1024));
		}
		if(n) printf("Received %d breaks\n", n);
		

	}

	for(x = 0; x < 4; x++) {
		if(recvbuf[x] != samplebuf[x]) {
			printf("Failed loopback!\n");
			printf(" got: %04X,%04X,%04X,%04X\n",
			  recvbuf[0], recvbuf[1], recvbuf[2], recvbuf[3]); 
			printf("want: %04X,%04X,%04X,%04X\n",
			  samplebuf[0], samplebuf[1], samplebuf[2],
			  samplebuf[3]); 
			goto FAILED;
		}
	}
	printf("Success!\n");

FAILED:
	close(sk);
	return 0;
}

RX Processing

The receiver uses 4kbytes arranged as a 2048 x 16-bit ring buffer. When a character, break, or period of idle exceeding the set idle threshold is received, an entry is written to the current address in the ring buffer and the current address is incremented. When the address reaches the end of the ring buffer, it rolls over back to 0. The format of each entry into the ring buffer is as follows:

Bit Used for
0-10 RX Data
11-12 RX Type
13-15 UART# character received from

RX Types:

Value Type
00 Character
01 Break
10 Idle
11 Invalid

When RX type is '00', the received character is present in data bits 10-0 left justified. The remaining LSB bits are random and should not be used.

When the RX type is '01' or '10' -- indicating a break condition or an extended period of idle was detected -- the remaining 11 least significant bits represent the length of the condition in bit times. However, if bit 10 is set, the actual 10 bit result should be left shifted by 6. A break of any length will be reported, but an idle RX entry will only be inserted into the ring buffer if it exceeds the programmed idle threshold from above. A break must first exceed the character time before being considered a break and the counter only starts once that happens, i.e. for a 8N1 (8 data bits, 1 start bit, 1 stop bit) 10 should be added to the counted result.

Since an RX type of '11' will never be written, software may choose to write this value to all data bytes as a way to know how much of the RX ring buffer has been updated since the last RX processing event. It is expected that RX processing be not only run in response to an RX interrupt, but also as part of a low speed timer interrupt. By knowing which UARTS are activated and at what baud rates and idle thresholds, worst case ring buffer overflow time can be computed.

TX Opcodes

Each transmitter has a dedicated 256 entry ring buffer in an external memory. Each entry is 16 bits and contains not only bytes to be transmitted, but also opcodes for fixed length break/idle and commands to change UART parameters such as baud rate, idle threshold, and mode. TX memory starts at offset 0x1000 of the external UART memory and each ring buffer is laid out contiguously.

TX Opcode Format
Bits Description
15:14 Reserved (write 0)
13 Sets TX IRQ at start of TX processing this entry
12:11 Opcode type
10:0 Opcode data


Opcode Types
Value Type
00 Character [1]
01 Break[2]
10 Idle [2]
11 Control opcode (updates internal UART regs)
  1. Opcode '00' data bits 10-0 are right justified character data bits. Number of data bits can be 7-10 as configured below.
  2. 2.0 2.1 Opcode '01' and '10' data bits 9-0 represent the number of bit times the chosen line condition is set for (idle or break) until the next character. If bit 10 is set, the 10 bit number is left shifted by 6 to allow for longer periods than could be represented in a 10 bit counter. Two back to back break opcodes will have at least 2 bit times of idle between them.

The 11 bit opcode data field is broken down into a 3 bit sub-op and a 8 bit data payload. The 3 bit sub-op represents the address of the internal register to update and is the most-significant 3 bits of the opcode data (bits 10-8). Since each register is 16 bits and there is only room for 8 bits of data, the upper 8 bits must be latched in a previous control opcode by writing to the sub-op field the reserved address of 7 (111).

Offset Bits Description
0x0 15:0 16 bit counter to generate 8x baud freq
0x1 15:4 Unused
3:2 Sets IRQ after specified idle time
Idle threshold
Value Description
00 1 bit time
01 8 bit times
10 16 bit times
11 32 bit times
1:0
Character size
Value Description
00 7 bits
01 8 bits
10 9 bits
11 10 bits

All traditional UART modes are possible with 7-10 bit character sizes. The UART does not have special provision for automatically appending/checking parity bits or multiple stop bits. Instead these must be handled with software. A single start bit and single stop bit is assumed with each transmit word. Parity and multiple stop bits can be added easily in software with precomputed lookup tables.

Register Map

Note: This interface provides a lower level access to the hardware than is commonly needed for applications. Most development should be done using the pts servers created by xuartctl. This information is provided without support.

The XUARTs are composed of 2 sets of registers. One consists entirely of the TX and RX buffers, and the control registers are used for all other settings. We have a very standard interface written in C that can be included in any project intended to access these registers.

xuartcore.c and xuartcore.h.

TS-XUART Control Registers
Offset Bits Access Description
0x0 15:8 Read Channel 0 RCNT
Write Channel 0 RIRQ
7:0 Read Channel 0 TXPOS
Write Channel 0 TXSTOP
0x2 15:8 Read Channel 1 RCNT
Write Channel 1 RIRQ
7:0 Read Channel 1 TXPOS
Write Channel 1 TXSTOP
0x4 15:8 Read Channel 2 RCNT
Write Channel 2 RIRQ
7:0 Read Channel 2 TXPOS
Write Channel 2 TXSTOP
0x6 15:8 Read Channel 3 RCNT
Write Channel 3 RIRQ
7:0 Read Channel 3 TXPOS
Write Channel 3 TXSTOP
0x8 15:8 Read Channel 4 RCNT
Write Channel 4 RIRQ
7:0 Read Channel 4 TXPOS
Write Channel 4 TXSTOP
0xa 15:8 Read Channel 5 RCNT
Write Channel 5 RIRQ
7:0 Read Channel 5 TXPOS
Write Channel 5 TXSTOP
0xc 15:8 Read Channel 6 RCNT
Write Channel 6 RIRQ
7:0 Read Channel 6 TXPOS
Write Channel 6 TXSTOP
0xe 15:8 Read Channel 7 RCNT
Write Channel 7 RIRQ
7:0 Read Channel 7 TXPOS
Write Channel 7 TXSTOP
0x10 15:8 Read Only RX IRQ (1 - IRQ pending, 0 - IRQ not pending)
7:0 Read Only TX IRQ (1 - IRQ Pending, 0 - IRQ not pending)
0x12 15:8 Read/Write RX enabled (1 - RX is enabled, 0 - RX is disabled)
7:0 Read Only TX Pending ( 1 - TX is running, 0 - TX completed)
0x14 15:0 Read Only Next RX address to be written
0x16 15:0 Read
TX flow control status
Value Description
0 TX idle, CTS input deasserted
1 TX busy, CTS input deasserted
2 TX idle, CTS input asserted
3 TX busy, CTS input asserted
Write
TX Config Values
Value Description
0 TX suspended (TX idle)
1 TX suspended (TX break instead)
2 TX enabled
3 TX enabled with hardware CTS flow control

RCNT signifies the current count of entries written to the RX fifo for the particular UART. This counter will increment every time an entry is written into the RX ring buffer on behalf of this UART and wrap back to 0 on overflow. An interrupt will be flagged on a new received character when RCNT == RIRQ. By this mechanism, protocol request/response latency less than 1 bit time can be achieved by precisely coordinating an interrupt to be generated at end of packet should the packet size be known ahead of time. Due to possible internal synchronization issues, the RCNT cannot be written from the CPU, so even though its reset value is 0, that fact should not be counted on in driver software.

TXPOS is the position in the TX ring buffer of the next character to be sent. When TXPOS == TXSTOP the transmission is complete and the UART will be idle. To transmit characters, breaks, or timed idle periods the UART TX ring should be loaded with data starting at the current TXPOS and TXSTOP should be updated to point one past the last entry. Similar to RCNT, TXPOS is not writable.

The IRQ signal output is set whenever any of the above bits are set. IRQ will continue to be asserted until all set bits are cleared. Writes will have no effect. To deassert TX IRQs, respective TX/RX control channel register must be read. Bit 0 corresponds to UART channel #0.

A TX IRQ is set whenever a TX op is loaded in the TX shift register with the most-significant bit set (bit 15). By specifically picking which TX bytes will generate IRQs, IRQ load can be low while still ensuring the transmitter is kept busy and at 100% utilization with no inter-character idle gaps.

A RX IRQ is set either when RCNT == RIRQ as set in registers 0x0-0xe, or when a per-UART programmable idle threshold is exceeded. This threshold can be set to generate an IRQ after 1, 2, 4, or 8 bit times of idle allowing an IRQ to be generated only after a string of back-to-back characters has been received. Details on how this idle threshold is set is described below. All RX IRQ bits will be cleared on read of RX address (iobase + 0x14).

When RX is first enabled, the first RX entry may be an idle or break. The time as recorded will not have started from the moment the UART was enabled and should probably be thrown away.

The memory registers are stored in a different region than the control, and on many products this is in a memory window. Refer to the specific FPGA implementation for more information.

Memory Map
Offset Description
0x0 RX ring buffer
0x1000 TX ring buffer 0
0x1200 TX ring buffer 1
0x1400 TX ring buffer 2
0x1600 TX ring buffer 3
0x1800 TX ring buffer 4
0x1a00 TX ring buffer 5
0x1c00 TX ring buffer 6
0x1e00 TX ring buffer 7