Question

I am working on an application that communicates with a USB device using a virtual serial port device driver. We have run into a situation where, if the device is unplugged (or crashes) while the serial port handle is open, the only way to reconnect after the serial port handle is closed is to unplug the device and then plug it back in.

There are potential work-arounds if I can detect the failure quickly enough. The problem is that, under these conditions, the following function calls do not report an error:

In my experience, the only function that returns an error when the device is unplugged is WriteFile. Understandably, I don't really want to write meaningless data just to test if the port connection is still valid.

My question is whether there is some method that I can used to determine whether the port connection is still valid.

In case there is any question about what I am doing, the following code fragment shows what my port polling thread is doing:

// set up the communications timeouts
COMMTIMEOUTS timeouts;
if (!GetCommTimeouts(port_handle, &timeouts))
   throw OsException(my_strings[strid_get_comm_timeouts_failed].c_str());

timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
timeouts.ReadTotalTimeoutConstant = 10;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 10000;
if(!SetCommTimeouts(port_handle, &timeouts))
   throw OsException(my_strings[strid_set_comm_timeouts_failed].c_str());

on_open();

// we need to set a base for the carrier detect signal.  This will be used to determine
// when the signal "changes" while the loop executes
bool carrier_detect_set = false;
uint4 modem_status = 0;

if(!GetCommModemStatus(port_handle, &modem_status))
   throw OsException(my_strings[strid_get_modem_status_failed].c_str());
if(modem_status & MS_RLSD_ON)
   carrier_detect_set = true;

 // we are now ready to enter the main service loop for this thread.
 OVERLAPPED io_control;
 memset(&io_control, 0, sizeof(io_control));
 while(!should_close)
 {
    // we need to check to see if any comm errors have occurred
    uint4 comm_errors = 0;
    if(!ClearCommError(port_handle, &comm_errors, 0))
       throw OsException(my_strings[strid_clear_comm_errors_failed].c_str());
    if(comm_errors != 0)
       on_comm_errors(comm_errors);

    // we also need to determine if the carrier detect line has changed
    modem_status = 0;
    if(!GetCommModemStatus(port_handle, &modem_status))
       throw OsException(my_strings[strid_get_modem_status_failed].c_str());
    if(carrier_detect_set && (modem_status & MS_RLSD_ON) == 0)
       on_carrier_detect_change(false);
    else if(!carrier_detect_set && (modem_status & MS_RLSD_ON) != 0)
       on_carrier_detect_change(true);

    // we will now execute any command that might be waiting
    command_handle command;
    commands_protector.lock();
    while(!commands.empty())
    {
       command = commands.front();
       commands.pop_front();
       commands_protector.unlock();
       command->execute(this, port_handle, false);
       commands_protector.lock();
    }
    commands_protector.unlock();

    // now we will try to write anything that is pending in the write queue
    fill_write_buffer(tx_queue);
    while(!tx_queue.empty() && !should_close)
    {
       uint4 bytes_avail = tx_queue.copy(tx_buff, sizeof(tx_buff));
       uint4 bytes_written = 0;

       rcd = WriteFile(port_handle, tx_buff, bytes_avail, &bytes_written, &io_control);
       if(!rcd || bytes_written == 0)
          throw Csi::OsException(my_strings[strid_write_failed].c_str());
       if(rcd)
       {
          SetLastError(0);
          if(bytes_written)
          {
             tx_queue.pop(bytes_written);
             on_low_level_write(tx_buff, bytes_written);
          }
          if(bytes_written < bytes_avail)
             throw OsException(my_strings[strid_write_timed_out].c_str());
       }
    }

    // we will now poll to see if there is any data available to be written
    uint4 bytes_read = 0;

    rcd = ReadFile(port_handle, rx_buff, sizeof(rx_buff), &bytes_read, &io_control);
    if(rcd && bytes_read)
       on_low_level_read(rx_buff, bytes_read);
    else if(!rcd)
       throw OsException(my_strings[strid_read_failed].c_str());
 }

I have encountered the same problem when using overlapped I/O as well.

Was it helpful?

Solution

I am able to detect the port failure by adding the following code:

while(!should_close)
{
  // we will attempt to get and then set the comm state each time the loop repeats.
  // This will hopefully allow use to verify that the port is still valid.
  if(tx_queue.empty())
  {
      DCB dcb;
      init_dcb(&dcb);
      if(!GetCommState(port_handle, &dcb))
         throw OsException(my_strings[strid_get_modem_status_failed].c_str());
      if(!SetCommState(port_handle, &dcb))
         throw OsException(my_strings[strid_get_modem_status_failed].c_str());
 }

I am concerned about the overhead of doing this test on each poll loop. I might try combining this with RegisterDeviceNotification as was suggested by MSalters.

Bonus Reading

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top