Sample Code Chunks, From a Device Driver

From EDM2
Jump to: navigation, search

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

;************************************************************************
; DATA block declarations
;************************************************************************
DGROUP  group   _DATA

        _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
;************************************************************************

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: 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!
;************************************************************************
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

  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.
;************************************************************************

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

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

        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



;************************************************************************
;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 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 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 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
;************************************************************************
Example proc near
   LOCAL   fReadOrWrite:   WORD,
           fpbfr:          DWORD,
           swpa:           SWAPACCESS

   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