Serial Port State

A central part of implementing the IOSerialDriverSync interface is managing the serial port's state. The serial port state is a bitfield of 32-bits that is used to report events, such as the arrival of data on the serial port, as well as to save the overall state of the port, such as whether it has been opened by a user space process or not. Although there are methods that are directly involved in manipulating the state bitmask, ultimately every method that the serial port implements from IOSerialDriverSync will need to access the serial port's state, even if for no other reason than to verify that the serial port has been opened before attempting to perform an operation.

The state bits are defined in the header file IOSerialStreamSync.h. The meaning of each bit is described as follows:

  • PD_S_ACQUIRED indicates that the serial port has been opened and is in use by a user space application. This state bit is never set or cleared through the setState() method but rather is set in the acquirePort() method and cleared in the releasePort() method.
  • PD_S_ACTIVE is set immediately following the acquisition of the serial port and is cleared immediately before the serial port is released. This state bit is never set or cleared through the setState() method; instead, the bit is set or cleared through the executeEvent() method, which uses an event type of PD_E_ACTIVE to manipulate this state bit.
  • PD_S_TX_ENABLE and PD_S_RX_ENABLE are set to indicate that the serial port's transmit and receive interfaces are enabled. Most implementations, including the AppleUSBCDCDMM driver, set these bits when the serial port is opened and clear them when the serial port is closed, but otherwise make no other use of these state bits.
  • PD_S_TX_BUSY and PD_S_RX_BUSY are set to indicate that the serial port driver is in the middle of sending data from its transmit buffer to the serial port hardware, or it is in the middle of reading data that has been sent over the serial port hardware into a driver buffer.
  • PD_S_TX_EVENT and PD_S_RX_EVENT are two states that are used internally by the IOSerialBSDClient class to signal the beginning of a write or read operation. Although these state bits are unused by the serial driver, it needs to set the corresponding bit in setState() and allow a client to observe the bit through watchState() to ensure that the IOSerialBSDClient operates correctly.

A number of bits describe the status of the serial driver's transmit and receive buffers. The transmit buffer is used by the serial driver to hold bytes that it has been provided with through the enqueueData() method but that it has yet to send over the serial port hardware. The receive buffer holds bytes that the serial driver has read from the serial port hardware but has yet to pass on through the dequeueData() method. Following are descriptions of the serial driver buffer state bits:

  • PD_S_TXQ_EMPTY and PD_S_RXQ_EMPTY indicate that the transmit buffer or receive buffer is empty and contains no bytes.
  • PD_S_TXQ_LOW_WATER and PD_S_RXQ_LOW_WATER indicate that the number of bytes in the transmit buffer or receive buffer is below a “low water level.” The AppleUSBCDCDMM driver sets the low water level to be one-third of the size of the overall transmit buffer or receive buffer.
  • PD_S_TXQ_HIGH_WATER and PD_S_RXQ_HIGH_WATER indicate that the number of bytes in the transmit buffer or receive buffer is above a “high water level.” The AppleUSBCDCDMM driver sets the high water level to be two-thirds of the size of the overall transmit buffer or receive buffer.
  • PD_S_TXQ_FULL and PD_S_RXQ_FULL indicate that the transmit buffer or receive buffer is completely full and cannot accept any further data.

The header file IORS232SerialStreamSync.h defines status bits for the standard RS-232 signals. For example, definitions are provided for signals such as Clear To Send (PD_RS232_S_CTS) and Data Terminal Ready (PD_RS232_S_DTR). The state of software flow control, which uses the transmission of special XON and XOFF characters, is indicated through state bits PD_RS232_S_TXO and PD_RS232_S_RXO.

A serial port driver maintains a 32-bit integer that holds a bitmask of the state bits that describe the current state of the serial port. A serial driver must implement the following three methods to allow the serial port state to be manipulated: getState(), setState(), and watchState(). As well as being called by the IOSerialBSDClient class when it requires access to the state of the serial port, these three methods are called in the implementation of many other methods in the serial port class.

For example, the serial port method acquirePort(), which is called to open a serial port for exclusive access, and releasePort(), which is called to close the serial port, do so by setting the state bit PD_S_ACQUIRED. A possible implementation of the acquirePort() method is shown in Listing 11-3. This sample uses setState() to set the PD_S_ACQUIRED bit. If the serial port has already been acquired and the caller has requested that the method should block until the serial port becomes free, the implementation calls watchState() to wait until the PD_S_ACQUIRED state bit has been cleared.

Listing 11-3. A Sample Implementation of the acquirePort() Method. The Method Is Assumed to Have Been Called Through an IOCommandGate.

IOReturn       MySerialDriver::acquirePortGated (bool sleep, void* refCon)
{
       UInt32      state;
       IOReturn    rtn;
       
       // If the serial port is already acquired, wait until it is released
       while (m_currentState & PD_S_ACQUIRED)
       {
              // Abort if the caller has requested non-blocking operation
              if (sleep == false)
                     return kIOReturnExclusiveAccess;
              
              // Sleep until the acquired bit becomes clear
              state = 0;
              rtn = watchState(&state, PD_S_ACQUIRED, refCon);
              if (rtn != kIOReturnSuccess)
                     return rtn;
       }
              
       // Set the acquired bit and clear all other state bits
       setState(PD_S_ACQUIRED, 0xFFFFFFFF, refCon);
       
       // Serial port has been acquired, perform further initialization
       ...

       return kIOReturnSuccess;
}

A possible implementation of the releasePort() method, which uses setState() to clear the PD_S_ACQUIRED state is given in Listing 11-4.

Listing 11-4. A Sample Implementation of the releasePort() Method. The Method Is Assumed to Have Been Called Through an IOCommandGate.

IOReturn       MySerialDriver::releasePortGated (void* refCon)
{
       // Return an error if trying to release a port that hasn't been acquired
       if ((m_currentState & PD_S_ACQUIRED) == 0)
              return kIOReturnNotOpen;
       
       // Clear the entire state word, which also deactivates the port
       setState(0, 0xFFFFFFFF, refCon);
       
       return kIOReturnSuccess;
}

Often, the IOSerialBSDClient class, or even the serial port driver itself, needs to block the current thread until a particular state has become active or inactive. The serial driver provides this functionality through a method named watchState(). The events that the caller wishes to observe are described by two parameters. The “mask” parameter contains a bitmask of the state bits that the caller wishes to observe. The “state” parameter describes the corresponding value of each state bit that the caller wishes to observe. For example, if a bit is set in “mask”, but not set in “state”, the caller is interested in that state becoming inactive. If a bit is set in “mask” and also set in “state”, the caller is interested in that state becoming active.

The watchState() method will return as soon as any of the observed state bits match the current state of the serial port. Upon return, the current state of the serial port is returned to the caller through the “state” parameter. If the serial port is closed while a thread is blocked in watchState(), the sleep will be aborted and the method will fail and return an error code to the caller, such as kIOReturnNotOpen. The following code gives an example of how watchState() can be used; this code will block until either the driver's transmit buffer becomes empty (PD_S_TXQ_EMPTY is set) or the hardware finishes a write to the serial port hardware (PD_S_TX_BUSY is clear):

UInt32       state;
IOReturn     rtn;

state = PD_S_TXQ_EMPTY;
rtn = watchState(&state, PD_S_TXQ_EMPTY | PD_S_TX_BUSY, refCon);
if (rtn != kIOReturnSuccess)
       handle error;

The implementation of the watchState() method is closely related to the implementation of the setState() method. As well as setting bits in the serial port state word, the setState() method is also responsible for waking any threads that are waiting for a particular state to be set. In Chapter 7, we introduced condition variables and saw how one thread could sleep on a condition variable and remain blocked until another thread signaled the condition variable to indicate that an event had occurred. This provides a mechanism that a serial port driver can use to suspend a thread in the watchState() method and to signal it from the setState() method when the observed state has changed.

When the setState() method is called, the serial driver updates a variable that maintains the current serial port state, and then signals all threads that are blocked in the watchState() method, allowing them to test whether the state on which they are waiting has become active. As an optimization, rather than waking up threads blocked in watchState() for every change to the serial port state, the AppleUSBCDCDMM driver maintains a union of all state bits that are being waited on across all current calls to watchState(), and will only unblock the threads if the value of a state bit that is being watched has changed.

A sample implementation of the setState() and watchState() methods is provided in Listing 11-5.

Listing 11-5. A Sample Implementation of the setState() and watchState() Methods. The Methods Are Assumed to Have Been Called Through an IOCommandGate.

IOReturn       MySerialDriver::setStateGated(UInt32 state, UInt32 mask, void* refCon)
{
       UInt32   newState;
       UInt32   deltaState;
       
        // Verify that the serial port has been acquired or is being acquired by this call
       if ((m_currentState & PD_S_ACQUIRED) || (state & PD_S_ACQUIRED))
       {
              // Compute the new state
              newState = (m_currentState & ~mask) | (state & mask);
              // Determine the mask of changed state bits
              deltaState = newState ^ m_currentState;
              // Set the new state
              m_currentState = newState;
              
              // If any state that is being observed by a thread in watchState() has changed,
              // wake up all threads asleep on watchState()
              if (deltaState & m_watchStateMask)
              {
                     // Reset watchStateMask; it will be regenerated as each watchStateGated()
                     // sleeps
                     m_watchStateMask = 0;
                     fCommandGate->commandWakeup((void*)&m_currentState);
              }
              
              return kIOReturnSuccess;
              
       }
       
       return kIOReturnNotOpen;
}

IOReturn       MySerialDriver::watchStateGated(UInt32* state, UInt32 mask, void* refCon)
{
       UInt32      watchState;
       bool        autoActiveBit = false;
       IOReturn    ret;
       
       // Abort if the serial port has not been acquired
       if ((m_currentState & PD_S_ACQUIRED) == 0)
              return kIOReturnNotOpen;
       
       watchState = *state;
       // If the caller is not waiting on the acquired or active state, register
       // interest in the active state so that we can abort if the serial port closes.
       if ((mask & (PD_S_ACQUIRED | PD_S_ACTIVE)) == 0)
       {
              watchState &= ~PD_S_ACTIVE;
              mask |= PD_S_ACTIVE;
              autoActiveBit = true;
       }
       
       while (true)
       {
              // Check port state for any bits that match the watchState value
              // NB. the '^ ~' is a XNOR and tests for equality of bits.
              UInt32    matchedStates = (watchState ^ ~m_currentState) & mask;
              if (matchedStates)
              {
                     *state = m_currentState;
                     // Abort if the serial port was closed and the caller didn't watch
                     // PD_S_ACTIVE
                     if (autoActiveBit && (matchedStates & PD_S_ACTIVE))
                            return kIOReturnIOError;
                     else
                            return kIOReturnSuccess;
              }
              
              // Add the bits we are sleeping on to watchStateMask
              m_watchStateMask |= mask;
              // Sleep until the serial port state changes
              ret = fCommandGate->commandSleep((void*)&m_currentState);
              if (ret == THREAD_INTERRUPTED)
                     return kIOReturnAborted;
       }
       
       return kIOReturnSuccess;
}

Note that the implementation of watchState() in Listing 11-5 will make sure that either the PD_S_ACQUIRED or PD_S_ACTIVE bits are being watched, and if not, will add an extra state to the mask to watch for the PD_S_ACTIVE bit becoming clear. This ensures that when the serial port is closed, all threads that are blocked in a call to watchState() will wake up and return to the caller. If the mask for serial port deactivation were not explicitly added, the blocked thread would never wake up, causing the serial port driver to deadlock.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.93.169