Skip to content
Advertisement

Serial socket – ignore POLLHUP received in non-canonical mode?

I have a Raspberry Pi connected via UART to a microcontroller. The code on the RPI is trying to read incoming non-canonical UART data, but randomly receives POLLHUP. I have been able to recover by closing and reopening the file, but this is less than ideal.

Is there a way to disable the disconnect detection behavior of termios in Linux? I am not sure why the POLLHUP is being raised in the first place. I suspect that some control characters are still being interpreted despite my call to cfmakeraw(). The cable is unlikely to be the problem as canonical debug output works fine (admittedly over different pins, but same baud and same type of cable).

Sample code, setup:

bool UartSocket::setup()
{
    int fd = ::open("/dev/serial0", O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (fd == 0)
    {
        return false;
    }

    struct termios portSettings;
    ::memset(&portSettings, 0, sizeof(portSettings));
    if (m_rSyscalls.tcgetattr(fd, &portSettings) != 0)
    {
        m_logger.error("tcgetattr() failed, errno = %d.", errno);
        return false;
    }
    m_rSyscalls.cfsetispeed(&portSettings, 115200);
    m_rSyscalls.cfsetospeed(&portSettings, 115200);
    cfmakeraw(&portSettings);

    // Fiddling with more settings out of desperation
    portSettings.c_iflag &= ~IGNBRK; // disable break processing
    portSettings.c_lflag &= ~ICANON;
    portSettings.c_cc[VEOF] = 0;

    if (m_rSyscalls.tcsetattr(fd, TCSANOW, &portSettings) != 0)
    {
        m_logger.error("tcsetattr() failed, errno = %d.", errno);
        return false;
    }

    // Prepare to poll on recv() calls
    m_pollfd.fd = fd;
    m_pollfd.events = POLLIN;

    return true;
}

Sample code, Rx:

ssize_t UartSocket::recv(char* buf, size_t maxRead)
{
    ssize_t readResult = -1;

    int pollResult = ::poll(&m_pollfd, 1, 1000);
    if (pollResult > 0)
    {
        if (m_pollfd.revents & POLLERR)
        {
            int error = 0;
            socklen_t errlen = sizeof(error);
            if (getsockopt(
                        fd,
                        SOL_SOCKET,
                        SO_ERROR,
                        static_cast<void*>(&error),
                        &errlen))
            {
                m_logger.error(
                        "getsockopt failed when trying to diagnose an error.");
            }

            m_logger.error(
                    "Error on uart %s. Error = %d, len = %u.",
                    m_rConfig.getPath().c_str(),
                    error,
                    errlen);
            return -1;
        }

        if (m_pollfd.revents & POLLIN)
        {
            readResult = ::read( //
                    fd,
                    buf,
                    maxRead);
            m_logger.info("readResult = %d.", readResult);
            if (readResult > 0)
            {
                 // Party, we are happy
                 return readResult;
            }
            else if (readResult == 0)
            {
                // empty read..no-op
                m_logger.dump("Got an empty UART read.");
            }
            else
            {
                if (errno == EAGAIN)
                {
                    // No data was available to read; do nothing.
                    readResult = 0;
                    m_logger.dump("Got an empty UART read.");
                }
                else
                {
                    m_logger.error(
                            "Failure reading uart %s, errno = %d.",
                            m_rConfig.getPath().c_str(),
                            errno);
                }
            }
        }

        // We wait for the buffer to empty before handling any hangups
        if ((m_pollfd.revents & POLLHUP) && (readResult == 0))
        {
            m_logger.error("Hangup on uart %s.", m_rConfig.getPath().c_str());
            reopen(); // closes the fd, reopens it and repeats the termios setup
        }
    }
    else if (pollResult == 0)
    {
        // No data was available to read; do nothing.
        readResult = 0;
        m_logger.dump("Got an empty UART poll.");
    }
    else
    {
        m_logger.error("Failure polling uart 0, errno = %d.", errno);
        readResult = -1;
    }
    return readResult;
}

TL;DR: The code above has a branch which handles POLLHUP by closing and reopening the serial device. I am talking to a device that sends raw bytes and would prefer it if termios in Linux does not make the file descriptor unusable in case of POLLHUP. Ideally they should also entirely ignore whatever control character is causing this, if it is a control character. Is there a way to do this?

Advertisement

Answer

The POLLHUP issue was resolved by setting the baud rate correctly.

My original code had a call to cfsetispeed(&portSettings, 115200);. This is wrong, B115200 needs to be passed instead. B115200 is a constant that usually resolves to something unpredictable (example).

I recommend not copying from my code but rather using this example for a basic raw tty setup.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement