FASTIO with WFASTIO$ and support .DLL

From EDM2
Jump to: navigation, search

DISCLAIMER: This sample code is for your information only. Do not attempt to use this code on your equipment without first inspecting the source code and making sure it does what you want. This code is supplied as is without warranty or liability of any kind.


I needed a fast way of being able to do fastio to I/O ports, to access a data acquisition card. I saw the article from EDM/2 detailing the fastio driver. This seemed to be the ticket. But, I needed to change the functionality of the driver to meet the specific needs of my D/A card. I did not have cl or MASM and could not get them because they are not available any more. I wanted to be able to use what I had (WATCOM) to be able to make the changes to the driver. The first step was then to port the fastio driver to WATCOM. That is what I did.

With this driver you can access any I/O port you want very quickly. It does so buy calling a r3->r0 call gate which is returned to the application by the device driver. The call gate goes directly to the ring 0 code and executes the in/out instructions; therefore there is no need for a ring 2 IOPL segment. The driver call gate is faster than the standard IOPL r3->r2 call gate. For one there is no stack to copy the iolib passes data in registers. For more details see Holger's Article from EDM which explains the driver (one page back).

Why wouldn't the code work as it was? Well there are two reasons.

1) MASM and WASM are not exactly the same, parts of the assembler source were required to be changed so that they would be compatible with WATCOM.

2) The default calling conventions for WATCOM are different than those for MICORSOFT so the header detailing the prototyping was changed to make WATCOM adhere to C calling conventions. See IOLIB.H for more details on the syntax of the pragma statements.

The code should be complete. What is included are the files for the device driver itself, actually modified from the hello world files, the proper ASM files with all the changes needed for WASM, and an example which uses the driver and reads from the VGA registers on your video card.

Be sure your compile using STACK CALLING CONVENTIONS (page 9 of compiler options) or the example will not work. If you need to change this option you should get a unresolved external error when compiling the code.



Hello, and welcome. Before using this software there are a few things you should understand. This software should be used to access those devices which you would like to use but do not need to write a device driver for i.e. things like Data Acquisition cards. Be forewarned, that the I/O method used in this package does not have any checks. Having no restriction means that you can write to any port you want including video cards, sound cards etc. Wherever possible you should try to use the OS/2 system API calls to access the above device's. I am not responsible for damage or crashes to the users system as a result of these actions.

This package requires you to first load the WFASTIO$ device driver. For best results, put iolibdll.dll in C:\os2\dll. This way you only need one copy of it. Then link in the lib, and include the header with your application and you can call any of the functions in the dll to access your device.

If you have any questions or suggestions for this package, please feel free to e-mail me As far as compilers to use it with all should work, and I myself have used it with both Watcom and VAC++.

Get WIOLIBDLL.DLL - the supoort .DLL

/DEV/FASTIO$ - the Final Way

Okay. We just managed to get a transforming (32->16bit) call gate, that just happens to point to the wrong address. It was a matter of seconds to find the address of the corresponding GDT entry, and redirect it to the expected position. A kernel debugger is really a neat tool for the hacker. It worked!

At this point, calling the DevHlp_DynamicAPI function becomes useless, and will just occupy a later unusable entry point in the kernel. A quick look into the list of device helper functions offers the function DevHlp_AllocGDTSelector. We acquire a default GDT selector for exclusive use by the driver, and "adjust" it to form a 32->16 bit R3->R0 call gate into the I/O routine section of the driver.

Have a look at the code fragment in the FASTIO$ driver (figure 4) which does it all.

_acquire_gdt    proc    far

       mov     ax, word ptr [_io_gdt32]        ; get selector
       or      ax,ax
       jnz     aexit                           ; if we didn't have one
                                               ; make one

       xor     ax, ax
       mov     word ptr [_io_gdt32], ax        ; clear gdt save
       mov     word ptr [gdthelper], ax        ; helper

       push    ds
       pop     es                              ; ES:DI = addr of
       mov     di, offset _io_gdt32            ; _io_gdt32
       mov     cx, 2                           ; two selectors
       mov     dl, DevHlp_AllocGDTSelector     ; get GDT selectors
       call    [_Device_Help]
       jc      aexit                           ; exit if failed

       sgdt    qword ptr [gdtsave]             ; access the GDT ptr
       mov     ebx, dword ptr [gdtsave+2]      ; get lin addr of GDT
       movzx   eax, word ptr [_io_gdt32]       ; build offset into table
       and     eax, 0fffffff8h                 ; mask away DPL
       add     ebx, eax                        ; build address in EBX

       mov     ax, word ptr [gdthelper]        ; selector to map GDT at
       mov     ecx, 08h                        ; a single entry (8 bytes)
       mov     dl, DevHlp_LinToGDTSelector
       call    [_Device_Help]
       jc      aexit0                          ; if failed exit
       mov     ax, word ptr [gdthelper]
       mov     es, ax                          ; build address to GDT
       xor     bx, bx

       mov     word ptr es:[bx], offset _io_call ; fix address off
       mov     word ptr es:[bx+2], cs          ; fix address sel
       mov     word ptr es:[bx+4], 0ec00h      ; a r0 386 call gate
       mov     word ptr es:[bx+6], 0000h       ; high offset

       mov     dl, DevHlp_FreeGDTSelector      ; free gdthelper
       call    [_Device_Help]
       jnc     short aexit

aexit0: xor     ax,ax                           ; clear selector
        mov     word ptr [_io_gdt32], ax
aexit:  popa                                    ; restore all registers
        mov     ax, word ptr [_io_gdt32]
_acquire_gdt    endp

Figure 4: Initialization routine of FASTIO$ driver

Since a device driver is initialized in ring 3, this routine does not work during startup. Rather, the driver will call this code once the first time some client opens the device. Thus, to use the driver, a small routine io_init() needs to be called first. Refer to the file iolib.asm that comes with this issue of EDM/2.

A final improvement: Usually, C code passes arguments on the stack. A call gate can be configured to copy these parameters over to the new ring. But why should we do this? For really fast I/O access we pass the data in registers. This allows for direct replacement of I/O instructions in assembler code by a simple indirect call as shown in figure 5. The address of the indirect call is set up by the above mentioned io_init() procedure.

EXTRN   ioentry:FWORD
MOV     DX, portaddr
MOV     AL, 123
MOV     BX, 4           ; function code 4 = write byte
CALL    FWORD PTR [ioentry]

Figure 5: Calling I/O from assembler

If the code needs to be called from C, we simply write a small stub that wraps a stack frame envelope around it, just as shown in figure 6.

; Calling convention:
;       void c_outb(short port,char data)
       PUBLIC  _c_outb
       PUBLIC  c_outb
_c_outb PROC
       PUSH    EBP
       MOV     EBP, ESP                ; set standard stack frame
       PUSH    EBX                     ; save register
       MOV     DX, WORD PTR [EBP+8]    ; get port
       MOV     AL, BYTE PTR [EBP+12]   ; get data
       MOV     BX, 4                   ; function code 4 = write byte
       CALL    FWORD PTR [ioentry]     ; call intersegment indirect 16:32
       POP     EBX                     ; restore bx
       POP     EBP                     ; return
       ALIGN   4
_c_outb ENDP

Figure 6: A C callable I/O function

The file iolib.asm contains a set of functions c_inX() and c_outX() for using I/O from any 32 bit compiler that supports the standard stack frame. The files iolib.a and iolib.lib are pre-compiled versions; the file iolib.h contains the C prototypes.

In the complete driver, I gave up a small amount of the theoretically reachable performance. There are six basic I/O operations: IN and OUT instructions exist for transferring bytes, 16 bit words and 32 bit long words. To become really fast, one would have to provide a separate GDT selector for each of them. In a typical OS/2 system, this should not be a problem. However, if now everyone would start to add more routines, each with its own entry point, this resource could become rather quickly a scarce one. So I spent a function code, to be passed in the BX register, to multiplex the six functions into a single GDT selector. Refer to the io_call entry point in the fastio_a.asm driver source file.