Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

reading serial port blocks for unknown reason

I am trying to interface a contact-less smart card reader over UART (usbserial) using termios framework under Linux. The code works fine on the PC, but when I cross-compile and try it out on an ARM9 target, it is able to open the device and even writing the command to the device, but the read command blocks 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 A.H below, since it's 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 the file descriptor for serial device, termios structure, and various buffers I am using. The device as I mentioned is usb-to-serial ( module: ftdi_sio ) device. It's configured at 38400@8-N-1 in Canonical mode of termios.

Canonical mode because the responses from the reader end in '\n', so its better handled in Canonical mode since it reads the device till '\n' is received (pls correct me if I'm wrong).

First I call init() fn and then get_rdr_version(). The string "cmd sent, read version..." is printed so I know its able to write, but does not print string "reading done." after that.

Another thing is that if I remove the card reader and connect that port to gtkterm (serial port terminal program) on another PC, I do not receive the "$1V\n" on that gtkterm ??!! . Then after a little RnD I found out that if I reboot the system on which the reader is connected reader then only I get that cmd "$1V\n" on the other Gtkterm. If I try again without a reboot, that cmd is not seen on that Gkterm...a clue, but havnt figured it out yet.

Is it something like that the cmd is written to the device file, but not drained to the actual device ? Is there any way to chk this ?

Any help is deeply appreciated since I've been stuck on this fr some time now....thnks.

UPDATE :

Ok, I have got it working by modifying the code a little 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 the modified section of the code which open the port in my init() function. Two changes :

1) O_NDELAY is added to the flags in open() call, which ignores Data Carrier Detect (DCD) line to see if the other end is connected and ready for communication or not. This was used originally for MODEMs, which I did not need, in fact, I do not have it at all since I am using usbserial. But this flag also makes further calls to read() and write() as non-blocking. Point to note, I had thought this would be taken care of by adding CLOCAL to cflag of termios struct, which I had tried but did not work.

2) fcntl(fd,F_SETFL,0) restores the blocking behaviour of further read() and write() calls.

This combo is working perfectly for me. The only reason I am not posting this as an answer is that I do not yet understand why it worked on PC without this modification, since it was same hardware. In fact, I was able to read data from the smart card reader ON ARM9 TARGET using minicom , but not my program. I am going to check FT232BL docs to see if what is the status of DCD by default.

Anyway, I found this bit of info on Serial programming Guide for POSIX operating systems . Explanation anyone ??? Of course, I will post the answer when I find it out..

Cheers :)

like image 791
aditya Avatar asked Dec 20 '11 09:12

aditya


2 Answers

Having just encountered the same symptoms on a Raspberry Pi with a Telegesis USB module, I'm adding this as another data point.

In my case the cause turned out to be a missing RTS flag. The Telegesis expects CRTSCTS flow control, and would not send any data to the Raspberry without seeing the RTS. The perplexing aspects here were that a) the same code works just fine on a PC, and b) it worked fine on the Raspberry the first time the Telegesis was plugged in, but on subsequent openings of /dev/ttyUSB0 no data would be seen by the Raspberry.

For some reason, it seems that on the ARM the RTS flag gets cleared on device close, but not set again, whereas on x86/x64 the RTS flag stays set. The fix here is simply to set the RTS flag if it isn't already - e.g.

#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 do note that in your case you don't use flow control, so the above may very well not be applicable. It was your question and the mentioning that minicom worked that led us to discover the solution to our problem however - so thank you for that!

like image 163
johny Avatar answered Sep 22 '22 22:09

johny


Three things you might check:

Canonical mode / non canonical mode mixup 1

You are mixing the canonical mode and the the non-canonical mode stuff:

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

The termios(3) manpage states about VMIN:

VMIN Minimum number of characters for non-canonical read.

So clearly your timeout will not work the way you think.

Canonical mode / non canonical mode mixup 2

Additionally the manpage says further below:

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

So please check if the definitions of these constants are different for your two platforms. It might be, that the EOL/EOF logic might be spoiled by the wrong setting. Both EOL and EOF migh cause a return from read.

Non-initialized c_cc

Your code does not show a proper initialization of the c_cc array. You neither read the existing settings not provide suitable defaults for the values required for canonical modes. The code shown so far does not even clear the values. Therefore unpredictable values might be used.

like image 21
A.H. Avatar answered Sep 24 '22 22:09

A.H.