sysprg.doc
来自「MMURTL(tm) Computer Operating System Ver」· DOC 代码 · 共 383 行 · 第 1/3 页
DOC
383 行
- Check or set up the device(s) if needed
- Anything else you need before being called
B) Enter the required data in the DCB(s) to pass to the InitDevDr call.
C) Enable (UnMaskIRQ) any hardware interrupts you are servicing unless this is done in one of the procedures (such as DeviceInit) as a normal function of your driver.
D) Call InitDevDr.
InitDevDr never returns to you. It terminates your task and leaves your code resident to be called by the OS when one of the three device driver calls are made.
At this point you have three device driver routines (functions, procedures, or whatever you want to call them) that are ready to be called by the OS. It will be the OS that calls them and not the users of the devices directly. This is because the OS has a very small layer of code which provides common entry points to the three device driver calls for ALL drivers. This layer of code performs several functions so you don't have to do them. One very important function is blocking for non-reentrant device drivers. In a multitasking environment it is possible (even likely) that a device driver function could be called while someone else's code is already executing it (especially if the driver controls two devices). One of the items you fill out in the DCB is a flag (fDevReent) to tell the OS if your driver is reentrant. Most device drivers are NOT reentrant so this will normally be set to FALSE (0).
System Resources For Device Drivers
Device drivers are very close to the hardware (they have to be, they control it). They require special OS resources to accomplish their missions. Many device drivers need to use DMA, Timer facilities, and must handle interrupts from the devices they control. MMURTL has a complete array of support calls for such drivers.
Interrupts
Several calls are provided to handle initializing and handling hardware Interrupt Service Routines (ISRs) in your code. You should see the section that deals specifically with ISRs for a complete description and examples of simple ISRs. The floppy and hard disk device drivers also provide good examples of ISRs. ISRs should be written in Assembly language for speed and complete code control (although it's not a requirement). The following is a list with brief descriptions of the calls you may need to set up and use in your ISR:
SetIRQVector is how you tell the OS what function to call when your hardware interrupt occurs. This is often called an interrupt vector.
EndOfIRQ sends and "End of Interrupt" signal to the Programmable Interrupt Controller Units (PICU), and should be called by you as the last thing your ISR does. In other OSs (specifically DOS) the ISR usually sends the EOI sequence directly to the PICU. Do not do this in MMURTL as other code may be required to execute in later versions of MMURTL which will render your device driver obsolete.
MaskIRQ and UnMaskIRQ are used to turn on and off your hardware interrupt by masking and unmasking it directly with the PICU. More detail about each of these calls can be found in the alphabetical listing of OS calls in this manual.
Direct Memory Access Device (DMA)
If your device uses DMA, you will have to use the DMASetUp call each time before you instruct your device to make the programmed DMA transfer. DMASetUp lets you set a DMA channel for the type, mode, direction, and size of a DMA move. DMA also has some quirks you should be aware of such as it's inability to cross segment physical boundaries (64Kb). Your driver should use the AllocDMAPage call to allocate memory that is guaranteed not to cross a physical segment boundary which also provides you with the physical address to pass to DMASetUp. It is important that you understand that the memory addresses your program uses are LINEAR addresses and do not equal physical addresses. MMURTL uses PAGED memory and the addresses you use will probably NEVER be equal to the physical address! Another thing to consider is that DMA on the ISA platforms is limited to a 16Mb physical address range. Program code can and will most likely be loaded into memory above the 16 Mb limit if you have that much memory in your machine. This means all DMA transfers must be buffered into memory that was allocated using AllocDMAPage. See the OS Public Call descriptions for more information on DMASetUp and how to use AllocDMAPage. Many users of DMA also need to be able to query the DMA count register to see if the DMA transfer was complete, or how much was transferred. This is accomplished with GetDMACount.
Timer Facilities
Quite often device drivers must delay (sleep) while waiting for an action to occur, or have the ability to "timeout" if an expected action doesn't occur in a specific amount of time. Two calls provide these functions. The Sleep call actually puts the calling process to sleep (makes it wait) for a specific number of 10 millisecond periods. The Alarm call will send a message to a specified exchange after a number of caller specified 10 millisecond increments. The Alarm call can be used asynchronously to allow the device code to continue doing something while the "Alarm" is counting down. If you use Alarm, you will most likely need to use KillAlarm which stops the alarm function if it hasn't already fired off a message to you. See the call descriptions for more information on the Sleep, Alarm, and KillAlarm functions.
Message Facilities for Device Drivers
Interrupt Service Routines (if you need them in your driver) sometimes require special messaging features that application programs don't need. A special call that may be required by some device drivers is ISendMsg. ISendMsg is a special form of SendMsg that can be used inside an ISR (with interrupts disabled). It doesn't force a task switch (even if the destination exchange has a higher priority process waiting there). ISendMsg also does NOT reenable interrupts before returning to the ISR that called it so it may be called from an ISR while interrupts are disabled. ISendMsg is used if an ISR must send more than one message from the interrupted condition. If you must send a message that guarantees a task switch to a higher priority task, or you only need to send one message from the ISR, you can use SendMsg. If you use SendMsg, it must be the FINAL call in your ISR after the EndOfIRQ call has been made and just before the IRETD (Return From Interrupt). This will guarantee a task switch if the message is to a higher priority task.
Detailed Device Interface Specification
The following sections describe the DCB, each of the three PUBLIC device calls, and how to implement them in your code.
Device Control Block Setup and Use
The DCB structure is shown below with sizes and offsets specified. How the structure is implemented in each programming language is different, but should is easy enough to figure out. The length of each field in the DCB is shown in bytes and all values are UNSIGNED. You can look at some of the included device driver code with MMURTL to help you.
The size of a DEVICE CONTROL BLOCK is 64 bytes. You must ensure that there is no padding between the variables if you use a RECORD in Pascal, or a structure in C or assembler to construct the DCB. If your driver controls more than one device you will need to have a DCB for each one it controls. Multiple DCBs MUST be CONTIGUOUS in memory.
Name Size Offset Description
DevName 12 0 Device Name, left justified
sbDevName 1 12 Length of Device name in bytes
DevType 1 13 1 = RANDOM device, 2 = SEQUENTIAL
nBPB 2 14 Bytes Per Block (1 to 65535 max)
(0 for variable block size)
dLastDevErc 4 16 Last error code from an operation
ndDevBlocks 4 20 Number of blocks in device
(0 for sequential)
pDevOp 4 24 pointer to device Operation handler
pDevInit 4 28 pointer to device Init handler
pDevStat 4 32 pointer to device Status handler
fDevReent 1 36 Is device handler reentrant?
fSingleUser 1 37 Is device usable from 1 JOB only?
wJob 2 38 If fSingleUser true, this is JOB
OSUseONLY1 4 40 ALLOCATE and 0, BUT DO NOT MODIFY
OSUseONLY2 4 44 "
OSuseONLY3 4 48 "
OSuseONLY4 4 52 "
OSuseONLY5 4 56 "
OSuseONLY6 4 60
The fields of the DCB must be filled in with the initial values for the device you control before calling InitDevDr. Most of the fields are explained satisfactorily in the descriptions next to the them, but the following fields require detailed explanations:
DevName - Each driver names the device it serves. Some names are standardized in MMURTL, but this doesn't prevent the driver from assigning a non-standard name. The name is used so that callers don't need to know a devices number in order to use it. For instance, floppy disk drives are usually named FD0 and FD1, but a driver for non-standard floppy devices can name them anything (up to 12 characters). Each devie name must be unique.
sbDevName - a single byte containing the number of bytes in the device name.
DevType - this indicates whether the device is addressable. If data can be reached at a specific numbered address as specified in the DeviceOP call parameter dLBA (Logical Block Address), then it's considered a RANDOM device and this byte should contain a 1. If the device is sequential and the concept of an address for the data has no meaning (such as with a COMMs port) then this should contain a 2.
nBPB - number of bytes per block. Disk drives are divided into sectors. Each sector is considered a block. If the sectors are 512 byte sectors (MMURTL standard) then this would be 512. If it is another device, or it's a disk driver that is initialized for 1024 byte sectors then this would reflect the true block size of the device. If it is a single byte oriented device such as a comms port or sequential video then this would be 1. If the device has variable length blocks then this should be 0.
dLastDevErc - When a driver encounters an error (usually an unexpected one) it should set this variable with the last error code it received. The driver does not need to reset this value as MMURTL resets it to ZERO on entry to each call to DeviceOP for each device.
ndDevBlocks - This is the number of addressable blocks in your device. For example, on a floppy disk this would be the number of sectors * number of cylinders * number of heads on the drive. MMURTL expects device drivers to organize access to each RANDOM device by 0 to n logical addresses. An example of how this is accomplished is with the floppy device driver. It numbers all sectors on the disk from 0 to nTotalSectors (nTotalSectors is nHeads * nSecPerTrack * nTracks). The head number takes precedence over track number when calculating the actual position on the disk. In other words, if you are on track zero, sector zero, head zero, and you read one sector past the end of that track, we then move to the next head (NOT the next track!). This reduces disk arm movement which increases throughput. The Hard disk driver operates the same way. Addressing with 32 bits limits address access to 4Gb on a single byte device and 4Gb * Sector Size on disk devices, but this is not much of a limitation. If the device is mountable or allows multiple media sizes this value should be set initially to allow access to any media for identification (this is device dependent).
pDevOp, pDevInit, & pDevStat are pointers to the calls in your driver for the following PUBLIC device functions:
DeviceOp - Performs I/O operations in device
DeviceInit - Initializes or resets the device
DeviceStats - Returns device specific status
MMURTL uses indirect 32 bit NEAR calls to your driver's code for these 3 calls. The RETURN instruction you use should be a 32 bit NEAR return. This is usually dictated by your compiler or assembler and how you define the function or procedure. In C-32 (MMURTL's standard C compiler) defining the function with no additional attributes or modifiers defaults it to a 32 bit near return. In assembler, RETN is the proper instruction.
fDevReent - This is a one byte flag (0 false, non-zero true) that tells MMURTL if the device driver is reentrant. MMURTL handles conflicts that occur when more than one task attempts to access a device. Most device drivers have a single set of variables that keep track of the current state of the device and the current operation. In a true multitasking environment more than one task can call a device driver. For devices that are NOT reentrant, MMURTL will "queue" up tasks that call a device driver currently in use. The queueing is accomplished with an exchange, the SendMessage, and WaitMessage primitives. A portion of the reserved DCB is used for this purpose. On very complicated devices such as a SCSI driver where several devices are controlled though one access point, the driver may have to tell MMURTL it's reentrant and handle device conflicts internally.
fSingleUser - If a device can only be assigned and used by one Job (user) at a time this flag should be true (non膠ero). This applies to devices like COMM ports that are assigned and used for a session.
wJob - If the fSingleUser is true, this will be the job that is currently assigned to the device. If this is zero, no job is currently assigned the device. If fSingleUser is false this field is ignored.
OSUseONLY1,2,3,4,5,6 - These are reserved for OS use and device drivers should NOT alter the values in them after the call to InitDevdr. They MUST be set to ZERO before the call to InitDevdr.
Standard Device Call Definitions
As described previously, all calls to device drivers are accessed through three predefined PUBLIC calls in MMURTL. They are:
DeviceOp(dDevice, dOpNum, dLBA, ndBlocks, pData):dError
DeviceInit(dDevice, pInitData, sdInitdata):dError
DeviceStat(dDevice, pStatRet, sdStatRetmax):dError
Detailed information follows for each of these calls. The procedural interfaces are shown with additional information such as the CALL FRAME OFFSET where the parameters will be found on the stack after the function has set up it's stack frame using Intel standard stack frame entry techniques. Note that your function should also remove these parameters from the stack. All functions in MMURTL return errors via the EAX register. Your function implementations must do the same. Returning ZERO indicates successful completion of the function. A non-zero value indicates and error or status that the caller should act on. Standard Device errors should be used when they adequately describe the error or status you wish to convey to the caller. Device specific error codes should be included with the documentation for the driver.
DeviceOp Function Implementation
The DeviceOp function is used by services and programs to carry out normal operations such as Read and Write on a device. The call is not device specific and allows any device to be interfaced through it. The dOpNum parameter tells the driver which operation is to be performed. An almost unlimited number of operations can be specified for the Device Operation call (2^32). The first 256 operations (dOp number) are predefined or reserved. They equate to standard device operations such as read, write, verify, and format. The rest of the dOp Numbers may be implemented for any device specific operations so long as they conform to the call parameters (described below).
CallName Parameters Call Frame Offset Return
DeviceOp( dDevice, EBP+24
dOpNum, EBP+20
dLBA, EBP+16
dnBlocks, EBP+12
pData) EBP+08
: dError
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?