MMPM/2 Device Driver Reference:Audio Physical Device Driver Template
Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation
- 1 Audio Physical Device Driver Template
- 1.1 PDD Architecture
- 1.2 Source Code for the Object-oriented Audio Device Driver
- 1.3 Source Code for AD1848 PDD
- 1.4 Data Transfers
- 1.5 Stream Descriptions
- 1.6 Roadmaps
Audio Physical Device Driver Template
This DDK provides two working samples for writing your own audio physical device drivers for the MMPM/2 environment:
- For wave audio/MIDI, the Turtle Beach "Tropez Plus" device driver. See the document "Object-oriented OS/2 Audio Device Driver Sample" which resides on the DDK Web page for details.
- For wave audio, the IBM-written AD1848 Business Audio device driver.
These samples include all the code required for communication with MMPM/2 - including establishing communication with MMPM/2, controlling specific hardware devices, and establishing inter-device driver (IDC) communications with the MMPM/2 stream handlers and IOCtl-based communications with the MMPM/2 amplifier-mixer (ampmix) and media control drivers (MCDs).
Note: For further information on when to use a physical device driver (PDD) and how it operates, see the online OS/2 Physical Device Driver Reference provided in this package. This reference describes the types of physical device drivers, their interfaces, and available system services.
The MMPM/2 audio PDD is a standard OS/2 PDD. Audio PDDs have two entry points for communication with the system. The OS/2 device driver standard strategy entry point is used for open/close and setup operations-received from application-level components. Data movement is done via Ring-0 interdevice driver (IDC) interface. These interfaces provide the foundation for sharing of the audio device by MMPM/2 applications and media drivers.
The audio IOCtls are standard OS/2 IOCtls. Definitions follow in this chapter. Media drivers running at Ring 3 use the IOCtl interface to set up the audio device sample rate, for access, to change volume controls, and so on. The IOCtl interface is not meant to be used to send audio data to the device driver.
The IDC interface is standard 16:16 to 16:16 for procedure calls, initiated by the OS/2 AttachDD DevHlp function. Stream handler device drivers and audio physical device drivers use the IDC interface to communicate with one another to coordinate their efforts to stream data to and from the audio device.
The following graphic shows the audio physical device driver architecture.
The IDC Interface
Two entry points are provided for the two device drivers participating in the IDC interface, DDCMDEntryPoint and SHDEntryPoint. DDCMDEntryPoint allows the stream handler device driver to call to the PDD, and SHDEntryPoint allows the audio PDD to call to the stream handler.
Note that streaming data buffer pointers are passed by the Sync/Stream Manager to the audio stream handler by means of Sync/Stream Manager Helper (SMH) calls. Then the audio stream handler passes pointers to the PDD using device driver command (DDCMD) messages.
The following graphic shows the audio physical device driver modules.
Communication to the PDD
DDCMD Messages are defined from the DDCMDEntryPoint. Each message represents a specific request by the stream handler for the audio PDD to perform an action. Read and write messages allow the stream handler to get and receive data directly from the PDD without any intervention required by the application. The application neither has to send data through an IOCtl nor allocate and lock memory to perform data transfers.
Communication to the Stream Handler
The SHDEntryPoint contains the following two messages. These messages are located in the SHDD.H file of the \MMOS2\MMTOOLKT\H subdirectory. SHDD.H contains the data structures, their type definitions, and #define statements for certain values. Note that the messages pass pointers to packets of data, to allow maximum flexibility for the future.
SHD_REPORT_INT The PDD uses this message when it needs data at interrupt time. For example, it uses this message to tell the stream handler it has used up all the data and needs more.
When the stream handler gets the call, it knows the PDD is passing back a buffer that it might already have consumed. So the stream handler returns on that call, giving the PDD a fresh buffer to consume.
SHD_REPORT_EVENT The stream handler uses this message to keep in sync with PDD activities. For example, the stream handler can request the PDD to report back every tenth of a second that data is played. The stream handler has all the logic to handle these events. The PDD examines the request, and during its checks when it realizes a tenth of a second has been played in data, the PDD calls SHD_REPORT_EVENT. The stream handler can do what it wants at this point, and the PDD returns.
The PDD keeps track of the processes. In other words, only the PDD knows how much data, to the millisecond, has been played out to the device. The stream handler can approximate the data played, using calculations based on how much data has gone by. But the stream handler cannot calculate the data played to the millisecond, or even to the fraction of a millisecond, the way the PDD can.
Stream Handler Values
There are certain values that the stream handler is expecting. For example , when the stream handler requests a stop or a pause on a DDCMD_CONTROL message, the pointer that comes back to the stream handler is a pointer to the cumulative time that the PDD has recorded in real time. So whenever the stream handler requests the device to stop, the PDD honors that request and informs the stream handler the real time that the PDD stopped within the stream.
Another value the stream handler looks for is returned on DDCMD_STATUS. This is also a pointer to the cumulative time from the PDD, with respect to when that stream was first started at the stream handler's request.
The stream handler passes a pointer to the PDD on DDCMD_STATUS. This points to a value used by the PDD for setting the referenced time of the PDD. It is not always correct for the PDD to start its time at 0 every time the stream handler does a start, because the stream handler might have performed a seek in the stream. The PDD might have played a minute of data and then performed a backwards seek to the 30-second mark in the data. If a start is issued, the PDD should start from the 30-second mark in that stream.
DDCMD_CONTROL has an important NOTIFY subfunction, which is used for cuepoint or event detection. The stream handler supports events in cuepoints-an application request to be notified when a particular location in the file is reached or a specific time period has elapsed. The stream handler uses two methods for detecting how much time has elapsed:
- Using DDCMD_CONTROL NOTIFY, the stream handler requests to be notified by the PDD at a particular time and passes a pointer to the cue time.
- The stream handler determines the time internally. This method is not as precise as the first method, because only the PDD knows the real time.
For example, suppose the stream handler does a DDCMD_CONTROL NOTIFY at one minute. If the PDD supports precise event detection, it must accept this request and put it into a queue somewhere, preferably a linked list. This linked list will have the time of one minute so that during the streaming process, the PDD occasionally checks to see whether it is at the one minute mark. When this event occurs, the PDD calls back on an SHD_REPORT_EVENT. Then you can free up the event detection linked list node.
Keep in mind that the PDD should have the capability to queue these requests because there might be additional requests. For example, an application might request to be notified at the one-minute mark, next at a minute and a half, and possibly every five minutes.
If the PDD does not support event detection, then when it gets called on a DDCMD_CONTROL NOTIFY, it responds with an ERROR_INVALID_REQUEST. This response tells the stream handler that it must do the event-detection itself.
Source Code for the Object-oriented Audio Device Driver
Source code for the object-oriented audio device driver, TROPEZ.SYS, which supports the Turtle Beach "Tropez Plus" audio card can be had from the IBM Developer Connection Device Driver Kit for OS/2.
Source files include documentation headers, which provide detailed descriptions of the programming concepts and routines incorporated within the module.
Source Code for AD1848 PDD
Source code for the AD1848 device driver is located in the \MMOS2\MMTOOLKT\SAMPLES\AD1848 subdirectory. Source files include documentation headers, which provide detailed descriptions of the programming concepts and routines incorporated within the module.
|AD1848.C||This file contains functions that interface with the Analog Device stereo codec AD1848.|
|AUDCONT.C||This file contains functions that support audio control commands from the DosDevIoctl interface to the device driver.|
|DMA.C||This file contains routines that support DMA services.|
|HEADER.C||This file contains device driver header information.|
|MEM.C||This file contains functions related to dynamic memory allocation.|
|PARAM.C||This file contains routines that parse parameters from the device assignment statement in the config.sys file.|
|PRINTF.C||This file contains routines to implement C printf functions for writing messages to console during initialization and to COM: port during execution for debugging. COM: support is ifdefed out of driver for production builds.|
|STRATEGY.C||This file contain the routine that services the device driver strategy entry point and routines that support the vdd interface.|
|STRING.C||This file contains miscellaneous string utility functions.|
|STRMCONT.C||This file contains functions that service requests from the audio stream handler and report events to the audio stream handler.|
|STRMHELP.C||This file contains routines that support stream related services.|
|TIMER.C||This file contains miscellaneous time related functions.|
|TRACE.C||This file contains routines that support the data logging facility used for device driver debugging and performance analysis.|
|AUDCONT.H||This file contains prototypes for public functions originating in AUDCONT.C|
|DEVHELP.H||This file contains prototypes for public functions in DEVHLP.ASM.|
|DEVICE.H||This file contains definitions for shared constants and prototypes for public functions that originate in DEVICE.C.|
|DMA.H||This file contains prototypes for public functions and definitions for shared constants originating in DMA.C.|
|ECHO.H||This file contains prototypes for public functions originating in ECHO|
|END.H||This file contains prototypes for public functions in END.C.|
|DEVHLP.ASM||This file contains routines that support device driver helper services from the operating system.|
|ENTRY.ASM||This file contains functions that support all the entry points into the driver.|
|UTIL.ASM||This file contains miscellaneous utility functions for io port data transfer, memory data transfer, and address manipulation.|
Following are the two methods of transferring data:
Direct Memory Access (DMA) The most efficient way to transfer data from the host system to the audio adapter's memory is through direct memory access (DMA). This is a technique for moving data directly between main storage without requiring processing of the data by the CPU. To accomplish this, we create a DMA buffer within the device driver to transfer the data. As the device driver receives the stream buffer data, it copies the stream buffer data into the DMA buffer at interrupt time. When the DMA buffer is full, it can be sent directly to the audio device. A preferable (but unproven) method would be to DMA directly from the stream buffer. Currently, however, a copy operation (which requires time and CPU utilization) is required to get data into the AUDIODD DMA buffer.
To perform direct memory access, you are essentially programming the DMA microcontroller on the system board. For each direct memory access, you can DMA transfer data up to the limit of the DMA channel that the audio adapter is using:
- - 64KB for the 8-bit DMA channels 0, 1, and 3
- - 128KB for the 16-bit DMA channels 5, 6, and 7
Note that this DMA transfer must always be from the same buffer pointer. Unless you decide to change the buffer pointer, you would have to reprogram the microcontroller from the beginning.
Therefore, if you want to DMA directly from the stream buffer, your device driver would always require a different pointer which means reprogramming the DMA controller at each interrupt. So, each time you get a new buffer from the stream handler, you would have to look at the pointer, reprogram the DMA controller, and then start the DMA transfer. At this stage, most developers state that there is not enough time to reprogram the DMA controller during interrupt time. Therefore, they are creating one DMA buffer when the device driver is initialized at system startup. The developer programs the DMA controller at that point, informs the controller that this is the pointer, and tells it how much data the device driver is going to transfer. The value is set for the lifetime of the driver.
Dual DMA Buffers
MMPM/2's stream handler implementation uses two DMA buffers or dual DMA buffersto ensure a smooth flow of audio data.
In this implementation, the amplifier-mixer (ampmix) device issues a request through the OS/2 kernel to the audio PDD for it to prepare to process audio data. The audio PDD informs the stream handler that it needs the audio data, and the stream handler informs the file system PDD to fill two 4KB DMA buffers with the appropriate audio data.
When the audio PDD processes the first 4KB buffer, it issues a hardware interrupt that signals the file system PDD to replace the first 4KB buffer with the next 4KB of audio data. Meanwhile, the audio PDD is processing the second 4KB buffer. When the audio PDD processes the second 4KB buffer, it issues a hardware interrupt again and the process is repeated.
With this process, you only have to track two DMA buffers and the stream handler ensures a smooth flow of audio data to the audio PDD. The following illustration provides an overview of this process.
1st 4KB DMA 2nd 4KB DMA +---------------------------+ +--- | | | | | | | | +---------------------------+ | | +----------------------+ +---------------The audio PDD | | issues hardware | +-----------------+ | interrupts when it | | Stream Handler | | finishes processing | +-----------------+ | each 4KB DMA block. | | | | | | | | | | | | | | The stream handler manages | +-------+ +-------+ | the process of the audio | |Audio | |File | | PDD processing 4KB DMA | |PDD | |System | | blocks and the file system | | | |PDD | | PDD replacing the block as | +-------+ +-------+ | it is processed. +----------------------+
Programmed I/O Programmed I/O involves copying the stream buffer's data directly to the adapter's buffer.
Each physical device driver supports the following:
Multiple Opens A PDD must be able to handle multiple open operations. In other words, the OS/2 operating system should be able to open your device driver as many times as necessary. However, resource should only be allocated at the audio initialization stage (IOCtl stage).
Multiple Streams A PDD must be able to handle multiple streams. For example, there might be ten multimedia programs on the desktop that have the device open. However, only one program can own the device. For that one program, a stream is created and resources are used. However, if that one program gets put to sleep or the audio is paused, a second program can take its place and use that resource. The Media Device Manager will save pointers to the stream instance that is currently paused, point to the next program, and allow the program to use the resource. At this point, there will be two streams created. If the second program gets initialized, and perhaps paused, the third program will be initialized, and so on.
Many streams can be created, however, the program that has focus is the program using the adapter. In addition, a device driver might have ten streams allocated but only one stream will be active at a given time if the hardware supports only one stream. Some hardware can support more than one active stream at a time. For example, the ACPA card can play two mono streams at the same time but only one stereo.
Multiple Streams Implementation A stream table is a collection of streams with each stream having different data-similar to a data instance for DLLs. For example, one stream might be set up for recording using specific data rates and buffer pointers, while another stream might be set up for playback of a different data rate and buffer pointers. The two streams are unaffected by each other. The hardware will react to whichever stream is active.
A stream table contains the key indexes to determine which stream to make active. The key indexes are hStream and ulSysFileNum. The hStream comes from the stream handler during stream registration while the ulSysFileNum comes from both the DDCMD_REGISTER message and the AUDIO_INIT IOCtl. For example, in the following table, stream #1 has a system file number of 6B and stream #2 has a system file number of 7C. When the AUDIO_INIT IOCtl comes in to start the second instance, the IOCtl requests that you start the stream with the matching ulSysFileNum. Next, across the inter-device driver communications interface, the stream handler will tell you to start stream #2. To start stream #2, search the stream table for a match with system file number #7C. If you find a match, make the stream active. If you do not find a match, the operation fails.
IOCtl ulSysFileNum | | hstream sysfile# state data pData +--------------------------------------+ IDC | 1 | 6B | | | | hStream |--------|--------|------|-----|-------| --------- | 2 | 7C | | | | |--------|--------|------|-----|-------| | 3 | 8D | | | | +--------------------------------------+
The following figures illustrate the routines and IOCtls included in the audio physical device driver files.
STARTUP.ASM File +---------------------+ OS/2 IOCtls ----------> | AUDIODD.C | | | Stream Handler IDC ---> | MMDDCMDS.C | | | VDD IDC --------------> | AUDIOVDM.C | | | VDD <---------------- | CallVDD() | +---------------------+
- AUDIODD.C File
Strategy.C() | |-> IOCTL_Init() |-> IOCTL_Invalid() |-> IOCTL_Read() |-> IOCTL_NondestructiveRead() |-> IOCTL_ReadStatus() |-> IOCTL_FlushInput() |-> IOCTL_Write() |-> IOCTL_WriteStatus() |-> IOCTL_FlushOutput() |-> IOCTL_Input() |-> IOCTL_Output() |-> IOCTL_Open() |-> IOCTL_Close() \-> IOCTL_GenIOCTL() | |-> Audio_IOCTL_Init() |-> Audio_IOCTL_Status() |-> Audio_IOCTL_Control() |-> Audio_IOCTL_Buffer() |-> Audio_IOCTL_Load() |-> Audio_IOCTL_Wait() \-> Audio_IOCTL_Hpi() InitPos() InitStreams() AllocDMABuffer() ParseArgs()
- MMDDCMDS.C File
DDCMDInternalEntryPoint() | |- DDCMDSetup() |- DDCMDRead() |- DDCMDWrite() |- DDCMDStatus() |- DDCMDControl() |- DDCMDRegister() +- DDCMDDeRegister() GetStreamEntry()
- AUDIOVDM.C File
PDDInternalEntryPoint() VDMInterruptTime() | +- CallVDD() VDMClose() | +- CallVDD()
- AUDINTR.C File
InterruptHandler() | |- VDMInterruptTime() |- WriteDatatoCard() |- SHDEntryPoint() call back to stream handler +- EOI()
- CDEVHLP.ASM File
DevHlp_AllocGDTSelector() DevHlp_AllocPhys() DevHlp_PhysToGDTSelector() DevHlp_PhysToVirt() DevHlp_VirtToLin() DevHlp_ABIOSCall() DevHlp_GETLIDEntry() DevHlp_FreeLIDEntry() DevHlp_RegisterPDD() DevHlp_AttachDD()