Sample Code Chunks, From a Device Driver

By IBM

This file contains sample code information for memory management. This sample code is provided on an "AS IS" basis and therefore, assistance with its contents is NOT supported.

INTERNATIONAL BUSINESS MACHINES CORPORATION PROVIDES THIS DOCUMENT "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.

IN NO EVENT WILL IBM BE LIABLE FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO ANY LOST PROFITS, LOST SAVINGS OR ANY INCIDENTAL OR CONSEQUENTIAL DAMAGES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, OR FOR ANY CLAIM BY YOU BASED ON A THIRD PARTY CLAIM.

 The following code sections were pruned from a 2 GIGABYTE VIRTUAL RAM DISK driver developed by Peter Norberg, Consulting (CID 76260,3355). The driver allows for 3 sets of memory to be managed: (1) a list of physical memory buffers which are not normally accessed by OS/2, (2) non-swappable memory, managed by OS/2 (3) swappable memory, managed by OS/2

They show some techniques for access of FLAT memory under OS/2 2.0; at this point, the comments far from complete! I recommend careful reading to understand the code.

I hereby release this set of sample code chunks into the public domain, and I expressly deny any liability for it! Use this at your own risk -- this is intended for educational and training use.

Initial assumptions: During your INIT phase (or delayed, if you are allocating memory beyond      the first 16 meg), allocate your FLAT memory pointer using the VMA_ALLOC command. This memory may be fixed or movable, non-swapping or swappable. Place the returned flat address into the global plSwapMem pointer, with the clSwapMem counter describing the size of      the buffer. The following code will then demonstrate read/write access to that buffer. You must also allocate 2 selectors (selGDT and      selGDTUser) for use by this code.

Some simplifications are possible, depending on your application. This code assumes that you have allocated two selectors (selGDT and      selGDTUser) for use by the code for data copies. You may elect to use the calls which generate results in DS and ES as temporary selectors. I chose the global selectors so that interrupts could be enabled while accessing the data, and to allow me to keep the buffers addressed for as long as I like. However, since I am using global selectors, I had to protect the code which filled the selectors from reentrancy during the times the selectors are active. All of this is managed from the SingleThread/UnSingleThread pair and the higher-order DrvAccessNextBuffer and its associated DrvMapUnlock tool.

See the "Example" code at the end for a complete access/de-select example

Define a structure which stays on the stack, as a way of accessing flat memory. This struction contains the lock mechanism for that memory

SWAPACCESS STRUCT 2         ; swap plLockedRegion dd   0     ; if fLocked is non-0, this is the start of the locked region ulOffMem       dd   0     ; Offset to memory start clTotLeft      dd   0     ; Total amount of memory left to transfer cSingleThreaded dw  0     ; non-0 means we are single threaded idPhysMem      dw   0     ; If phys mem access, this is non-0 flocked        db   0,0   ; non-0 means memory is locked by this package achLock        db   12 dup (0)      ; Lock Handle SWAPACCESS ENDS

DGROUP group   _DATA
 * DATA block declarations
 * DATA block declarations

_DATA segment dword public use16 'DATA'

... Place you device header here ...

plSwapMem      dd     0      ; Gets LINEAR address in 2.0, of swap space cbSwapMem      dd     0      ; count of memory in the swap region

sem    dd      0             ; Semaphore for serializing access to selectors selGDT dw      0             ; global selector to use for DISK buffer access selGDTUser dw  0             ; global selector to use for USER buffers

_DATA  ends

_TEXT segment dword public use16 'CODE' assume cs:_TEXT, ds:DGROUP, es:NOTHING


 * SingleThread: Called with SI referencing the STACK BASED SWAPACCESS
 * structure; which has already been cleared by the caller.
 * Returns carry set if we were not allowed to go single threaded
 * Returns carry set if we were not allowed to go single threaded

SingleThread   proc inc    ss:[si.SWAPACCESS.cSingleThreaded] ; Set for recursive call cmp    ss:[si.SWAPACCESS.cSingleThreaded],1 ; See if already here je     @F                      ; nope: do the block clc                            ; else called recursively: ignore ret                            ; and leave @@:       PUSHAD IFDEF NOSEM @@:    CLI                             ; No ints for a while clc                            ; Set no error test   byte ptr sem,1          ; See if blocked already jz     @F                      ; nope: we now own it        call    FinishBlock             ; else block for a while jnc    @B                      ; if no error, retry mov    ss:[si.SWAPACCESS.cSingleThreaded],0 ; Set not blocked mov    ax,08103h               ; set error STC                            ; force error report STI                            ; re-allow interrupts POPAD ret                            ; and return: done

@@:    mov     byte ptr sem,1          ; take the semaphore over STI                            ; re-allow interrupts POPAD ret                            ; and return ELSE lea    bx,sem                  ; get the sem address mov    ax,ds                   ; from our dgroup mov    cx,-1                   ; set mov    di,cx                   ; the timeout mov    dl,DevHlp_SemRequest    ; request the sem call   [fpDevHlp]              ; and call the processor jnc    @F                      ; Worked OK        mov     ss:[si.SWAPACCESS.cSingleThreaded],0 ; Set not blocked mov    ax,08103h               ; set error @@:       POPAD ret                            ; and return: done ENDIF SingleThread   endp

UnSingleThread proc test   ss:[si.SWAPACCESS.cSingleThreaded],-1 ; Set for recursive call jnz    @F                      ; yep: we have been locked clc                            ; else not locked ret                            ; leave now @@:       PUSHAD
 * UnSingleThread: Called with SI referencing the STACK BASED SWAPACCESS
 * structure; which has been properlay maintained by caller and by
 * SingleThread. Concept is: any unlock unlocks all!
 * SingleThread. Concept is: any unlock unlocks all!

IFDEF NOSEM mov    byte ptr sem,0          ; set sem is cleared call   FinishUnBlock           ; and unblock our code clc                            ; no errors ELSE lea    bx,sem                  ; get the sem address mov    ax,ds                   ; from our dgroup mov    dl, DevHlp_SemClear     ; clear it        call    [fpDevHlp]              ; and call the processor ENDIF mov    ss:[si.SWAPACCESS.cSingleThreaded],0 ; Set no longer locked POPAD ret                            ; return; all done UnSingleThread endp


 * V20FlatSS: Converts SS:SI into eax FLAT address.
 * dl destroyed, returns Carry SET on error, CLEAR on OK.
 * dl destroyed, returns Carry SET on error, CLEAR on OK.

V20FlatSS proc                ; Called with SI ref. SS memory to convert to                              ; flat, on ret eax = address mov   ax,ss                 ; Set to access movzx esi, si               ; the the memory mov   dl, DevHlp_VirtToLin  ; then set to perform the Lin To Pagelist action call  [fpDevHlp]            ; and do the action ret                         ; And return as needed V20FlatSS endp

DrvMapUnlock   proc USES eax edx bx
 * On entry, si references the SWAPACCESS structure
 * On entry, si references the SWAPACCESS structure

mov    bx,ss:[si.SWAPACCESS.idPhysMem] ; get the memory access ID        or      bx,bx           ; See if locked jz     @F              ; nope mov    ss:[si.SWAPACCESS.idPhysMem],0 ; else set no longer locked push   si              ; save SI for a moment mov    si,PDNMEM_UNLOCK ; and call   CallPdnMem      ; call the routine to unlock pop    si              ; then @@:       push    si              ; restore SI        test    ss:[si.SWAPACCESS.fLocked],-1   ; See if locked jz     @F              ; not locked! mov    ss:[si.SWAPACCESS.fLocked],0    ; set no longer locked lea    si,ss:[si.SWAPACCESS.achLock]   ; Get the lock handle call   V20FlatSS       ; the page list flat address jc     @F              ; if error, ignore mov    esi,eax                 ; else mov    dl,DevHlp_VMUnlock      ; and set call   [fpDevHlp]              ; to unlock the memory @@:       pop     si                      ; restore ref to SWAPACCESS

call   UnSingleThread          ; And turn off single thread, if needed ret                            ; Return: done DrvMapUnlock   ENDP

; if access legal, you will be in SINGLE THREADED mode (i.e., ;             re-entrancy to the subsystem is blocked), and selGDT will ref ; the selected memory. ; ; ; WARNING! Must be called with DS             referencing our data segment!
 * DrvAccessNextBuffer: Map the sector passed into a temporary physical pointer
 * in es:di or ds:si:
 * On entry:
 * DH is 0 for mapping into ds:si,
 * 1 for mapping into es:di, 0 for ds:si
 * 2 means do NOT assign ds or es to resulting selector
 * 3 means we will write to the selector, but do not set ES or DS
 * SI = reference to the SWAPACCESS array on entry,
 * in the SS group
 * On exit,
 * DX is unchanged
 * if DH was 0 or 1, the selected register pair (es:di or ds:si) are updated
 * to ref the selGDT selector
 * else ES, DS, SI, DI are unchanged on exit.
 * ECX is the count you can access this time through.
 * Side Effects: SWAPACCESS structure updated to reflect current conditions,
 * if DH was 0 or 1, the selected register pair (es:di or ds:si) are updated
 * to ref the selGDT selector
 * else ES, DS, SI, DI are unchanged on exit.
 * ECX is the count you can access this time through.
 * Side Effects: SWAPACCESS structure updated to reflect current conditions,
 * Side Effects: SWAPACCESS structure updated to reflect current conditions,

DrvAccessNextBuffer proc near LOCAL fbDH: WORD, cbReq:  WORD, ulOldDI: DWORD, npSwpa:  WORD

mov    fbDH,dx           ; Save the storage flag mov    npSwpa,si         ; save low half of SI; access to SWAPACCESS array mov    ulOldDI,edi       ; save EDI

call   DrvMapUnlock      ; Unlock prior access

mov    ecx,ss:[si.SWAPACCESS.clTotLeft] ; 0 extend the count cmp    ecx,MAX_BLOCK_XFER      ; see jbe    @F                      ; if at end mov    ecx,MAX_BLOCK_XFER      ; nope: set upper limit @@:  mov     eax, ss:[si.SWAPACCESS.ulOffMem] ; get the memory ref add    eax,plSwapMem           ; Here on in the SWAP region: cmp    ecx,cbSwapMem           ; see if legal ja     DrvMapBadReq            ; OK   mov     cbReq,cx                ; save count really requested mov    ebx,eax                 ; for later use mov    ss:[si.SWAPACCESS.plLockedRegion],eax ; Save for later free

mov    si,npSwpa               ; re-get access to swap info lea    si,ss:[si.SWAPACCESS.achLock]  ; Set for extract of lock handle call   V20FlatSS               ; lock page list rel to SS   jc      DrvMapOops              ; FAILED: forget it   mov     esi,eax                 ; save the lock handle sub    eax,eax                 ; Lock in place, short term mov    edi,-1                  ; set no page list is to be generated test   byte ptr fbDH+1,1       ; see if write planned jz     @F                      ; nope mov    ax,8                    ; yep: force "WE ARE WRITING" bit @@:  mov     dl,DevHlp_VMLock        ; and call   [fpDevHlp]              ; perform the lock jc     DrvMapOops              ; FAILED: forget it   mov     si,npSwpa               ; restore ref to info mov    ss:[si.SWAPACCESS.fLocked],1 ; Else set LOCKED

call   SingleThread            ; force into single threaded mode jc     DrvMapOops              ; OOPS: No single thread! forget it  mov     ax,selGDT               ; get the selector ;EBX is still the linear address of the buffer ; ECX is the length of the buffer mov    dl,DevHlp_LinToGDTSelector ; convert to GDT selector call   [fpDevHlp]               ; do the conversion jc     DrvMapOops              ; FAILED the mapping mov    si,npSwpa               ; PHYS! restore ref to info mov    edi,ulOldDI             ; restore EDI mov    dx,fbDH                 ; restore results flag movzx  ecx,cbReq               ; and correct the count for excess add    ss:[si.SWAPACCESS.ulOffMem],ecx ; calculate the next access offset sub    ss:[si.SWAPACCESS.clTotLeft],ecx ; and the total left to transfer

cmp    dh,2                    ; are we all done? jae    DrvMapOkRet             ; yep: so exit

or     dh,dh                   ; see who gets what jz     @F                      ; if 0, DS:SI sub    di,di                   ; get the SI   mov     es,selGDT               ; so get it DrvMapOkRet: clc                            ; set good access ret                            ; and return @@:    ; HERE on results in DS:SI sub    si,si                   ; get the SI   mov     ds,selGDT               ; DESTRY DS   ret                             ; and return

DrvMapBadReq: mov    eax,0AA55h              ; set special error code mov    si,npSwpa               ; get the handle to the lock info call   DrvMapUnlock            ; go unlock it

stc                            ; FAILED ACTION ret                            ; return quickly

DrvAccessNextBuffer endp

Example proc near LOCAL  fReadOrWrite:   WORD, fpbfr:         DWORD, swpa:          SWAPACCESS
 * Example code section which accesses some of the above
 * Entered with:
 * dx = Read (0) or write (1) flag
 * eax = PHYSICAL address of user buffer
 * ebx = offset from start of our FLAT buffer to access
 * ecx = count of bytes to access
 * ecx = count of bytes to access

mov    fReadOrWrite, dx                ; ON ENTRY, dx = READ/WRITE flag ; 0 read, 1 write mov    fpbfr,eax                       ; eax = pointer to buffer to access mov    edx,ecx                         ; save count of bytes lea    di,swpa                         ; set mov    cx, sizeof swpa                 ; to init sub    ax,ax                           ; the cld                                    ; swap info structure push   ss                              ; as   pop     es                              ; needed rep    stosb                           ; to all 0

... Now have code calculate where in the buffer it needs to access: ... set swpa.ulOffMem to the offset to the start of the portion of the buffer, ... set swpa.clTotLeft to the total count of bytes left to access

mov    swpa.clTotLeft,edx              ; save the count for access mov    swpa.ulOffMem,ebx               ; save the memory offset from our ; flat buffer

DrvRdLp:                     ; PROCESS LOOP: loop here until transfer is done cmp    swpa.clTotLeft,0                ; see if any left je     DrvRWDone                       ; if not, leave

mov    dh,2                            ; Set do NOT change es/ds add    dh,byte ptr fReadOrWrite        ; convert to 3, for write lea    si,swpa                         ; get the address of the info call   DrvAccessNextBuffer             ; do the mapping, LOCK THREAD jc     DrvRwBadSec                     ; could not lock/map! mov    ax,word ptr fpbfr+2             ; get the user buffer mov    bx,word ptr fpbfr               ; physical address mov    si,selGDTUser                   ; set target GDT call   PhysToGDTSel                    ; into our selector jc     DrvRwBadSec                     ; could not generate the mapping!

push   ds                              ; Save our current segment test   byte ptr fReadOrWrite,-1        ; See if READ or WRITE jnz    @F                              ; WRITE: Process as needed

mov    es,selGDTUser                   ; USER pointer and mov    ds,selGDT                       ; and the memory base jmp    DrvRdWrCpy                      ; go do the copy

@@:  ; HERE for WRITE action set-up mov    es,selGDT                       ; and the memory base mov    ds,selGDTUser                   ; get the USER selector

DrvRdWrCpy: assume DS:NOTHING mov    bx,cx                           ; Save the transfer count sub    di,di                           ; set for target offset and sub    si,si                           ; source offsets of 0. cld                                    ; Force correct string direction rep    movsb                           ; and transfer the buffer

; OPTIMIZATION NOTE: The above could have a SHR CX,2 \ REP MOVSD ;      if it is known that the buffer size is DWORD oriented; ;      or similar in-line code could detect that condition

pop    ds                              ; Restore DS   assume  DS:DGROUP                       ; and


 * NOTE: THIS IS WHERE TC_YIELD SHOULD BE, IF USED!

mov    ax,bx                           ; calculate add    word ptr fpBfr,bx               ; to form the adc    word ptr fpBfr+2,0              ; next user buffer address to access jmp    DrvRdLp                         ; do next

DrvRWDone:                                ; HERE on all done lea    si,swpa                         ; get the handle to the lock info call   DrvMapUnlock                    ; unlock the memory

CLC                                    ; Set OK   ret                                     ; and report as needed

DrvRWFailed: pop    ds                              ; Restore DS

DrvRWBadSec: lea    si,swpa                         ; get the handle to the lock info call   DrvMapUnlock                    ; unlock the memory

mov    ax,8103h                ; Set DRIVE NOT READY stc                            ; Set ERROR

ret                            ; and report in

Example endp

_TEXT  ends

end 