Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

49KB

Developing Your Own 32-Bit Operating System — Chapter-10: Systems Programming

  • Source: /library/Developing Your Own 32-Bit Operating System/source-file.pdf
  • PDF pages: 163–180
  • Pages without text: 163

Systems Programming

142 | Part III ■ MMURTL-Specific Details This chapter is aimed at the MMURTL systems programmer. It describes writing message- based system services and device drivers. Use of the associated operating system calls is described in a generic format that can be understood by C, Pascal, and assembly language programmers. Systems programming deals with writing programs that work directly with the operating sys- tem and hardware or that provide services to application programs. Systems programmers require a better understanding of the operating system. Application pro- grammers shouldn't have to worry about hardware and operating-system internal operations to accomplish their tasks. They should have a basic understanding of multitasking and messag- ing and should be able to concentrate on the application itself. If you want (or need) to build device drivers or system services, you should make sure you understand the material covered in the architecture and applications programming chapters. They contain theory and examples to help you understand accessing services, messaging, and memory management operations. Writing Message-Based System Services System services are installable programs or built-in operating system services that provide system-wide, message-based services for application programs, as well as for other services. MMURTL is a message-based operating system and is designed to support programs in a client/server environment. Many people associate the term client/server only with a separate com- puter system that is the server for client workstations. MMURTL takes this down to a single processor level. A program that performs a specific function or group of functions that are shared by two or more programs is a system service. This is the basis behind the Request ( ) and Respond ( ) messaging primitives. Client programs make a request; the service does the process- ing and responds. This type of messaging can be compared to remote procedure calls with a twist. The file system and keyboard services are examples of message-based system services. Initializing Your System Service The basic steps to initialize your service and start serving requests are listed here:

  1. Initialize or allocate any resources required, such as: Additional memory, if required. Main service exchange (clients send requests here). Additional exchanges, if needed. Additional tasks, if required. Anything else you need to prepare for business (initialize variables, and so on).
  2. Call RegisterSVC( ) with your name and main service exchange.
  3. Wait for messages, service them, then respond.

Chapter 10 ■ Systems Programming A Simple System Service Example Listing 10.1 is the world's simplest system service for the MMURTL operating system. Its purpose is to hand out unique numbers to each request that comes in. It's not very useful, but it's a good clean example showing the basic “guts” of a system service. The service name is “NUMBERS " (note that the name is space-padded). Service names are case-sensitive. The service will get a pointer in the request block (pDatal) that tells it where the user wants the number returned. Listing 10.1. A system service. /* Super Simple System Service */ /* 64 byte request block structure */ struct RqBlk { long ServiceExch; long RespExch; long RqOwnerJob; long ServiceRoute; char *pRqHndlRet; long dDataO; long dDatal ; long dData2; int ServiceCode; char npSend; char npRecv; char *pDatal ; long cbDatal ; char *pData2; long cbData2; long RQBRsvdl ; long RQBRsvd2; long RQBRsvd3; }; #define ErcOK 0 #define ErcBadSvcCode 32 struct pRqBlk *RqBlk; unsigned long NextNumber : unsigned long MainExch; unsigned long Message[2]; /* A pointer to a Request Block */ /* The number to return */ /* Where we wait for Requests */ /* The Message with the Request */ void main(void) { unsigned long OSError, ErrorToUser; OSError = AllocExch(&MainExch) ; if (OSError) { exit (OSError) ; /* get an exchange */ look for a kernel error */ OSError = RegisterSvc( “NUMBERS “, MainExch); if (OSError) { /* look for a system error */

144 | Part III ■ MMURTL-Specific Details Listing 10.1. continued exit(OSError) ; while (1) { /* WHILE forever */ OSError = WaitMsg(MainExch, Message); /* Exch & pointer */ if (lOSError) { pRqBlk = Message[0]; /* First DWORD contains ptr to RqBlk */ if (pRqBlk.ServiceCode ==0) /* Abort request from OS */ ErrorTollser = ErcOK; else if (pRqBlk.ServiceCode == 1 ) { /* User Asking for Number */ *pRqBlk.pData1 = NextNumber++; /* Give them a number */ ErrorToUser = ErcOK; /* Respond with No error */ } else ErrorToUser = ErcBadSvcCode; /* Unknown Service code! */ OSError = Respond(pRqBlk, ErrorToUser): /* Respond to Request */ } } /* Loop while(1) */ The Request Block As you can see, the system service interface using Request ( ) and Respond ( ) is not complicated. As a system service, when you receive a request, Wait returns and the two-dword message is filled in. The first dword is the request block handle. This value is actually a pointer to the request block itself in unprotected operating system memory. The memory address of the re- quest block and the aliased pointer to the user's memory are active and available only while the request block is being serviced. This gives the system service the opportunity to look at or use any parts of the request block it needs. Several items in the request block are important to the service. The number of items needed depends on the complexity of the service you're writing. Items in the Request Block The request block contains certain items that the service needs to do its job. Programs and the operating system pass information to the service in various request block items. The complex- ity of your service, and how much information a program must pass to you, will be determined by how much information you need to carry out the request. As a system service writer, you define the data items you need, and it is your responsibility to document this information for application programmers.

Chapter 10 ■ Systems Programming The service must never attempt to write to the request block. In fact, installable services would find themselves terminated for the attempt. The request block is in operating-system memory that is read-only to user-level jobs. An installable service will operate at the protection and ac- cess user levels. The Service Code A single word (a 1 6-bit value) is used for the callers to indicate exactly what service they are requesting from you. This is called the service code. For example, in the file system, one num- ber represents OpenFile, another CloseFile, and another RenameFile. In the simple example above, the service only defines one service code that it handles. This is the number 1 (one). The operating system reserves service code 0 (zero) for itself in all services. You may not use or define service code 0 to callers in your documentation. When a service receives a request with service code 0, the operating system is telling you that a job (a user program or another service) either has exited or has been shut down by the operating system. How you handle this depends on how complex your service is, and whether or not you hold requests from multiple callers before responding. For all other values of the service code, you must describe in your documentation what request information you expect from the caller and what service you provide. If you receive a service code you don't handle or haven't defined, you should return the error message Bad Service Code, which is defined in standard MMURTL header files. Caller Information in a Request Three dwords are defined to allow a program to pass information to the service you require to do your job. These items are dDatae, dDatal, and dData2. They are 32-bit values, and whether or not they are signed or unsigned is up to you (the system service writer). They are one-way data values from the caller to you. They may not contain pointers to the caller's memory area, because the operating system does not alias them as such. A prime example of their use is in the file system service, where callers pass in a file handle using these data items. For larger data movement to the service, or any data movement back to the caller, the request block items pDatal and pData2 must be used. These two request block items are specifically designed as pointers to a data area in the caller's memory space. The size of the area is defined by the accompanying 32-bit values, cbDatal and cbData2. The operating system is “aware” of this, and it will alias the pointers so the caller's memory space is accessible to the service. As a service, you must not attempt to access any data in the caller's space other than what is defined by pDatal , cbDatal and pData2, cbData2. Also, be aware that these pointers are valid only from the time you receive the request and until you respond.

H Part III ■ MMURTL-Specific Details Asynchronous Services Most services will be synchronous. Synchronous services wait, receive a request, and then re- spond before waiting for the next request. All operations are handled in a serial fashion. This is the easiest way to handle requests. A service may receive and hold more than one request before responding. This type of service is defined as asynchronous. This type of service may even respond to the second request it re- ceives before responding to the first. This type of service requires one or more additional exchanges to hold the outstanding requests. The keyboard service is a good example of an asynchronous service. In the keyboard service, callers can make a request to be notified of global key codes. These key codes may not occur for long periods of time, and many normal keystrokes may occur in the meantime. This means the keyboard service must place the global key code requests on another exchange while it handles normal requests. The kernel primitive MoveRequest ( ) takes a request you have received using Wait, and moves it to another exchange. You can then use WaitMsg or CheckMsg to get the requests from this sec- ond hold exchange when you know you can respond to them. While you are holding requests asynchronously, you may receive an abort service code from the operating system (service code 0). If this happens, you need to ensure that you look at all the requests you are holding to see if this abort is for one of those requests. If so, you must respond to the held request identified by the abort immediately, before responding to any other requests you are holding. This is so that the operating system can reclaim the request block, which is a valuable system resource. The abort request contains the job number of the aborted program in dDataG. You would look at each request block in the RqOwnerJob field to see if this request came from it. If so, you must respond without accessing its memory or acting on its request. System Service Error Handling When the service responds, the error (or status) to the user is passed back as the second dword parameter in the respond call. The first is the request block handle. When you write system services, you must be aware of conventions used in error passing and handling. Some operating systems simply give you a true or false style of response as to whether or not the function you requested was carried out without errors. MMURTL uses this concept but carries it one step further. You return a 0 (zero) if all went well. If an error occurred or if you need to pass some kind of status back to the caller, you return a number that indicates what you want to tell them. An example of this is how the file system handles errors. OpenFile( ) returns a 0 error code if everything went fine. However, it

Chapter 10 ■ Systems Programming may return five or six different values for various problems that can occur during the OpenFile ( ) process. The file may already be opened in an incompatible mode by someone else, the file may not exist, or maybe the file system couldn't allocate an operating system resource needed to open your file. In each of these cases, you would receive a non-zero number indicating the error. You don't have to access a global in your program or make another call to determine what went wrong. |
147 Writing Device Drivers Device drivers control or emulate hardware on the system. Disk drives, communications ports (serial and parallel), tape drives, RAM disks, and network frame handlers are all examples of devices (or pseudo devices) that require device drivers to control them. MMURTL has stan- dardized entry points for all devices on the system. MMURTL is built in layers. The device drivers are the closest layer to the operating system. You should not confuse device drivers with message-based system services. Device drivers are accessed via call gates transparent to the applications programmer that allow callers to easily and rapidly access code with a procedural interface from high-level languages (C and Pascal for example), or by simply “pushing” parameters on the stack in the assembler and making a call. MMURTL provides several built-in device drivers including floppy disk, hard disk, basic video, keyboard, and communication (serial and parallel). Devices in MMURTL are classified as two different types: random- and sequential-oncnttd. Random-oriented devices are things such as disk drives and RAM disks that require the caller to tell the driver how much data to read or write, as well as where the data is on the device. Network frame handlers may also be random because a link layer address is usually associated with the read and write functions, although they may be sequential too. Some types of tape drives allow reading and writing to addresses (sectors) on the tape and therefore may also be random devices. Sequential devices are things like communications ports, keyboard, and se- quential video access (ANSI drivers, and so on). Most byte-oriented devices tend to be sequen- tial devices because they don't have a fixed-size block and are not randomly accessed. The MMURTL standard device driver interface can handle both sequential and random de- vices with fixed or variable block and/or sector lengths. The interface is non-specific. All device drivers use a device control block (DCB). The device driver defines and keeps the DCB in its memory space, which actually becomes the operating system memory space when you load it. When a device driver initializes itself with the call initDevDr ( ) , it passes in a pointer to its DCB. If a device driver controls more than one device, it will pass in a pointer to an array of DCBs. Things like Device Name, Block Size, and Device Type are examples of the fields in a DCB. Each device driver must also maintain in its data area all hardware specific data (vari- ables) needed to control and keep track of the state of the device.

H Part III ■ MMURTL-Specific Details Device Driver Theory MMURTL is a protected-mode, paged memory operating system that uses two of the four possible protection levels of the 386/486 processors. The levels are 0 and 3 and are referred to as System (0) and User (3). I have made it rather painless to build fast, powerful, device drivers and have even gone into substantial detail for beginners. Device drivers must have access to the hardware they control. Device drivers have complete access to processor I/O ports for their devices without operating system intervention. Only system code (protection level 0) is allowed processor port I/O access. This means message- or request-based system services and application code (both at user level 3) cannot access pons at all; otherwise, a processor protection violation will occur. For example, the file system is at user level 3. It does not directly access hardware. The device-driver builder must not access ports other than those required to control its hardware. Device drivers must not attempt to direcdy control the system DMA, timer hardware, inter- rupt vectors, PICUs, or any operating system structures. MMURTL provides a complete array of calls to handle all the hardware, which ensures complete harmony in MMURTL's real-time, multitasking, multithreaded environment, while providing a fair amount of hardware inde- pendence. MMURTL takes care of synchronization of system-level hardware access so device- driver programmers don't have to worry about it. Those who have written device drivers on other systems will appreciate this concept. Building Device Drivers When the device driver is installed by the loader, or included in the operating system code at build time, it has to make a system call to set itself up as a device driver in the system. Addi- tional system calls may have to be made to allocate or initialize other system resources required before it can handle its device. Device drivers can be doing anything from simple disk device emulation (RAM disk, for ex- ample), to handling complex devices such as hard drives, SCSI ports, or network frame han- dlers (network link layer devices). The call to set up as a device driver is InitDevDr( ). With InitDevDr( ), you provided a pointer to your device control blocks (DCB) after you filled in the required information. The DCB is shared by both the device driver and operating system code. Before calling InitDevDr, you will most likely have to allocate other system resources and pos- sibly even set up hardware interrupts that you will handle from your device. Additional system resources available to device drivers are discussed later in this chapter. The device driver looks, and is programmed, like any application in MMURTL. It has a main entry point you specify when writing the program. In C this would be main ( ) ; in Pascal it's the main program block. In assembler, this would be the START entry point specified for the

Chapter 10 ■ Systems Programming program. This code is immediately executed after loading the driver, just like any other pro- gram. This code will execute only once to allow you to initialize the driver. The main entry point will never be called again. This will generally be a very small section of code (approxi- mately 30 lines). How Callers Reach Your Driver All device drivers are accessed via one of three public calls defined in the operating system. You will have functions in your driver to handle each of these calls. When you fill in the DCB, you will fill in the addresses of the three entry points the operating system will call on behalf of the original caller. Your driver program must interface properly with the three public calls. You can name them anything you want in your program, but they must have the same number and type of param- eters (arguments) as the PUBLIC calls defined in the operating system for all device drivers. The operating system defines the three following PUBLIC calls: Devicelnit() DeviceOpO DeviceStat() The parameter listings and function expectations are discussed in detail later in this chapter. Your functions may not have to do anything when called depending on the device you are controlling. They must at least accept the calls and return a status code of 0 (for no error) if they ignore it. When programs call one of these PUBLIC calls, MMURTL does some house- keeping and then calls your specified function. Device Driver Setup and Installation The initialization section of your device driver must make certain calls to set up and install as a driver. The generic steps are described here (your actual calls may vary depending on what system resources you need): 1 . Initialize or allocate any resources required. Allocate any additional memory, if required. Allocate exchanges, if needed for messaging from a second task or the ISRs (see the floppy device driver for an example of a device driver with a separate task to handle hardware interrupts). Set up interrupt service routines, if needed. Check or set up the devices, if needed. Do anything else you need to before being called. 2. Enter the required data in the DCBs to pass to the InitDevDr( ) call.

150 Part III ■ MMURTL-Specific Details 3. Enable (unMaskIRQ( )) any hardware interrupts you are servicing, unless this is done in one of the procedures (such as Device I nit ( )) as a normal function of your driver. 4. Call InitDevDr(). InitDevDr never returns to you. It terminates your task and leaves your code resident to be called by the operating system when one of the three device-driver calls is made. At this point, you have three device-driver routines that are ready to be called by the operating system. It will be the operating system, not the users of the devices, that calls them. This is because the operating system has a very small layer of code that provides common entry points to the three device-driver calls for rf// drivers. This layer of code performs several functions for you. One very important function is blocking for non-re-entrant device drivers. In a multitasking environment it is 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 (f DevReent) to tell the operating system whether your driver is re-entrant. Most device drivers are not re-entrant, so this will normally be set to FALSE (0). System Resources for Device Drivers Because they control the hardware, device drivers are very close to the hardware. They require special operating system resources to accomplish their missions. Many device drivers need to use DMA and timer facilities, and they must service 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 servicing hardware interrupt service rou- tines (ISRs) in your code. See the section that deals specifically with ISRs for complete descrip- tions and examples of simple ISRs. The floppy and hard-disk device drivers also provide good ISR examples. ISRs should be written in assembly language for speed and complete code con- trol, 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 operating system what function to call when your hardware interrupt occurs. This is often called an interrupt vector. EndOfIRQ() sends an “End of Interrupt” signal to the programmable interrupt control- ler units (PICU), and should be called as the last thing the ISR does. In other operat- ing systems, 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 for execution 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 operating system calls in Chapter 1 5i “API Specification”.

Chapter 10 ■ Systems Programming Direct Memory Access Device (DMA) If your device uses DMA, you will have to use the DMASetUp ( ) call each time before instructing your device to make the programmed DMA transfer. The DMASetUp ( ) call 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 its inability to cross-segment physical boundaries (64 K). Your driver should use the AllocDMAPage( ) call to allocate memory that is guaranteed not to cross a physical segment boundary and also provide you with the physical address to pass to DMASetUp ( ) . It is important to 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 16MB limit if you have that much memory in your machine. This means all DMA transfers must be buffered into memory that was allo- cated using AllocDMAPage( ). See the operating system public call descriptions for more infor- mation 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 capability to “time-out” 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 1 0-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 de- scriptions 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 messag- ing 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 wofre-enable interrupts before returning to the ISR that called it, so it may be called from an ISR while interrupts are dis- abled. 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 x.\e final cA\ in your ISR after the EndOf IRQ( ) 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. H

152 | Part III ■ MMURTL-Specific Details Detailed Device Interface Specification The following sections describe the device control block (DCB), each of the three PUBLIC de- vice calls, and how to implement them in your code. Device Control Block Setup and Use The DCB structure is shown in the following table with sizes and offsets specified. How the structure is implemented in each programming language is different, but easy 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 there is no padding between the variables if you use a record in Pascal, a structure in C, or assembler to define 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. Table 10.1 presents the fields that define a DCB. Table 10.1. Device control block definition. Name Size Offset Description DevName 12 0 cbDevName 1 12 DevType 1 13 nBPB 2 14 dLastDevErc 4 16 ndDevBlocks 4 20 pDevOp 4 24 pDevInit 4 28 pDevStat 4 32 fDevReent 1 36 fSingleUser 1 37 wJob 2 38 OSUseONLYl 4 40 0SUse0NLY2 4 44 0Suse0NLY3 4 48 0Suse0NLY4 4 52 0Suse0NLY5 4 56 0Suse0NLY6 4 60 Device name, left justified Length of device name in bytes 1 = RANDOM device, 2 = SEQUENTIAL Bytes Per Block (1 to 65535 max.) (0 for variable block size) Last error code from an operation Number of blocks in device (0 for sequential) Pointer to device operation handler Pointer to device Init handler Pointer to device status handler Is device handler re-entrant? Is device usable from 1 job only? If fSingleUser true, this is job Allocate and 0, but do not modify Allocate and 0, but do not modify Allocate and 0, but do not modify Allocate and 0, but do not modify Allocate and 0, but do not modify Allocate and 0, but do not modify

Chapter 10 ■ Systems Programming The fields of the DCB must be filled in with the initial values for the device you control before calling lnitDevDr( ). Most of the fields are explained satisfactorily in the descriptions next to 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 device's number in order to use it. For instance, floppy disk drives are usually named FDO and FD1, but a driver for non-standard floppy devices can name them anything (up to 12 characters). Each device name must be unique. cbDevName 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 communications port), then this should contain a 2. nBPB The 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 drive that is initialized for 1 024-byte sectors, then this would reflect the true block size of the device. If it is a single-byte oriented device such as a communications 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 X number of cylinders X 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 X 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. S

154| Part III ■ MMURTL-Specific Details pDevOp, pDevlnit, and pDevStat These are pointers to the calls in your driver for the following PUBLIC device functions: DeviceOp — Performs I/O operations in device Device I nit — Initializes or resets the device DeviceStats — Returns device-specific status MMURTL uses indirect 32-bit near calls to your driver's code for these three 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 1-byte flag (0 false, non-zero true) that tells MMURTL if the device driver is re-entrant. 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 re-entrant, MMURTL will queue up tasks that call a device driver currently in use. The queuing is accomplished with an exchange using the SendMsg( ) and WaitMsg( ) 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 re-entrant, 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-zero). This applies to devices like communications 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 to the device. If fSingleUser is false, this field is ignored. OSUseONLYi ,2,3,4,5,6 These are reserved for operating system use and device drivers should not alter the values in them after the call to lnitDevDr( ). 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 these: DeviceOp(dDevice, dOpNum, dLBA, ndBlocks, pData) :dError DeviceInit(dDevice, plnitData, sdlnitdata) :dError DeviceStat(dDevice, pStatRet, sdStatRetmax) :dError

Chapter 10 M Systems Programming 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 its stack frame using Intel standard stack frame entry tech- niques. Note that your function should also remove these parameters from the stack. All func- tions in MMURTL that return errors use the EAX register. Your function implementations must do the same. Returning zero indicates successful completion of the function. A non-zero value indicates an error or status that the caller should act on. Standard device errors should be used when they describe well 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 inter- faced 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 (232). The first 256 operations (dOp number) are pre-defined or reserved. They equate to standard de- vice operations such as read, write, verify, and format. The rest of the dOp Numbers may be implemented for any device-specific operations as long as they conform to the call parameters described here. dError = DeviceOp(dDevice,dOpNum,dLBA,dnBlocks)pData) ; The call frame offsets for assembly language programmers are these: dDevice [EBP+24] dOpNum [EBP+20] dLBA [EBP+16] dnBlocks [EBP+12] pData [EBP+08] Parameter descriptions: dDevice The device number dOpNum Identifies which operation to perform 0 Null operation 1 Read (receive data from the device) 2 Write (send data to the device) 3 Verify (compare data on the device) 4 Format Block (tape or disk devices) 5 Format Track (disk devices only) 6 Seek Block (tape or disk devices only)

\156 Part III U MMURTL-Specific Details 7 Seek Track (disk devices only) (Communications devices) 10 OpenDevice (communications devices) 1 1 CloseDevice (communications devices) (RS-232 devices with explicit modem control) 15SetDTR 16 SetCTS Undefined operation numbers below 255 are reserved 256-n driver defined (driver specific) dLBA Logical block address for I/O operation. For sequential devices, this parameter will be ignored. dnBlocks Number of contiguous blocks for the operation specified. For sequential devices, this will simply be the number of bytes. pData Pointer to data (or buffer for reads) for specified operation DevkeStat Function Implementation The DeviceStat( ) function provides a way for device-specific status to be returned to a caller if needed. Not all devices will return status on demand. In cases where the function doesn't, or can't, return status, you should return 0 to pdStatusRet and return the standard device error ErcNoStatus. dError = DeviceStat(dDevice,pStatRet,dStatusMax,pdStatusRet) ; The call frame offsets for assembly language programmers are these: dDevice [EBP+20] pStatusRet [EBP+16] dStatusMax [EBP+12] pdStatusRet[EBP+08] Parameter descriptions: dDevice Device number to status pStatBuf Pointer to buffer where status will be returned dStatusMax Caller sets this to tell you the maximum size of status to return in bytes pdStatusRet Pointer to dword where you return actual size of status returned in bytes

Chapter 10 ■ Systems Programming icy Devicelnit Function Implementation Some devices may require a call to initialize them before use or to reset them after a catastro- phe. An example of initialization would be a communications port for baud rate, parity, and so on. The size of the initializing data and its contents are device specific and should be defined with the documentation for the specific device driver. dError = Devicelnit (dDevice,pInitData,dInitData) ; The call frame offsets for assembly language programmers are these: dDevice [EBP+16] plnitData [EBP+12] dlnitData [EBP+08] Parameter descriptions: dDevice Dword indicating device number plnitData Pointer to device specific data for initialization to be returned dlnitData Dword indicating maximum size of status to return in bytes Initializing Your Driver InitDevDr( ) is called from a device driver after it is first loaded to let the operating system in- tegrate it into the system. After the device driver has been loaded, it should allocate all system resources it needs to operate and control its devices while providing service through the three standard entry points. A 64-byte DCB must be filled out for each device the driver controls before this call is made. When a driver controls more than one device, it must provide the device control blocks for each device. The DBCs must be contiguous in memory. If the driver is flagged as not re-entrant, then all devices controlled by the driver will be locked out when the driver is busy. This is because one controller, such as a disk or SCSI controller, usually handles multiple devices through a single set of hardware ports, and one DMA channel if applicable, and can't handle more than one active transfer at a time. If this is not the case, and the driver can handle two devices simultaneously, the driver should be broken into two separate drivers. The definition and parameters to lnitDevDr( ) are as follows: InitDevDr(dDevNum, pDCBs, nDevices, fReplace) dDevNum This is the device number that the driver is controlling. If the driver controls more than one device, this is the first number of the devices. This means the devices are numbered consecutively.

s Part III ■ MMURTI-Specific Details pDCBs This is a pointer to the DCB for the device. If more than one device is controlled, this is the pointer to the first in an array of DCBs for the devices. This means the second DCB must be located at pDCBs + 64, the third at pDCBs + 128, and so on. nDevices This is the number of devices that the driver controls. It must equal the number of contiguous DCBs that the driver has filled out before the InitDevDr call is made. f Replace If true, the driver will be substituted for the existing driver functions already in place. This does not mean that the existing driver will be replaced in memory, it only means the new driver will be called when the device is accessed. A replacement driver must specify at least as many devices as the original driver handled. Operating System Functions for Device Drivers The following is a list of functions that were specifically designed for device drivers. They per- form many of the tedious functions that would otherwise require assembly language, and a very good knowledge of the hardware. Please use the MMURTL API reference for a detailed description of their use. AllocDMAMem Allocates memory that will be compatible with DMA operations. It also returns the physical address of the memory which is required for the DMASetUp call. EndOf IRQ Resets the programmable interrupt controller unit (PICU) at the end of the ISR sequence. MasklRQ Masks one interrupt (prevents it from interrupting) by programming the PICU. SetlRQVector Sets up a vector to your ISR. DMASetUp Programs the DMA hardware channel specified to move data to or from your device. UnMasklRQ Allows interrupts to occur from a specified channel on the PICU. Standard Device Error Codes The MMURTL device-driver interface code will return errors for certain conditions such as a device that's not installed being called. Your device drive will also return error codes for prob- lems it has honoring the device call. If an error code already defined in MMURTL's standard header file adequately describes the error or status, then use it.

Powered by TurnKey Linux.