The HDLC API for Windows 2000 or later includes an inter-driver interface that gives kernel drivers access to the API services. Any type of driver can access the API: standard kernel, NDIS miniport, NDIS intermediate or other. The driver that uses the API services is called the client driver in this document.
The API driver exports functions used by a client driver to access the API services. The client driver links to the driver import library (MGHDLCD.LIB). This is similar to MGHDLC.LIB for user mode applications.
The driver interface functions mirror the user mode API functions, and are prefixed with "Di". For example, DiMgslReceive is the driver interface version of MgslReceive. The calling conventions and return values are different, but the functionality is equivalent.
The user mode API uses asynchronous notification, where an API function may return before request completion. Request completion is signaled with a WIN32 event. Driver interface functions are synchronous, where all tasks performed by the functions are complete before returning.
Functions that may return ERROR_IO_PENDING in user mode, are implemented as polling functions in kernel mode. For example, DiMgslReceive returns STATUS_SUCCESS if data is available, or STATUS_RETRY otherwise. Each such polling function is matched to a notification callback function in the client driver. For DiMgslReceive, the callback is ReceiveReady, which is called when the API receives data. The ReceiveReady callback typically calls DiMgslReceive repeatedly until all receive data has been processed.
| Polling Function |
Client Driver Callback |
Called When |
| DiMgslGetTraceEvent | TraceReady | Trace event placed in API trace buffers |
| DiMgslMonitorEvents | EventReady | Monitored event has occurred |
| DiMgslReceive | ReceiveReady | Data received |
| DiMgslTransmit | TransmitComplete | Transmit completed |
See also:
Driver Interface Function Reference
Opening the Port
A port handle must be obtained by calling DiMgslOpen before calling the other API functions. The requested port is identified by a NULL terminated string containing the port name. The client driver passes DiMgslOpen a pointer to a DICALLBACKS structure initialized with the address of the client implemented callback functions and context data that is passed to the callback functions by the API driver.
void *Handle;
char DeviceName[] = "MGHDLC1";
DICALLBACKS Callbacks;
CLIENT_DEVICE_EXTENSION *pDX;
memset(&Callbacks, 0, sizeof(Callbacks));
/* callback context is typically a pointer the client device
* instance data (device extension)
*/
Callbacks.Context = pDX;
/* callback functions are implemented in client driver */
Callbacks.TransmitComplete = CbTransmitComplete;
Callbacks.ReceiveReady = CbReceiveReady;
Callbacks.EventReady = CbEventReady;
Callbacks.TraceReady = CbTraceReady;
if (DiMgslOpen(DeviceName, &Handle, &Callbacks)!= STATUS_SUCCESS)
{
/* error */
}
Port access by client drivers is exclusive. If a driver tries to open a port already opened by a driver, the open fails with STATUS_DEVICE_BUSY.
A user mode application is allowed concurrent port access with a client driver. Only trace API calls are allowed by a user mode application when a driver has opened the same port. All other user mode API calls return ERROR_BUSY. The client driver access is not restricted.
The purpose of concurrent port access by a user mode application and a client driver is to allow use of the user mode tracing utility to monitor the client driver activity (including driver interface API calls). It is the responsibility of application and client driver writers to properly manage concurrent port access between user mode applications and drivers.
Port Names and Selection
API ports for single port adapters are named MGHDLCx where x is the adapter number. API ports for multiport adapters are named MGMPxPy where x is the adapter number and y is a port number in the range 1 to the number of ports on the adapter.
API port names are displayed in the control panel HDLC Ports applet for Windows NT 4 and earlier, and by the device properties in the device manager of Windows 2000 or later. Port names may be obtained programmatically by a user mode component calling MgslEnumeratePorts. The component may be a custom control panel applet or device property page that configures the client driver with a user selected API port. This component can save the selected port name to the registry for the client driver to read during initialization.
Port Configuration
Port configuration is handled in the same way as user mode applications. The functions DiMgslGetParams and DiMgslSetParams retrieve and set the current port configuration.
Sending Data
Data is sent using the DiMgslTransmit call. The client driver passes a data buffer and the data size to the API port specified by Handle. The API either accepts the data for transmission or indicates that the buffers are full.
rc = DiMgslTransmit(Handle, Buffer, Size);
switch(rc) {
case STATUS_SUCCESS: /* data accepted for transmission */
break;
case STATUS_DEVICE_BUSY: /* API send buffers are full, data not accepted */
break;
}
When a transmission completes, the API calls the CbTransmitComplete function in the client driver. The callback includes the status of the completion (error or OK). The callback function typically will send more data if available.
void CbTransmitComplete(void *Context, ULONG Status)
{
CLIENT_DEVICE_EXTENSION *pDX = (CLIENT_DEVICE_EXTENSION*)Context;
/* do client device specific synchronization (spinlock) */
/* update statistics based on Status if desired */
UpdateStats(pDX, Status);
/* send more data if available with DiMgslTransmit */
SendMoreData(pDX);
/* release client device synchronization */
}
The client driver is not required to wait for the transmit completion callback for each call to DiMgslTransmit. The client driver may call DiMgslTransmit repeatedly until STATUS_DEVICE_BUSY is returned. The API will buffer as much transmit data as possible. The transmit completion callback will be called after each send frame has completed or all asynchronous send data has been sent.
Receiving Data
Receive data is retrieved from the API buffers with the DiMgslReceive call. This call is typically made in response to the receive ready callback. The callback calls DiMgslReceive repeatedly until STATUS_RETRY is returned, so the client driver can process all available receive data. In the example below the API handle and temporary buffer are located in the client device extension.
void CbReceiveReady(void *Context)
{
CLIENT_DEVICE_EXTENSION *pDX = (CLIENT_DEVICE_EXTENSION*)Context;
UINT Size;
UINT Status;
/* do client device specific synchronization (spinlock) */
Size = sizeof(pDX->Buf);
while(DiMgslReceive(pDX->Handle, pDX->Buf,&Size,&Status)
==STATUS_SUCCESS)
{
/* process received data */
Size = sizeof(pDX->Buf);
}
/* release client device synchronization */
}
Monitoring Serial Signals
Serial signals and other events are monitored with DiMgslMonitorEvents and the EventReady callback function. These functions are similar to the user mode MgslWaitEvent function. Below is an example of client code that monitors the state of DSR, DCD, and CTS. The function ProcessEvents does most of the work of processing status changes, saving the new signal states, and calling DiMgslMonitorEvents to have the API call CbEventReady when the specified events occur.
To start the monitoring process, ProcessEvents should be called
directly by the client driver after opening the port with Events set to
MgslEvent_Dsr + MgslEvent_Dcd + MgslEvent_Cts.
void ProcessEvents(CLIENT_DEVICE_EXTENSION *pDX, ULONG Events)
{
ULONG Mask;
USHORT NewSig;
USHORT OldSig;
CheckAgain:
OldSig = pDX->Signals;
NewSig = (Events & MgslEvent_DsrActive ? SerialSignal_DSR : 0) +
(Events & MgslEvent_DcdActive ? SerialSignal_DCD : 0) +
(Events & MgslEvent_CtsActive ? SerialSignal_CTS : 0);
pDX->Signals = NewSig;
if ((NewSig ^ OldSig) & SerialSignal_DSR)
{
/* DSR has changed */
if (NewSig & SerialSignal_DSR)
{
/* DSR is active */
}
}
/* tell API to report DSR/DCD/CTS change from current state */
Mask = ~Events & MgslEvent_Dsr + MgslEvent_Dcd + MgslEvent_Cts;
if (DiMgslMonitorEvents(pDX->Handle, Mask,&Events)
==STATUS_SUCCESS)
goto CheckAgain;
}
/* Callback from API driver indicating a monitored event has happened.
*/
void CbEventReady(void *Context, ULONG Events)
{
CLIENT_DEVICE_EXTENSION *pDX = (CLIENT_DEVICE_EXTENSION*)Context;
/* client specific synchronization */
ProcessEvents(Adapter, Events);
/* release client specific synchronization */
}
Closing the Port
The client driver must call DiMgslClose when unloading or when the API port is no longer needed. The API driver has no way of detecting when a client driver unloads and cannot release a port unless DiMgslClose is called. If a driver unloads without calling DiMgslClose, then that port will be unavailable (BUSY) for use until the system reboots.
| Previous | Contents | Next |