diff options
Diffstat (limited to 'private/mvdm/vdd/vddserv.doc')
-rw-r--r-- | private/mvdm/vdd/vddserv.doc | 791 |
1 files changed, 791 insertions, 0 deletions
diff --git a/private/mvdm/vdd/vddserv.doc b/private/mvdm/vdd/vddserv.doc new file mode 100644 index 000000000..175fa7b7f --- /dev/null +++ b/private/mvdm/vdd/vddserv.doc @@ -0,0 +1,791 @@ + + +Installable Virtual Device Driver (VDD) Support For NTVDM +========================================================== + +Problem: +-------- +There is a class of DOS applications which run on their +own custom hardware. Generally these applications will +have a pluggable card and a 16bit device driver for their +card. As these cards are beyond the scope of normal PC +architecture, NTVDM cannot virtualize them in a secure +manner. So all such applications are not supported under +NTVDM. + +Solution: +--------- + +If an ISV is writing an NT native device driver for such a +card, it is technically quite simple to provide the support +such that the DOS application runs unmodified and in a secure +fashion. The vendor has to write a Virtual Device Driver (VDD) +which will virtualize the card for the DOS application by calling +the native device driver. + + + ----------------------- + | | + | DOS Application | V86 mode + | | + ----------------------- + I/O-map | ^ |Mem-Map | DMA + IO | | |IO | + -------------|----------------------------- + | | | | + V | V V + ----------------------- + | | DMA | + | NTVDM -------| NT User mode code + | | + ----------------------- + ^ | + | | Dispatches the event to VDD + | V + ----------------------- + | | + | VDD | VDD is a DLL attached to NTVDM + | | + ----------------------- + ^ | + | | Call the real driver + -------------|----------------------------- + | | + | V + ----------------------- + | | + | NT Device Driver | Kernel mode + | | + ----------------------- + ^ | + | | Carries out the operation with its card + | V + ----------------------- + | | + | Plugged Card | + | | + ----------------------- + +Following are the main work items to achieve the above solution: + + a. Loading the VDD + b. Support for I/O mapped I/O + c. Support for Memory mapped I/O + d. Support for DMA operations + e. Register manipulation services + f. Memory accessing services + g. Interrupt simulation services + h. Miscelleneous services + + +a. Loading the VDD: + + The system administrator will add the command lines in the + \\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\CONTROL\"VirtualDeviceDrivers" + section of the registry for all the VDDs to be loaded in the + VDM process. The command line format is REG_MULT_SZ (i.e. ASCIIZZ). + This key will laways be present and install program just need to + add their VDD name. + + [VirtualDeviceDrivers] + VDD = <full-path of the VDD1.DLL>\0<full-path of the VDD2>\0\0 + + VDD ACTION : + + NTVDM will load all these VDDs from the registry at the VDM process + initialization time and call their initialization routine. VDDs should + make sure at this time that their respective NT device driver is + present and make all the resource allocations. The VDD handle passed + in this initialization routine will be the Id of the VDD when calling + the VDD services as described below. + + +b. Support for I/O mapped I/O: + + Following services will be provided for VDDs to deal with IO + ports: + + BOOL VDDInstallIOHook (HANDLE, IO_PORT_RANGE, IO_PORT_HANDLERS); + BOOL VDDDeInstallIOHook (HANDLE, IO_PORT_RANGE); + + The HANDLE is the one passed to the VDD in its DLLInit routine. + Only one IO hook may be installed for a given port, all subsequent + requests will fail. On DeInstalling, the default IO hook will be + placed, which means no one is hooked on those IO ports. IO_PORT_HANDLERS + should atleast provide a byte read and a byte write handler. In addition, + word and string handlers can also be provided. In the absense of word + or string handlers, these will be emulated using byte handlers. + A port has to be hooked for both read and write. VDDs should not hook + DMA ports as these are virtualized by NTVDM and services are provided + for VDDs to access DMA. + + VDD Action: On an IO access (or on a series of IO accesses) the VDD + can check if it needs to start the DMA. If so it can call + the DMA services given in the following section. If it + does'nt require the DMA or if the DMA has transfered the + buffer, then the VDD can call its NT device driver to + complete the desired request. (Its also possible that the + VDD first requests the device driver and than using DMA + services copies the contents to the VDM buffer). + + +c. Support for Memory mapped I/O: + + Following services will be provided for VDDs to deal with their + memory mapped addresses: + + BOOL VDDInstallMemoryHooks (HANDLE, ADDR_RANGE, MEMORY_HANDLER); + BOOL VDDDeInstallMemoryHooks (HANDLE, ADDR_RANGE); + + The addr_range should be a valid range i.e. above RMSIZE and below system + rom. Only one Memory hook may be installed for a given range, all subsequent + requests will fail. On deinstalling the range, VDD will no longer get page + fault on those ranges. Memory_handler will + tell on which address the fault occured and whether it was a + read or write fault. On its return from the memory handler it will be + assumed that the fault was handled. + + + A port-range will actually result in a whole page to be reserved. That + means the page will not remain available to EMM for EMM page frames and + UMBs. + + VDD Action: It can map the normal memory and let the app write to it. + Later they can use the WIN32 API's or its device driver + as per the case to deal the whole memory range. + +d. Support for DMA operations + + Following DMA service will be provided for the VDD: + + DWORD VDDRequestDMA (HANDLE, DMA_CHANNEL, BUFFER, TRANSFER_BYTES); + BOOL VDDQueryDMA (HANDLE, DMA_CHANNEL, DMA_INFO_BUFFER); + BOOL VDDSetDma (HANDLE, DMA_CHANNEL, INDEX, DMA_INFO_BUFFER); + + NTVDM will control all the DMA ports and will maintain all the + information on per channel basis. There are two flavors in which + a VDD can carry-out the DMA operations. It can call VDDRequestDMA + and this service will interpret the DMA registers and do the + DMA transfer. It should be clear at this point that this will + involve two buffer copyings. For example, the VDD will ask the device + driver to trasnfer some data in a buffer (allocated by VDD) then it will + call this service to transfer this buffer to the address DOS application + has asked for (through DMA programming). + + On the other hand, a VDD can collect all the DMA registers using + VDDQueryDMA and figure out where the DOS application has asked the + DMA to take place, in what mode and how much to transfer. Then it can + call the NT device driver with same address as asked by DOS app. In this + case there will be only one copying. VDD should use VDDSetDMA after + such an operation to update the DMA state. + +e. Register manipulation services: + + See the reference section for details. There is a get and a set service + for each V86 registers. + + +f: Memory Accessing services: + + Following services are provided for Manipulating VDM's memory. + + PVOID GetVDMPointer(ULONG Address, ULONG Size, BOOL ProtectedMode); + BOOL FreeVDMPointer(ULONG Address, ULONG Size, BOOL ProtectedMode); + BOOL FlushVDMPointer(ULONG Addr, + ULONG Size, PVOID Buffer, BOOL ProtectedMode); + +g: Interrupt simulation services + + Following services is provided for simulating an interrupt to the VDM. + + VOID VDDSimulateInterrupt (BYTE ms, BYTE line, WORD count); + + +h. Miscellaneous Services + + Following services will be provided for memory allocation/deallocation. + + VDDAllocMem (HANDLE, ADDRESS, PAGES); + VDDFreeMem (HANDLE, ADDRESS, PAGES); + + VDD will use VDDAllocMem when it gets a page fault on a page which + it has hooked using VDDInstallMemoryHook. Later it can free this memory + using VDDFreeMem. We are asking VDDs to use WIN32 API for all their needs + and here also a VDD could have used VirtualAlloc and VirtualFree. The + problem is that on a non-x86 machine this would'nt have worked because + on such a plateform the VDM memory is under the emulator's control and it + has to be told of such memory changes. + + Following service will be provided for VDM termination. + + VDDTerminateVDM (VOID); + + VDD will use this service to terminate the VDM. For instance if a VDD + fails to allocate memory on a memory mapped IO, it may want to terminate + the VDM as its state is inconsistenet. + + VDDs should always use these services to achieve plateform independence. + + + +---------------- REFERENCE SECTION ------------------------- + +/** Basic typedefs of VDD IO hooks **/ + +typedef VOID (*PFNVDD_INB) (WORD iport,BYTE * data); +typedef VOID (*PFNVDD_INW) (WORD iport,WORD * data); +typedef VOID (*PFNVDD_INSB) (WORD iport,BYTE * data,WORD count); +typedef VOID (*PFNVDD_INSW) (WORD iport,WORD * data,WORD count); +typedef VOID (*PFNVDD_OUTB) (WORD iport,BYTE data); +typedef VOID (*PFNVDD_OUTW) (WORD iport,WORD data); +typedef VOID (*PFNVDD_OUTSB) (WORD iport,BYTE * data,WORD count); +typedef VOID (*PFNVDD_OUTSW) (WORD iport,WORD * data,WORD count); + +/** Array of handlers for VDD IO hooks. **/ + +typedef struct _VDD_IO_HANDLERS { + PFNVDD_INB inb_handler; + PFNVDD_INW inw_handler; + PFNVDD_INSB insb_handler; + PFNVDD_INSW insw_handler; + PFNVDD_OUTB outb_handler; + PFNVDD_OUTW outw_handler; + PFNVDD_OUTSB outsb_handler; + PFNVDD_OUTSW outsw_handler; +} VDD_IO_HANDLERS, *PVDD_IO_HANDLERS; + +/** Port Range structure **/ +typedef struct _VDD_IO_PORTRANGE { + WORD First; + WORD Last; +} VDD_IO_PORTRANGE, *PVDD_IO_PORTRANGE; + +/** Memory mapped I/O handler. **/ + +typedef VOID (*PVDD_MEMORY_HANDLER) (PVOID FaultAddress, ULONG RWMode); + +/** Buffer for returning DMA information **/ +typedef struct _VDD_DMA_INFO { + WORD addr; + WORD count; + WORD page; + BYTE status; + BYTE mode; + BYTE mask; +} VDD_DMA_INFO, *PVDD_DMA_INFO; + +/*** VDDInstallIOHook - This service is provided for VDDs to hook the + * IO ports they are responsible for. + * + * INPUT: + * hVDD ; VDD Handle + * cPortRange; Number of VDD_IO_PORTRANGE structures + * pPortRange; Pointer to array of VDD_IO_PORTRANGE + * IOhandler : VDD handler for the ports. + * + * OUTPUT + * SUCCESS : Returns TRUE + * FAILURE : Returns FALSE + * GetLastError has the extended error information. + * + * NOTES: + * 1. The first one to hook a port will get control. Subsequent + * requests will be failed. There is no concept of chaining + * the hooks. + * + * 2. IOHandler must atleast provide a byte read and a byte write + * handler. Others can be NULL. + * + * 3. If word or string handlers are not provided, their effect + * will be emulated using byte handlers. + * + * 4. VDDs should not hook DMA ports. NTVDM manages it for all + * the clients and services are provided to perform DMA + * operations and to access and modify DMA data. + * + * 5. VDDs should not hook video ports as well. Such a hooking + * will succeed but there is no gurantee that the IO handler will + * get called. + * + * 6. Each Vdd is allowed to install only one set of IO hooks + * at a time. + * + * 7. Extended Error codes: + * + * ERROR_ACCESS_DENIED - One of the requested ports is already hooked + * ERROR_ALREADY_EXISTS - Vdd already has active IO port handlers + * ERROR_OUTOFMEMORY - Insufficient resources for additional VDD + * Port handler set. + * ERROR_INVALID_ADDRESS - One of the IO port handlers has an invalid + * address. + */ +BOOL VDDInstallIOHook ( + HANDLE hVdd, + WORD cPortRange, + PVDD_IO_PORTRANGE pPortRange, + PVDD_IO_HANDLERS pIOFn +); + +/*** VDDDeInstallIOHook - This service is provided for VDDs to unhook the + * IO ports they have hooked. + * + * INPUT: + * hVDD : VDD Handle + * + * OUTPUT + * None + * + * NOTES + * + * 1. On Deinstalling a hook, the defult hook is placed back on + * those ports. Default hook returns 0xff on reading + * and ignores the write operations. + * + */ +VOID VDDDeInstallIOHook ( + HANDLE hVdd, + WORD cPortRange, + PVDD_IO_PORTRANGE pPortRange +); + +/*** VDDInstallMemoryHook - This service is provided for VDDs to hook the + * Memory Mapped IO addresses they are resposible + * for. + * + * INPUT: + * hVDD : VDD Handle + * addr : Starting linear address + * count : Number of bytes + * MemoryHandler : VDD handler for the memory addresses + * + * + * OUTPUT + * SUCCESS : Returns TRUE + * FAILURE : Returns FALSE + * GetLastError has the extended error information. + * + * NOTES + * 1. The first one to hook an address will get the control. There + * is no concept of chaining the hooks. VDD should grab the + * memory range in its initialization routine. After all + * the VDDs are loaded, EMM will eat up all the remaining + * memory ranges for UMB support. + * + * 2. Memory handler will be called with the address on which the + * page fault occured and with a falg telling whether it was a + * read or write operation. + * + * 3. On returning from the hook handler it will be assumed that + * the page fault was handled and the return will go back to the + * VDM. + * + * 4. Installing a hook on a memory range will result in the + * consumption of memory based upon page boundaries. The Starting + * address is rounded down, and the count is rounded up to the + * next page boundary. The VDD's memory hook handler will be + * invoked for all addreses within the page(s) hooked. The page(s) + * will be set aside as mapped reserved sections, and will no + * longer be available for use by NTVDM or other VDDs. The VDD is + * permitted to manipulate the memory (commit, free, etc) as needed. + * + * 5. After calling the MemoryHandler, NTVDM will return to the + * faulting cs:ip in the 16bit app. If the VDD does'nt want + * that to happen it should adjust cs:ip appropriatly by using + * setCS and setIP. + * + * 6. Only one VDD will be allowed to have memory hooks in a page. + * In other words a page si owned by a VDD exclusively. + * + * 7. Extended Error codes: + * + * ERROR_ACCESS_DENIED - One of the requested ports is already hooked + * ERROR_OUTOFMEMORY - Insufficient resources. + */ +BOOL VDDInstallMemoryHook ( + HANDLE hVDD, + PVOID pStart, + DWORD count, + PVDD_MEMORY_HANDLER MemoryHandler +); + +/*** VDDDeInstallMemoryHook - This service is provided for VDDs to unhook the + * Memory Mapped IO addresses. + * + * INPUT: + * hVDD : VDD Handle + * addr : Starting linear address + * count : Number of addresses + * + * OUTPUT + * None + * + * NOTES + * 1. On Deinstalling a hook, the memory range becomes invalid. + * VDM's access of this memory range will cause a page fault. + * + * 2. Extended Error codes: + * ERROR_INVALID_PARAMETER - One of the parameter is invalid. + */ +BOOL VDDDeInstallMemoryHook ( + HANDLE hVDD, + PVOID pStart, + DWORD count +); + +/*** VDDRequestDMA - This service is provided for VDDs to request a DMA + * transfer. + * + * INPUT: + * hVDD VDD Handle + * iChannel DMA Channel on which the operation to take place + * Buffer Buffer where to or from transfer to take place + * length Transfer Count (in bytes) + * If Zero, returns the Current VDMA transfer count + * in bytes. + * + * OUTPUT + * DWORD returns bytes transferred + * if Zero and GetLastError is set means operation failed. + * if Zero and GetLastError is clear means VDMA transfer count + * was zero. + * + * NOTES + * 1. This service is intended for those VDDs which do not want to + * carry on the DMA operation on their own. Carrying on a DMA + * operation involves understanding all the DMA registers and + * figuring out what has to be copied, from where and how much. + * + * 2. This service will be slower than using VDDQueryDMA/VDDSetDMA and + * doing the transfer on your own. + * + * 3. Extended Error codes: + * + * ERROR_ALREADY_EXISTS - Vdd already has active IO port handlers + * ERROR_OUTOFMEMORY - Insufficient resources for additional VDD + * Port handler set. + * ERROR_INVALID_ADDRESS - One of the IO port handlers has an invalid + * address. + * + */ +DWORD VDDRequestDMA ( + HANDLE hVDD, + WORD iChannel, + PVOID Buffer, + DWORD length +); + +/*** VDDQueryDMA - This service is provided for VDDs to collect all the DMA + * data. + * + * INPUT: + * hVDD VDD Handle + * iChannel DMA Channel for which to query + * Buffer Buffer where information will be returned + * + * OUTPUT + * SUCCESS : Returns TRUE + * FAILURE : Returns FALSE + * GetLastError has the extended error information. + * + * + * NOTES + * 1. This service is intended for those VDD which are doing + * performance critical work. These VDD can do their own DMA + * transfers and avoid one extra buffer copying which is a + * overhead in using VDDRequestDMA. + * + * 2. VDDs should use VDDSetDMA to properly update the state of + * DMA after carrying on the operation. + * + * 3. Extended Error codes: + * + * ERROR_INVALID_ADDRESS - Invalid channel + * + */ +BOOL VDDQueryDMA ( + HANDLE hVDD, + WORD iChannel, + PVDD_DMA_INFO pDmaInfo +); + +/*** VDDSetDMA - This service is provided for VDDs to set the DMA data. + * + * INPUT: + * hVDD VDD Handle + * iChannel DMA Channel for which to query + * fDMA Bit Mask indicating which DMA data fields are to be set + * VDD_DMA_ADDR + * VDD_DMA_COUNT + * VDD_DMA_PAGE + * VDD_DMA_STATUS + * VDD_DMA_ALL (all Above) + * Buffer Buffer with DMA data + * + * OUTPUT + * SUCCESS : Returns TRUE + * FAILURE : Returns FALSE + * GetLastError has the extended error information. + * + * NOTES + * + * 1. Extended Error codes: + * + * ERROR_INVALID_ADDRESS - Invalid channel + * + */ +BOOL VDDSetDMA ( + HANDLE hVDD, + WORD iChannel, + WORD fDMA, + PVDD_DMA_INFO pDmaInfo +); + +/** VDDAllocMem - Allocates memory at a given virtual address. + * + * INPUT + * hVDD : VDD + * Address: Address where memory is to be allocated (between 640k and 1Mb) + * nBytes : Number of bytes to allocate + * + * OUTPUT + * SUCCESS : Returns TRUE + * FAILURE : Returns FALSE + * GetLastError has the extended error information. + * Notes: + * 1. VDDs have to use this service instead of WIN32 VirtualAlloc + * to be plateform independent. On non-x86 machines VDDAllocMem + * tells the CPU emulator about this memory allocation. + * + * 2. The address will be made page aligned downwards and nBytes will + * be streched upward to be page aligned. + * + * 3. Extended Error codes: + * ERROR_OUTOFMEMORY - Insufficient memory + * ERROR_INVALID_ADDRESS - Invalid address + */ +VOID VDDAllocMem ( + HANDLE hVDD, + PVOID Address, + ULONG nBytes +); + +/** VDDFreeMem - Free memory at a given virtual address. + * + * INPUT + * hVDD : VDD + * Address: Address where memory is to be freed + * nBytes : Number of bytes to free + * + * OUTPUT + * SUCCESS : Returns TRUE + * FAILURE : Returns FALSE + * GetLastError has the extended error information. + * + * Notes: + * 1. Extended Error codes: + * ERROR_INVALID_ADDRESS - Invalid address + * + * 2. The address will be made page aligned downwards and nBytes will + * be streched upward to be page aligned. + * + */ +VOID VDDFreeMem ( + HANDLE hVDD, + PVOID Address, + ULONG nBytes +); + +/** VDDTerminateVDM - Terminate the VDM. + * + * INPUT + * None + * + * OUTPUT + * None + * + * Notes: + * 1. VDD should call this service on encoutering a fatal error, + * such that VDDs state is inconsistent. + * + * 2. VDD can use MessageBox WIN32 API to putup a popup before + * terminating the VDM. + */ +VOID VDDTerminateVDM ( + VOID +); + +/** Register Manipulation services + * + */ + +ULONG getEAX(VOID); +USHORT getAX(VOID); +UCHAR getAL(VOID); +UCHAR getAH(VOID); +ULONG getEBX(VOID); +USHORT getBX(VOID); +UCHAR getBL(VOID); +UCHAR getBH(VOID); +ULONG getECX(VOID); +USHORT getCX(VOID); +UCHAR getCL(VOID); +UCHAR getCH(VOID); +ULONG getEDX(VOID); +USHORT getDX(VOID); +UCHAR getDL(VOID); +UCHAR getDH(VOID); +ULONG getESP(VOID); +USHORT getSP(VOID); +ULONG getEBP(VOID); +USHORT getBP(VOID); +ULONG getESI(VOID); +USHORT getSI(VOID); +ULONG getEDI(VOID); +USHORT getDI(VOID); +ULONG getEIP(VOID); +USHORT getIP(VOID); +USHORT getCS(VOID); +USHORT getSS(VOID); +USHORT getDS(VOID); +USHORT getES(VOID); +USHORT getFS(VOID); +USHORT getGS(VOID); +ULONG getCF(VOID); +ULONG getPF(VOID); +ULONG getAF(VOID); +ULONG getZF(VOID); +ULONG getSF(VOID); +ULONG getIF(VOID); +ULONG getDF(VOID); +ULONG getOF(VOID); +USHORT getMSW(VOID); + +VOID setEAX(ULONG); +VOID setAX(USHORT); +VOID setAH(UCHAR); +VOID setAL(UCHAR); +VOID setEBX(ULONG); +VOID setBX(USHORT); +VOID setBH(UCHAR); +VOID setBL(UCHAR); +VOID setECX(ULONG); +VOID setCX(USHORT); +VOID setCH(UCHAR); +VOID setCL(UCHAR); +VOID setEDX(ULONG); +VOID setDX(USHORT); +VOID setDH(UCHAR); +VOID setDL(UCHAR); +VOID setESP(ULONG); +VOID setSP(USHORT); +VOID setEBP(ULONG); +VOID setBP(USHORT); +VOID setESI(ULONG); +VOID setSI(USHORT); +VOID setEDI(ULONG); +VOID setDI(USHORT); +VOID setEIP(ULONG); +VOID setIP(USHORT); +VOID setCS(USHORT); +VOID setSS(USHORT); +VOID setDS(USHORT); +VOID setES(USHORT); +VOID setFS(USHORT); +VOID setGS(USHORT); +VOID setCF(ULONG); +VOID setPF(ULONG); +VOID setAF(ULONG); +VOID setZF(ULONG); +VOID setSF(ULONG); +VOID setIF(ULONG); +VOID setDF(ULONG); +VOID setOF(ULONG); +VOID setMSW(USHORT); + +/** GetVDMPointer - Findout the linear address of a given VDM address + * + * INPUT + * Address - seg/sel:off (hi word has segment or selector and loword + * is offset) + * Size - Range of the pointer + * ProtectMode - If protectmode == TRUE its sel:off + * If protectmode == FALSE its seg:off + * + * OUTPUT + * Returns Linear address. + * + * NOTES: + * 1. VDDs should use this service to convert the address rather + * than shifting the seg by 4 and adding the offset. This makes + * them plateform independent. On non-x86 machine VDM's 0 and + * the process's 0 are different and the actual adddress conversion + * is provided by the CPU emulator. + */ +PVOID GetVDMPointer( + ULONG Address, + ULONG Size, + BOOL ProtectedMode +); + +/** FlushVDMPointer - Flushes the contents (required because of emulator) + * + * INPUT + * Address - seg/sel:off (hi word has segment or selector and loword + * is offset) + * Size - Range of the pointer + * Buffer - Address returned by GetVDMPointer. + * ProtectMode - If protecmeode == TRUE its sel:off + * If protecmeode == FALSE its seg:off + * + * OUTPUT + * Returns Linear address. + * + * NOTES: + * 1. VDDs should use this service to make sure that on non-x86 + * machines, the CPU emulator gets a chance to flush any data + * associated with a memory range. + */ +BOOL FlushVDMPointer( + ULONG Addr, + ULONG Size, + PVOID Buffer, + BOOL ProtectedMode +); + +/** FreeVDMPointer - Frees a pointer previously returned by GetVDMPointer + * + * INPUT + * Address - seg/sel:off (hi word has segment or selector and loword + * is offset) + * Size - Range of the pointer + * ProtectMode - If protecmeode == TRUE its sel:off + * If protecmeode == FALSE its seg:off + * + * OUTPUT + * None + * + * NOTES: + * 1. FreeVDMPointer does FlushVDMPointer as well. + */ +BOOL FreeVDMPointer( + ULONG Address, + ULONG Size, + BOOL ProtectedMode +); + +/** VDDSimulateInterrupt - Simulates an interrupt to the VDM. + * + * INPUT + * ms - Is either ICA_MASTER or ICA_SLAVE as appropriate + * line - Interrupt line + * count - allows a batch of interrupts to be delivered but will usually + * be 1. + * + * OUTPUT + * None + */ +VOID VDDSimulateInterrupt ( + BYTE ms, + BYTE line, + WORD count +); |