3.2 Sample PDD: DRIVER2$
The DRIVER2$ sample has an additional feature. It supports a blocking read function. If an application performs a blocking read, it blocks until one special bit at the parallel port is set to 0.
Because the standard file functions should still read without blocking, a new mechanism is needed. For special purpose driver functions, the OS/2 API call DosDevIOCtl() can be used.
APIRET ret; HFILE Hf; UCHAR buffer[256]; ULONG dataLen; ret = DosDevIOCtl(Hf,0x80,0x60,NULL,0,0,buffer,1,&dataLen);
Before the function DosDevIOCtl() could be used, the device must be opened and save the file handle in the variable "Hf". The 1st parameter is the file handle for the already opened device. The 2nd and 3rd parameters are the "category" and the "function" code. In the sample I defined 0x80 as the "category" and 0x60 as the "function". "Categories" above 0x7F and "functions" above 0x5F are user defined. No parameters are transferred to the driver (parameters 4 to 6). The buffer for the reading byte is described with the parameters 7 to 9.
The drivers code looks like this:
case CMDGenIOCTL: if( (((PRP_GENIOCTL)rp)->Category == 0x80) && (((PRP_GENIOCTL)rp)->Function == 0x60) ) { /* lock callers buffer until request ends */ if(DevHelp_Lock(SELECTOROF(((PRP_GENIOCTL)rp)->DataPacket), LOCKTYPE_LONG_ANYMEM, 0, &lockHandle)) return (STDON | STERR | STATUS_ERR_UNKCMD); /* verify callers access rights to the buffer */ if(DevHelp_VerifyAccess(SELECTOROF(((PRP_GENIOCTL)rp)->DataPacket), 1, OFFSETOF(((PRP_GENIOCTL)rp)->DataPacket), VERIFY_READWRITE)) { DevHelp_UnLock(lockHandle); return (STDON | STERR | STATUS_ERR_UNKCMD); } /* block the running thread. disable intrrupts (ProcBlock enables it) */ _asm {cli} do { /* inform ticker function to poll */ blockedReadPending = (ULONG)rp; result = DevHelp_ProcBlock(blockedReadPending, -1L, WAIT_IS_INTERRUPTABLE); _asm {cli} if(result == WAIT_INTERRUPTED) { /* inform ticker function not longer to poll */ blockedReadPending = 0L; _asm {sti} /* unlock callers buffer */ DevHelp_UnLock(lockHandle); return (STDON | STERR | ERROR_I24_CHAR_CALL_INTERRUPTED); } } while(result || blockedReadPending); _asm {sti} /* inform ticker function not longer to poll */ blockedReadPending = 0L; /* copy the temporary buffer to the transfer buffer */ ((PBYTE)(((PRP_GENIOCTL)rp)->DataPacket))[0] = inputByte; /* unlock callers buffer until request ends */ if(DevHelp_UnLock(lockHandle)) return (STDON | STERR | STATUS_ERR_UNKCMD); return STDON; } else return (STDON | STERR | STATUS_ERR_UNKCMD);
First, the code checks, if the correct category code (0x80) and function code (0x60) for blocking read are used. The pointer to the read buffer, transferred with the IOCtl request packet, is a VIRTUAL ADDRESS (need not be converted), not LOCKED and not VERIFIED. The buffer needs to be locked with the DevHelp function DevHelp_Lock(). If the buffer's memory is not locked, the address could be invalid after returning from a blocking call.
In the next step, the address of the buffer's address should be checked for the correct privilege level with the function DevHelp_VerifyAccess(). Because the buffers address is a "normal" parameter to the function DosDevIOCtl(), the pointer could be invalid (maybe it does not have the correct privilege level).
Now the driver could wait until a specific bit of the port set to 0. Because the driver need not block the entire system, it must yield the CPU for other tasks. This gets done with the function DevHelp_ProcBlock(). This function receives a specific block id (it is a common way to use the request packet itself as the id). This function does not return until the blocking ends with a signal or another piece of code calls the function DevHelp_ProcRun(). In the sample DRIVER2$ a timer routine is installed which polls a specific port bit and calls DevHelp_ProcRun() if the bit is 0.
Here is the timer routine:
void ticker() { USHORT awakeCount; _asm {cli} if(blockedReadPending) { inputByte = inp(0x379); if( !(inputByte & 0x20)) { DevHelp_ProcRun(blockedReadPending,&awakeCount); blockedReadPending = 0L; } } _asm {sti} }
The DD init section installs this ticker routine while the DD gets initialized. The ticker() routine is called every 55 millisecond and verifies, if a request is pending (the variable "blockReadPending" is 0 if no request is running). If a request is pending AND the requested port bit is set to 0, the IOCtl driver section gets unblocked while calling the function DevHelp_ProcRun().
It is important to disable interrupts before calling the DevHelp_ProcBlock() function in the strategy routine and checking the wake up condition! The DevHelp_ProcBlock() itself enables the interrupts after calling. This mechanism is necessary to avoid deadlocks!
The timer handler could be installed in the init section:
/* install timer handler for ticker function */ if(DevHelp_SetTimer((NPFN)TIMERPROC)) return (STDON | STERR | STATUS_ERR_UNKCMD);
The function TIMERPROC is a stub, defined in the assembler code which calls the C function ticker():
_TIMERPROC proc far pusha push ds push es call _ticker pop es pop ds popa ret _TIMERPROC endp
With this mechanism, the application which requests the blocking read function waits until the bit is set to 0 without wasting too much CPU time.
The calling from the application's point of view looks like the following:
UCHAR buffer[256]; ULONG numBytes; ... ... ret = DosDevIOCtl(Hf,0x80,0x60,NULL,0,0,buffer,1,&dataLen);
This function block, until the bit 5 (Paper Empty) from the printer port becomes 0!
Warpstock Europe 1999 |