Implementing the IOSerialDriverSync Class

Serial drivers on Mac OS X must be derived from the IOSerialDriverSync class. The IOSerialDriverSync class is a pure abstract class that provides an interface that must be implemented by the serial driver. The methods that must be implemented by a serial driver are given in Listing 11-2.

Listing 11-2. The Interface of IOSerialDriverSync, Which Declares the Methods That Must Be Implemented by a Serial Port Driver

class IOSerialDriverSync : public IOService
{
       OSDeclareAbstractStructors(IOSerialDriverSync);

public:
       virtual IOReturn  acquirePort(bool sleep, void *refCon) = 0;

       virtual IOReturn  releasePort(void *refCon) = 0;

       virtual IOReturn  setState(UInt32 state, UInt32 mask, void *refCon) = 0;

       virtual UInt32    getState(void *refCon) = 0;

       virtual IOReturn  watchState(UInt32 *state, UInt32 mask, void *refCon) = 0;

       virtual UInt32    nextEvent(void *refCon) = 0;

       virtual IOReturn  executeEvent(UInt32 event, UInt32 data, void *refCon) = 0;

       virtual IOReturn  requestEvent(UInt32 event, UInt32 *data, void *refCon) = 0;

       virtual IOReturn  enqueueEvent(UInt32 event, UInt32 data,
                                   bool sleep, void *refCon) = 0;

       virtual IOReturn  dequeueEvent(UInt32 *event, UInt32 *data,
                                   bool sleep, void *refCon) = 0;

       virtual IOReturn  enqueueData(UInt8 *buffer, UInt32 size, UInt32 *count,
                                   bool sleep, void *refCon) = 0;

       virtual IOReturn  dequeueData(UInt8 *buffer, UInt32 size, UInt32 *count,
                                   UInt32 min, void *refCon) = 0;
};

You will notice that each method is provided with a parameter named “refCon”. The refCon value can be used by a serial port driver to identify which serial port the method is operating on. The refCon value is actually specified by the serial port driver itself, and is passed to the IOSerialStramSync object at instantiation. In return, the IOSerialStreamSync class passes this refCon value back to the driver whenever it calls a method from the driver's class. In most cases, the refCon value is not needed, since any data that the serial port driver needs can be added as instance variables to the driver class. However, in the case of a serial port driver that manages hardware with multiple serial ports, such as a USB adapter with a COM1 and a COM2 port, the driver would need some way to identify which port is being referred to in a method call. To do this, the serial driver would create two instances of the IOSerialStreamSync class, one for each of its hardware ports, and provide a unique refCon value for each port.

The interface may appear to be daunting, but the methods can be broken into three categories:

  • Methods that adjust the serial port's status and watch for changes in the serial port's state
  • Methods that get or set properties of the serial port
  • Methods that read and write data over the serial port

The following are brief descriptions of the methods defined by the IOSerialDriverSync interface:

  • Opening and closing the serial port:
    acquirePort()
    releasePort()
  • Managing a bitmask that represents the state of the serial port, and blocking the calling thread until a particular condition occurs. State bits describe such conditions as whether the serial port has been opened, whether data has been received over the serial port and is available for reading, and whether the serial port can accept bytes for writing:
    setState()
    getState()
    watchState()
  • Setting properties of the serial port:
    executeEvent()
    enqueueEvent()
  • Getting properties of the serial port:
    nextEvent()
    requestEvent()
  • Writing data over the serial port:
    enqueueData()
  • Reading data that has been received from the serial port:
    dequeueData()

One of the complexities in implementing the IOSerialDriverSync interface is that it requires careful synchronization. The interface methods may be called from multiple threads at any time, meaning that the implementation needs to make sure that each method is correctly synchronized to prevent such situations as the dequeueData() method returning data once the serial port has been closed. To further complicate matters, several methods may block the calling thread until a particular event occurs; this is particularly true of the watchState() method, which doesn't return until the serial port has entered a requested state. Both of these synchronization problems can be solved by using a mutex lock and a condition variable to signal changes in the serial port's state.

In the case of the AppleUSBCDCDMM driver, there is an even greater synchronization problem; the methods called through the IOSerialDriverSync interface need to be coordinated with callbacks that fire upon the completion of an asynchronous transfer over USB. This is necessary to prevent the completion callback for a USB write from placing data into a buffer at the same time that the dequeueData() method attempts to read out of that same buffer.

Although this description paints the picture of a very difficult synchronization problem, the I/O Kit design offers a surprisingly elegant solution. All USB completion callbacks are run on the USB work loop, so to synchronize serial port methods with USB code, the AppleUSBCDCDMM driver ensures that all serial port methods are handled on the USB work loop. This is performed as follows:

bool AppleUSBCDCDMM::start(IOService *provider)
{
       …
       // Get the USB work loop (superclass will use the provider class' work loop)
       fWorkLoop = getWorkLoop();
       …
       // Create a command gate and install it on the USB work loop
       fCommandGate = IOCommandGate::commandGate(this);
       fWorkLoop->addEventSource(fCommandGate);
       …
}

For each serial port method, the AppleUSBCDCDMM driver calls each method through the command gate, ensuring that it is synchronized to the USB work loop. This is shown below for the implementation of releasePort():

IOReturn AppleUSBCDCDMM::releasePort(void *refCon)
{
       IOReturn       ret = kIOReturnSuccess;
       
       // Call the static method releasePortAction() on the work loop, which requires no
       // parameters
       ret = fCommandGate->runAction(releasePortAction);

       return ret;
}

IOReturn AppleUSBCDCDMM::releasePortAction(OSObject *owner, void *, void *, void *, void *)
{
       // Call through to the method releasePortGated()
       return ((AppleUSBCDCDMM*)owner)->releasePortGated();
}

IOReturn AppleUSBCDCDMM::releasePortGated()
{
       …
       // Implementation of releasePort
       …
}

The IOCommandGate also provides an object that a thread can sleep on and can be used to signal sleeping threads when an event occurs. As we will see, this provides a convenient means for implementing the serial port watchState() method.

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

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