Channel Implementation

Describes how to implement DComm to drive a serial port hardware.

A physical channel defines the interface between the logical device and the physical device. The Serial Port Driver physical channel interface is defined by the DComm class.

The DComm class is defined in: ...\e32\include\drivers\comm.h, which is exported to epoc32\include\drivers:

class DComm : public DBase
    {
public:
    virtual TInt Start() =0;
    virtual void Stop(TStopMode aMode) =0;
    virtual void Break(TBool aState) =0;
    virtual void EnableTransmit() =0;
    virtual TUint Signals() const =0;
    virtual void SetSignals(TUint aSetMask,TUint aClearMask) =0;
    virtual TInt ValidateConfig(const TCommConfigV01 &aConfig) const =0;
    virtual void Configure(TCommConfigV01 &aConfig) =0;
    virtual void Caps(TDes8 &aCaps) const =0;
    virtual void CheckConfig(TCommConfigV01& aConfig)=0;
    virtual TInt DisableIrqs()=0;
    virtual void RestoreIrqs(TInt aIrq)=0;
    virtual TDfcQue* DfcQ(TInt aUnit)=0;
    inline TBool PowerGood(); 
    inline void SetCurrent(TInt aCurrent);
    inline void ReceiveIsr(TUint* aChar, TInt aCount, TInt aXonXoff);
    inline TInt TransmitIsr();inline void CheckTxBuffer();
    inline void StateIsr(TUint aSignals);
    inline TBool Transmitting();
public:
    DChannelComm *iLdd;
    TBool iTransmitting;
    };

Note that DComm defines the minimum set of functions to be used by the LDD. A real physical channel object, typically, requires the addition of an interrupt handler, some DFC callback routines (if these are not handled in the LDD itself) and various other state variables.

DCommVariant constructor

Implement the constructor for the channel object. Since there is no unit number argument, the constructor is limited to initialising only those members that are common across all possible channels, with no access to any specific hardware.

DCommVariant destructor

Implement the destructor for the channel object. It should release any hardware and Symbian platform resources that have been allocated to the driver, typically unbinding the UART interrupt ISR, and any private DFCs that are still in use. The destructor merely unbinds the interrupt as no channel specific DFCs are active at destructor invocation.

DoCreate()

Implement the DoCreate() channel initialisation function for the UART driver. In general, defining a DoCreate() function is not mandated by the device driver framework but is useful for drivers that make use of unit numbers. This is called by the factory object’s Create() function after the device object’s allocation has succeeded.

Typical operations performed by this function are to setup any channel specific hardware states and bind the driver’s ISR to the (possibly channel specific) interrupt channel, as well as enabling the UART hardware itself. However, since the Start() function has not yet been called, it should not enable the UART interrupts/poll routines which would actively buffer incoming data; any data I/O requests should be discarded until the driver’s Start() routine has been called.

Start()

Implement the Start() function. This should enable the driver’s receive path.

Received characters are placed in the LDD upstream buffer, either by the interrupt handler routine, or by a polling routine that tests for the existence of received characters (and their associated error state). This is done by calling the LDD’s ReceiveIsr() routine, with a pointer to the buffered characters, a count and a flag indicating whether the XON or XOFF flow control characters have been received.

The function is called by the LDD when the owning thread opens the device channel. It should complete any UART data I/O enabling that has not already been performed in the DoCreate() function, i.e. the enabling of UART receive interrupt processing as a minimum. After this function returns, it is expected that incoming data will be placed in a (local) buffer and passed upstream to the LDD, until Stop() is invoked. Note that since there may have been line activity prior to this call, it may be necessary to reset the UART state, as we do not want to report errors/states that may have occurred before the UART was (logically) enabled.

Stop()

Implement the Stop() function. This should disable the driver’s transmit and receive paths.

Depending on the type of stop requested, i.e. emergency power down, or otherwise, Stop() may not wait for the UART to finish transmitting any characters stored in the transmit FIFO queue. All receive paths are stopped instantly and any characters that may be in the receive FIFO queue are ignored.

The function is called by the LDD when the owning thread closes the device channel. At this point, the UART device should revert to the state in which all data I/O requests are ignored or failed. To save OS resources the natural method to accomplish this is to disable the UART interrupt processing or disable the UART itself. It may also be necessary to cancel any DFCs queued, so that event notifications are not sent to the LDD after it has requested an end to I/O.

DfcQ()

Implement the DfcQ() function. This should return the DFC queue on which the LDD will install its DFCs. Usually the standard low priority kernel DFC queue 0 is returned.

Break()

Implement the Break() function. This starts or ends a BREAK condition on the wire.

This operation is explicitly requested by the owning thread through the LDD. A break state requires the forcing of the line into an abnormal state, which violates standard data format timings (a form of out of band signalling), and is detected by the remote device and is usually used to force it to cycle through its baud rate detection modes. Forcing a break state requires that the PDD explicitly set some state in the UART hardware.

Signals()

Implement the Signals() function. This returns the state of the flow and MODEM control inputs, ie. DCD, DSR, CTS and RI (if implemented on the device itself). This function is called by the LDD to return the state of all the handshaking lines so that it can decide whether further data I/O operations are allowed.

SetSignals()

Implement the SetSignals() function. This controls the state of the flow and MODEM control outputs. It is used by the LDD to control MODEM handshaking and line failure detection, and can be invoked in response to a specific user request to change the handshake signal’s state, or when the LDD itself decides that the output lines states should be changed in response to some transfer state (e.g. requesting no further transmissions if buffer availability runs low).

Caps()

Implement the Caps() function. This returns the driver supported configurations: speed, format, and handshaking.

It is called by the LDD in response to a user request for the device's capabilities. The PDD must fill a TCommCapsV02 object, with the capabilities for that port.

The object is defined in d32comm.h:

class TCommCapsV01
    {
public:
    TUint iRate;
    TUint iDataBits;
    TUint iStopBits;
    TUint iParity;
    TUint iHandshake;
    TUint iSignals;
    TUint iFifo;
    TUint iSIR;
    };

class TCommCapsV02 : public TCommCapsV01
    {
public:
    TUint iNotificationCaps;
    TUint iRoleCaps;
    TUint iFlowControlCaps;
    };

The base object, TCommCapsV01, defines capabilities in terms of:

  • data rate

  • word format (i.e. parity, data bits, stop bits)

  • flow control lines

  • MODEM control lines

  • IrDA support.

Each attribute range is passed as a bitfield of possible settings, all assumed to be orthogonal, i.e. each attribute can assume all the values independently of the other attributes.

The attribute bitfields are defined in d32comm.h.

The iSIR attribute in TCommCapsV01 indicates whether the port can support slow infrared protocol.

The iNotificationCaps attribute in TCommCapsV02 allows the driver to describe its ability to report asynchronous events, if requested to do so by the client thread. The only useful applications seem to be to report data arrival and handshake line status change, although fields exist for other capabilities.

The iRoleCaps attribute in TCommCapsV02 is intended to indicate that the device is capable of acting as a DCE as well as a DTE, i.e. that the port can be configured to act like a MODEM port. This essentially reverses the normal I/O of the status lines, and could add functionality such as ring indication. Normal UART devices will never need to change this field from the default, i.e. DTE device personality.

The iFlowControlCaps field in TCommCapsV02 is unused and should be set to 0.

The iHandshake field in TCommCapsV01 describes the flow control signal support on the device. Input signals have attributes “Obey” and “Fail” e.g. ObeyCts, FailCts. Output signals have only the “Free” attribute, which indicates they can be driven (via a call to Signals()) to any state, independent of the internal state of the UART, i.e. they are entirely under LDD control. The Fail attribute implies that the LDD will deem operations to have failed if an input line thus labelled becomes disasserted. This means that the PDD can report these signal states asynchronously. If, for example, a change in CTS state can generate an interrupt, then the PDD should report it as possessing both Obey and Fail attributes, whereas if the DCD line cannot generate an asynchronous event, it should merely present itself as “Obey”. If the line is unimplemented then neither attribute should be reported.

Configure()

Implement the Configure() function. This configures the UART device according to the configuration data passed into the function.

Configuration data is encapsulated by a TCommConfigV02 object:

class TCommConfigV01
    {
public:
    TBps iRate;
    TDataBits iDataBits;
    TStopBits iStopBits;
    TParity iParity;
    TUint iHandshake;
    TUint iParityError;
    TUint iFifo;
    TInt iSpecialRate;
    TInt iTerminatorCount;
    TText8 iTerminator[KConfigMaxTerminators];
    TText8 iXonChar;
    TText8 iXoffChar;
    TText8 iParityErrorChar;
    TSir iSIREnable;
    TUint iSIRSettings;
    };

class TCommConfigV02: public TCommConfigV01
    {
public:
    TInt iTxShutdownTimeout;
    };

This function is called by the LDD when the physical channel is opened. The default configuration is set by the LDD when the Dcomm object is constructed, but the user can override the configuration with a SetConfig request on the opened channel. The configuration is described by specifying a set of individual capability attributes from the supported ranges returned in the Caps() call.

Note that the iTerminatorCount, the iTerminatorArray and iSpecialRate are currently unused. The iParityErrorChar replaces the character that the UART determined had a parity violation. This can be used to send a prearranged escape character to the upstream LDD.

CheckConfig()

Implement the CheckConfig() function. This is used by autosensing UARTs to pass detected configuration upstream to the LDD. This is called by the LDD when it creates the initial channel to allow the autosensed state to override any default it has set. If the UART device cannot perform autosensing, and most cannot, then the CheckConfig() function should leave the parameter buffer unchanged.

ValidateConfig()

Implement the ValidateConfig() function. This returns true if the specified configuration is supported by the driver. This should never return false if the correct capabilities have been setup, but may be used to check for cases where, for example, a specific set of attributes cannot be applied together. As an example, a data format word size may not be generated for some specific baud rate.

DisableIrqs()

Implement the DisableIrqs() function. This is called by the LDD to disable (all) interrupts during critical sections, for example, during LDD buffer manipulation. It calls the kernel utility to perform the task.

RestoreIrqs()

Implement the RestoreIrqs() function, called by the LDD to enable (all) interrupts after critical sections have completed. Calls the kernel utility to perform the task.

EnableTransmit()

Implement the EnableTransmit() function. This initialises the UART transmit hardware, and the transmit ISR code path. This function can be called from the ISR (on receipt of an unblocking XON character), or to start a transmission of a LDD originated data buffer. It causes the UART to generate Tx service interrupts as it finishes transmitting a FIFOs worth of data, so that the entire buffer (passed by the LDD) can be sent under interrupt control.

Data handling routines

Implement the main data handling routines. These are entirely dependent on the UART hardware interface and cannot therefore be described generally. They are typically driven by the interrupt service routine as asynchronous events from the UART (eg. data received, finished transmission of data, change in handshaking/flow control inputs) occur.

The main ISR is typically driven by an interrupt multiplexed from all the sources in the UART. The ISR, therefore, needs to determine which of the interrupt sources requires attention. Since the most critical source is the data received state, as this requires that the data is processed before following data overwrites earlier data, then this is usually the signal to be checked first. To avoid wasting time, the error state of the data is also checked – data that has bad parity or framing must be noted as such. Typically the receive path will save the currently available data into a temporary buffer, and queue a DFC to process the data further at a later time, when more client thread context is available (though this is a function of the LDD). The receive ISRr just passes the location of the ISR buffer into an LDD function which queues a DFC to process it.

The transmit path, called when the transmit FIFO contents drop below a programmable number, merely requests more data from the LDD. If there is none available it disables the transmit interrupt so as to prevent further requests when there is no data to be sent. Further data to transmit will cause the transmit FIFO empty interrupt to be re-enabled. Hence the start of any transmission is always performed on an explicit start request from the LDD and then continues under PDD interrupt control until the source data is exhausted.

The status notification path is present to inform the upstream LDD of changes in the input status signals (MODEM and flow control status). The UART can generate interrupts when any input handshake line changes state – the ISR merely reads the current state and queues a handler DFC so that the LDD can be informed. The LDD is responsible for determining what status change has occurred and dealing with it.