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.
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:
The following are brief descriptions of the methods defined by the IOSerialDriverSync
interface:
acquirePort()
releasePort()
setState()
getState()
watchState()
executeEvent()
enqueueEvent()
nextEvent()
requestEvent()
enqueueData()
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.
3.147.75.221