Reading serial port blocks for an unknown reason

I am trying to connect a contactless smart card reader via UART (usbserial) using the termios framework under Linux. The code works fine on a PC, but when I cross-compile and test it on an ARM9 target, it can open the device and even write a command to the device, but the read command is blocked indefinitely. Here is the code snippet:

int mifare_rdr_init(struct mifare_1K * ptr, char *rdr_devnode) { bzero(ptr, sizeof(struct mifare_1K)); // zero the entire structure // open serial device int fd = open(rdr_devnode, O_RDWR|O_NOCTTY ); if (fd == -1) { perror("Failed to open serial device "); return 1; } ptr->serialfd = fd; // save file descriptor ptr->serialdev.c_iflag = 0; // no i/p flags ptr->serialdev.c_oflag = 0; // o/p flags ptr->serialdev.c_cflag = ( CS8 | CREAD | B38400 ); // 8 bits, receive enable, baud for rdr ptr->serialdev.c_lflag = ( ICANON ); // CANONICAL mode, means read till newline char '\n'. // control chars // commented below line as suggested by AH below, since it not needed in CANONICAL mode // ptr->serialdev.c_cc[VMIN] = 1; // read unblocks only after at least one received char. // flush all i/o garbage data if present tcflush(ptr->serialfd,TCIOFLUSH); int ret = 0; // apply settings ret = tcsetattr(ptr->serialfd,TCSANOW,&ptr->serialdev); if (ret == -1) { perror("tcsetattr() failed "); return 2; } return 0; } int get_mifare_rdr_version(struct mifare_1K *ptr, char *data) { // flush all i/o garbage data if present tcflush(ptr->serialfd,TCIOFLUSH); int chars_written = write(ptr->serialfd,"$1V\n",4); if( chars_written < 0 ) { perror("Failed to write serial device "); return 1; } printf("cmd sent, read version...\n"); // this prints, so I know cmd sent... int chars_read = read(ptr->serialfd,ptr->data_buf,14); if( chars_read < 0 ) { perror("Failed to read serial device "); return 2; } // copy data to user buffer printf("reading done.\n"); // this doesn't print... return 0; } 

The mifare_1K structure contains a file descriptor for the serial device, the termios structure, and the various buffers that I use. The device I mentioned is a usb-to-serial device (module: ftdi_sio). It is configured on 38400 @ 8-N-1 in canonical termios mode.

Canonical mode, because responses from the reader end in "\ n", so it’s better to process it in canonical mode, since it reads the device before receiving "\ n" (if you are mistaken, correct me).

First I call init () fn and then get_rdr_version (). The line "cmd sent, read version ..." is printed, so I know that she can write, but does not print the line "read done." after that.

Another thing is that if I remove the card reader and connect this port to gtkterm (serial port terminal program) on another PC, I don’t get "$ 1V \ n" on that gtkterm ?? !!, Then, after a little RnD, I found out that if I reboot the system the reader is connected to, then I only get this cmd "$ 1V \ n" on another Gtkterm. If I try again without a reboot, this cmd is not displayed on this Gkterm ... key, but havnt still not understood.

Is this something like cmd being written to a device file but not merging with the actual device? Is there a way to chk this?

Any help is deeply appreciated since I am stuck on this fr for a while ... thnks.

UPDATE:

Well, it works for me, changing the code a bit, as shown below.

 // open serial device int fd = open(rdr_devnode, O_RDWR|O_NOCTTY|O_NDELAY ); // O_NDELAY ignores the status of DCD line, all read/write calls after this will be non-blocking fcntl(fd,F_SETFL,0); // restore read/write blocking behavior if (fd == -1) { perror("Failed to open serial device "); return 1; } 

This is a modified section of code that opens a port in my init () function. Two changes:

1) O_NDELAY is added to the flags in the open () call, which ignores the data carrier detection line (DCD) to find out if the other end is connected and ready to communicate or not. This was originally used for modems that I don’t need, in fact, I don’t have it at all, since I use usbserial. But this flag also calls further read () and write () calls as non-blocking. Let me remind you, I thought that they would take care of this by adding CLOCAL to the cflag termios struct, which I tried but did not work.

2) fcntl (fd, F_SETFL, 0) restores the behavior of blocking subsequent calls to read () and write ().

This combo works great for me. The only reason I am not sending this as an answer is because I still do not understand why it worked on a PC without this modification, as it was the same. In fact, I was able to read data from a smart card reader on an ARM9 TARGET using minicom , but not my program. I am going to check the FT232BL docs to see if DCD status is the default.

Anyway, I found this information about the Serial Programming Guide for POSIX operating systems . Explanation to anyone ??? Of course, I will send an answer when I find out.

Greetings :)

+4
source share
2 answers

Only when I encounter the same symptoms on a Raspberry Pi with the USB Telegesis module, I add this as another data point.

In my case, the reason was the missing RTS flag. Telegesis expects CRTSCTS flow control and will not send any data to Raspberry without seeing RTS. The amazing aspects here were that a) the same code works very well on a PC, and b) it worked fine on raspberries the first time Telegiz was connected, but with subsequent openings of / dev / ttyUSB 0, Malina was not visible.

For some reason, it seems that on the ARM the RTS flag is cleared when the device is closed, but not set again, while on x86 / x64 the RTS flag remains set. The fix here is simply to set the RTS flag if it does not already exist - for example,

 #include <sys/ioctl.h> //... int rtscts = 0; if (ioctl (fd, TIOCMGET, &rtscts) != 0) { // handle error } else if (!(rtscts & TIOCM_RTS)) { rtscts |= TIOCM_RTS; if (ioctl (fd, TIOCMSET, &rtscts) != 0) { // handle error } } 

I note that in your case you are not using flow control, so the above can be very inapplicable. It was your question, and mentioning that minicom worked made us find a solution to our problem, however - thanks so much for that!

+3
source

Three things you can check:

The combination of canonical mode / non-canonical mode 1

You mix canonical mode and non-canonical mode material:

 ptr->serialdev.c_lflag = ( ICANON ); // ... ptr->serialdev.c_cc[VMIN] = 1; 

The termios(3) man page says VMIN:

VMIN The minimum number of characters for non- canonical reading.

Thus, your timeout will not work the way you think.

Canonical mode / noncanonical mode mixup 2

In addition, manpage says below:

These character index values ​​are different, except that VTIME, VMIN can have the same value as VEOL, VEOF , respectively. In non-canonical mode, the value of a special character is replaced by a timeout meaning. For an explanation of VMIN and VTIME, see Non-canonical mode description below.

Therefore, please check if the definitions of these constants are different for your two platforms. Perhaps the EOL / EOF logic may be corrupted by incorrect configuration. Both EOL and EOF migh cause a return from read .

Uninitialized c_cc

The code does not properly initialize the c_cc array. You do not read existing settings without providing suitable default values ​​for the values ​​required for canonical modes. The code shown so far does not even clear the values. Therefore, unpredictable values ​​can be used.

+1
source

Source: https://habr.com/ru/post/1387170/


All Articles