Sample Code Chunks, From a Device Driver: Difference between revisions
Appearance
No edit summary |
No edit summary |
||
| Line 432: | Line 432: | ||
end | end | ||
</PRE> | </PRE> | ||
[[Category:Driver Articles]] | |||
Latest revision as of 19:19, 13 June 2020
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