Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

69KB

Developing Your Own 32-Bit Operating System — Chapter-24: Selected Device Driver Code

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

/go I Part P/ ■ The Operating System Source Code This chapter contains the source code for two MMURTL device drivers. Comments, in addi- tion to those in the code itself, precede sections of the code in order to explain the purpose of an entire section. The code comments should suffice for the detailed explanations of otherwise confusing code. The two device drivers are for the IDE disk drives and the RS232 asynchronous communica- tions device (UARTS). Unlike large portions of the rest of the MMURTL code, many device drivers are written in C. Even some of the ISRs are done in C, although I would rather have done them in assembler. Time was the deciding factor. IDE Disk Device Driver The IDE (Integrated Drive Electronics) device driver was actually one of the easiest drivers to write. All the hardware commands are well documented, and they are also fairly compatible with the MFM hard disk controllers. They were designed that way. In fact, this device driver should work with MFM drives, but I haven't owned any for a couple of years and can't find any to test it. If you have MFM drives, you're on your own, but I think you're OK. One of the things you'll note is that I don't depend on CMOS RAM locations to provide the hard disk drive geometry. I found so many variations in the different ROMs that I gave up and actually read the disk itself to find out. This seems to be the best, and maybe the only, depend- able way to do it. You will find these little #define statements at the top of almost every C source file I have. They simply save some typing and help my brain realize what is signed and unsigned. You'll see that I sometimes slip back to the long-hand notation (unsigned long int, for example), but I try not to. You may also notice that almost everything in MMURTL is unsigned anyway. This eliminates a lot of confusion for me. The CM32 C compiler requires ANSI prototypes for functions. All the operating system calls are prototyped here. There are no include files, so you don't have to go digging to see what I'm doing. I feel this is the best way to try to pass information in a source file to someone; other- wise I would have just included the standard MMURTL header files. See Listing 24.1. Listing 24.1. IDE device driver data and externals.

#define U32 unsigned long #define S32 long tfdefine U16 unsigned int #define S16 int #define U8 unsigned char #define S8 char /* MMURTL OS PROTOTYPES */

Chapter 24 ■ Selected Device Driver Code I /go extern far AllocExch(U32 *pExchRet) ; extern far U32 InitDevDr(U32 dDevNum, S8 *pDCBs, U32 nDevices, U32 dfReplace); extern far U32 UnMaskIRQ(U32 IRQNum) ; extern far U32 MaskIRQ(U32 IRQNum); extern far U32 SetIRQVector(U32 IRQNum, S8 *pIRQ) ; extern far U32 EndOf IRQ(U32 IRQNum); extern far U32 SendMsg(U32 Exch, U32 msgl , U32 msg2); extern far U32 ISendMsg(U32 Exch, U32 msgl , U32 msg2); extern far U32 WaitMsg(U32 Exch, U32 *pMsgRet); extern far U32 CheckMsg(U32 Exch, U32 *pMsgRet); extern far U32 Alarm(U32 Exch, U32 count); extern far U32 KillAlarm(U32 Exch); extern far U32 Sleep(U32 count); extern far void MicroDelay(U32 us15count); extern far void 0utByte(U8 Byte, U16 wPort); extern far void 0utWord(Ul6 Word, U16 wPort); extern far U8 InByte(U16 wPort); extern far U16 InWord(U16 wPort); extern far U8 ReadCM0S(U16 Address); extern far void CopyData(U8 *pSource, U8 *pDestination, U32 dBytes] extern far InWords(U32 dPort, U8 *pDataIn, U32 dBytes); extern far OutWords(U32 dPort, U8 pDataOut, U32 dBytes); While writing the operating system, I needed a method to get data to the screen. The monitor has a function that works very similarly to printf in C. It's called xprintf ( ). Any section of the operating system code included at build time can use this function for displaying trouble- shooting information. You must simply keep in mind that it writes to the video screen for the job that called the device driver. See Listing 24.2. Listing 24.2. Continuation of IDE driver data. / Near External for troubleshooting */ extern long xprintf (char fmt, ...); / LOCAL PROTOTYPES / U32 hdisk_setup(void); static void interrupt hdisk_isr(void) ; / The HD interrupt function */ static U32 hd_format_track(U32 dLBA, U32 dnBlocks); static void hd_reset (void) ; static U32 send_command(U8 parm) ; static U32 hd_wait (void); static U32 check_busy(void) ; static U32 hd_seek(U32 dLBA); static U32 hd_recal(U8 drive); static U32 hd_write(U32 dLBA, U32 dnBlocks, U8 *pDataOut);

H Part PV ■ The Operating System Source Code Listing 24.2. continued static U32 hd_read(U32 dLBA, U32 dnBlocks, U8 *pDataIn); static U32 hd_status(U8 LastCmd); static U32 setupseek(U32 dLBA, U32 nBlks); static U32 hd_init(U8 drive); static U32 ReadSector(U32 Cylinder, U32 HdSect, U8 pDataRet); / The following 3 calls are required in every MMURTL device driver */ static U32 hddev_op(U32 dDevice, U32 dOpNum, U32 dLBA, U32 dnBlocks, U8 *pData); static U32 hddev_stat(U32 dDevice, S8 * pStatRet, U32 dStatusMax, U32 *pdSatusRet); static U32 hddev_init(U32 dDevNum, S8 pInitData, U32 sdlnitData); / LOCAL DEFINITIONS / #define ok 0 / Error Codes to return / #define ErcNoMsg 20 #define ErcNotlnstalled 504 #def ine ErcBadBlock 651 #define ErcAddrMark 652 #define ErcBadECC 653 #def ine ErcSectNotFound 654 #def ine ErcNoDrive0 655 #define ErcNotSupported 656 #define ErcBadHDC 658 #def ine ErcBadSeek 659 #define ErcHDCTimeOut 660 #def ine ErcOverRun 661 #def ine ErcBadLBA 662 #define ErcInvalidDrive 663 #define ErcBadOp 664 #def ine ErcBadRecal 665 #def ine ErcSendHDC 666 #def ine ErcNotReady 667 fldefine ErcBadCmd 668 tfdefine ErcNeedsInit 669 #define ErcTooManyBlks 670 #def ine ErcZeroBlks 671 #def ine ErcWriteFault 672 / The controller can only do 128 max / / 0 Blocks not allowed for this cmd / / WriteFault bit set... bummer */ #define ErcMissHDDInt 675 #define ErcHDDMsgBogus 676

Chapter 24 M Selected Device Driver Code 501 #define ErcHDDIntMsg 677 #define ErcHDDAlarmMsg 678 /* Commands accepted by this HD driver */ #define CmdNull 0 #def ine CmdRead 1 #def ine CmdWrite 2 #define CmdVerify 3 #define CmdFmtBlk 4 #define CmdFmtTrk 5 #define CmdSeekTrk 6 #define CmdSetMedia 7 / #define CmdResetHdw 8 / Not used unless mountable */ Used to reset controller hardware */ CmdReadSect is the only device specific call in the IDE/MFM hard disk device driver. This allows you to read ONE sector specified by Cylinder, head and Sector number. Cylinder is HiWord of dLBA in DeviceOp call, Head is LoWord of dLBA in DeviceOp call, and Sector number is LowWord in dnBlocks. #define CmdReadSect 256 /* only device specific call in HDD */ /* HDC port definitions */ #define HD_PORT 0x1 f0 /* When writing to the port+X (where X =): 16 bit) 0 write data (1F0h 1 pre-comp (1F1h) 2 sector count (1F2h) 3 sector number (1F3h) 4 low cyl (1F4h) 5 high cyl (1F5h) 6 - size/drive/head (1F6h) 7 - command register (1F7h) When reading from the port+X (where X =) 0 - read data (1F0h - 16 bit) 1 - error register ( 1 F1 h) 2 - sector count (1F2h) 3 - sector number (1F3h) 4 - low cyl (1F4h) 5 - high cyl (1F5h) 6 - size/drive/head (1F6h) 7 - status register (1F7h) #define HD_REG_PORT 0x3f6 /* This is a byte wide write only control port that allows reset and defines some special characteristics of the hard drives. Bit Desc 0 Not used 1 Not used

502 | Part PV ■ The Operating System Source Code Listing 24.2. continued 2 Reset Bit - Set, wait 50us, then Reset 3 Mucho Heads Flag. Set = More than 8 heads 4 Not used 5 Not used 6 Disable retries 7 Disable retries (same as six, either one set) /* HDC Status Register Bit Masks (1F7h) #define BUSY 0x80 /* busy., can't talk now! */ #define READY 0x40 /* Drive Ready */ #define WRITE FAULT 0x20 /* Bad news */ #define SEEKOK 0x10 /* Seek Complete */ #define DATA REQ 0X08 /* Sector buffer needs servicing */ #define CORRECTED 0x04 /* ECC corrected data was read */ #define REV INDEX 0x02 /* Set once each disk revolution */ #define ERROR 0X01 /* data address mark not found */ /* HDC Error Regist er Bit Masks (1F1h) */ #define BAD SECTOR 0x80 /* bad block */ #define BAD ECC 0x40 /* bad data ecc */ #define BAD IDMARK 0x10 /* id not found */ #define BAD CMD 0x04 /* aborted command */ #define BAD SEEK 0x02 /* trk 0 not found on recalibrate, #define BAD ADDRESS 0X01 /* data address mark not found */ or bad seek */ /* HDC internal command bytes ( HDC_Cmd [ 7 ] ) #define HDC RECAL 0X10 * 0001 0000 */ #define HDC READ 0x20

  • 0010 0000 */ #define HDC READ LONG 0x22
  • 0010 0010 */ #define HDC WRITE 0x30

0011 0000 */ #def ine HDC WRITE LONG 0x32 * 0011 0010 */ #def ine HDC VERIFY 0x40

  • 0100 0000 */ #define HDC FORMAT 0x50
  • 0101 0000 */ #def ine HDC SEEK 0x70

0111 0000 */ #def ine HDC DIAG 0x90

  • 1001 0000 */ #def ine HDC SET PARAMS 0x91
  • 1001 0001 / You may notice that I don't initialize any variables in structures because that would place them in a different location in the data segment. If I did one member, I would have to do them all to ensure they would be contiguous. The operating system requires the DCB and status record fields to be contiguous in memory. See Listing 24.3. Listing 24.3. Continuation of IDE driver data. /•LOCAL DATA static U8 hd_Cmd[8]; / For all 8 command bytes */

Chapter 24 ■ Selected Device Driver Code static U8 static U8 fDataReq: statbyte; Flag to indicate is fDataRequest is active */ From HDC status register last time it was read / static U8 hd_control; static U8 hd_command; static U8 hddrive; static U8 hdhead; static U8 hd_nsectors; static U8 hd sector; / Current control byte value / / Current Command / / Current Physical Drive, 0 or 1 */ Calculated from LBA • which head */ Calculated from LBA - n sectors to read/write / / Calculated from LBA - Starting sector / / Current type drive 0 & 1 found in CMOS or Set by caller. / / Current number of heads, cylinders, and sectors set by caller */ static U8 hd0_type; static U8 hd0_heads; static U8 hd0_secpertrk; static U16 hd0_cyls; static U8 hdl_type; static U8 hd1_heads; static U8 hd1_secpertrk; static U16 hd1_cyls; #define sStatus 64 static struct statstruct { U32 ere; U32 blocks_done; U32 BlocksMax; U8 fNewMedia; U8 type_now; /( U8 resvd0[2]; /’ U32 nCyl; /’ U32 nHead; /’ U32 nSectors; /’ U32 nBPS; current fdisk_table for drive selected */ padding for DWord align */ total physical cylinders / total heads on device / Sectors per track / / Number of bytes per sect. 32 bytes out to here./ U32 LastRecalErc0; U32 LastSeekErc0; U8 LastStatByte0; U8 LastErcByte0; U8 flntOnReset; / Interrupt was received on HDC_RESET / U8 filler0; U32 LastRecalErd ; U32 LastSeekErd ; U8 LastStatBytel ; U8 LastErcBytel ; U8 ResetStatByte U8 filleM; U32 resvd1[2]; }; / Status Byte immediately after RESET / / out to 64 bytes */ static struct statstruct hdstatus; static struct statstruct HDStatTmp;

[504 Part PV ■ The Operating System Source Code Listing 24.3. continued static struct dcbtype { S8 Name[12] ; S8 sbName; S8 type; S16 nBPB; U32 last ere; U32 nBlocks; S8 *pDevOp; S8 pDevInit; S8 pDevSt; U8 fDevReent; U8 fSingleUser; S16 wJob; U32 OS1 U32 OS2 U32 OS3 U32 0S4 U32 0S5 U32 0S6 }; static struct dcbtype hdcb[2]; / two HD device control blocks / / Exch and msgs space for HD ISR / static U32 hd_exch; static U32 hdjnsg; static U32 hd_msg2; static long HDDInt; The hdisk_setup( ) function is called from the monitor to initialize the driver. In a loadable driver, this would be the main( ) section of the C source file. See Listing 24.4. Listing 24.4. IDE device driver code. U32 hdisk_setup(void) { U32 ere; / first we set up the 2 DCBs in anticipation of calling InitDevDr / hdeb[0].Name[0] hdcb[0].Name[l] hdcb[0] .Name[2] hdcb[0] .sbName hdcb(0] hdcb[0] type nBPB hdcb[0] .nBlocks hdcb[0] .pDevOp hdcb[0] .pDevInit “H ; ‘D’ ; ‘0’ ; 3; 1; /’ 512; 524288; I &hddev_op; &hddev init; Random / largest disk handled - 2Gb disks/

Chapter 24 M Selected Device Driver Code S hdcb[0].pDevSt = &hddev_stat; hdcb[1].Name[0] = ‘H’ ; hdcb[1].Name[1] = ‘D’ ; hdcb[1].Name[2] = ‘1 ' ; hdcb[1 ] .sbName = 3; hdcb[1 ] .type = 1; /* Random hdcb[1].nBPB = 512; hdcb[1 ] .nBlocks = 524288; /* largest de< hdcb[1 ] .pDevOp = &hddev_op; hdcb[1 ] .pDevInit = &hddev_init; hdcb[1].pDevSt = &hddev_stat; 2Gb disks*/ /* These are defaulted to non zero values to ensure we don't get a divide by zero during initial calculations on the first read. hd0_type = ReadCMOS(0x19) ; /* read this but don't use it */ hd0_heads =16; /* Max */ hd0_secpertrk = 17; /* most common */ hd0_cyls = 1024; /* Max */ hdl_type = ReadCMOS(0xlA) hdl_heads = 16; hd1_secpertrk = 17; hd1_cyls = 1024; ere = AllocExch(&hd_exch) ; /* Exhange for HD Task to use */ SetIRQVector(14, &hdisk_isr); UnMaskIRQ(14); /* Documentation lists the fixed disk types at CMOS 1 1 h and 12h, and also shows them at 19h and 1Ah. We don't actually read them because they are not dependable. They vary from BIOS to BIOS. We have to make this sucker work the hard way. */ /* Reset the HDC - hd_reset resets the controller (which controls both drives). We have to do it once, then try both physical drives, If the second drive is not there, some controllers will lock-up (the el-cheapos). In this case we have to reset it again so it will work. It seems like a lot of work, but to make it function with the widest range of IDE and MFM controllers this is the only way I have found that works. hd_reset() ; /* no error is returned */ /* Now we attempt to select and recal both drives. The driver MUST be able to recal the first physical drive or the Driver won't inititalize. */ ere = hd_recal(0) ; if (ere) { /* try to recal */ try one more time! */

cQf Part PV ■ The Operating System Source Code Listing 24.4. continued hd_reset() ; ere = hd_recal(0); /* try to recal */ if (ere) { hdcb[0] .last_erc = ere; hd0_type = 0; /* Must not be a valid drive */ return(EreNoDrive0) ; } } /* if we got here, drive 0 looks OK and the controller is functioning. Now we try drive 1 if type > 0. */ if (hdl_type) { ere = hd_recal(l); /* try to recal if CMOS says it's there */ if (ere) { hdcb[1 ] .last_erc = ere; hd1_type = 0; /* Guess it's not a valid drive */ if (!erc) /* We must redo drive 0 cause some cheap controllers lockup on us if drive 1 is not there. They SHOULD simply return a Bad Command bit set in the Error register, but they don't. */ hd_reset( ) ; ere = hd_recal(0); /* recal drive 0 */ hdcb[0] .last_erc = ere; } } return(erc = InitDevDr(l2, &hdcb, 2, 1)); } /A*********************************************************** Reset the HD controller. This should only be called by Devicelnit or hdisk_setup. This resets the controller and reloads parameters for both drives (if present) and attempts to recal them. static void hd_reset (void) { U32 i; UnMaskIRQ(14); /* enable the IRQ */ 0utByte(4, HD_REG_PORT) ; /* reset the controller */ MicroDelay(4) ; /* Delay 60us */ /* bit 3 of HDREG must be 1 for access to heads 8-15 */ /* Clear “MUCHO” heads bit, and clear the reset bit */ OutByte(hd_control & 0x0f, HD_REG_PORT)

Chapter 24 ■ Selected Device Driver Code H Sleep(20); /* 200ms - seems some controllers are SLOW!! */ i = CheckMsg(hd_exch, &hd_msg); /* Eat Int if one came back */ hdstatus.ResetStatByte = statbyte; /* The ISR gets statbyte */ if (i) hdstatus.flntOnReset = 1; else hdstatus.flntOnReset = 0; } The ISR is VERY simple. It just waits for an interrupt, gets the single status byte from the controller (which clears the interrupt condition) then sends an empty message to the exchange where the HD Driver task will be waiting. This tells the HD task currently running that it's got some status to act on! static void interrupt hdiskisr(void) { statbyte = InByte(HD_P0RT+7) ; HDDInt = 1 ; ISendMsg(hd_exch, 0xfffffff0, 0xfffffff0); End0fIRQ(14); } This checks the HDC controller to see if it's busy so we can send it commands or read the rest of the registers. We will wait up to 3 seconds then error out. The caller should call checkbusy and check the error. If it's 0 then the controller became ready in less than 3 seconds. ErcNotReady will be returned otherwise. It leaves the status byte in the global statbyte. static U32 check_busy(void) { S16 count; count = 0; while (count++ < 60) { statbyte = InByte(HD_P0RT+7) ; if ((statbyte & BUSY) == 0) return(ok); Sleep(5); /* 50ms shots */ } return(ErcNotReady) ; /* controller out to lunch! */ } This sends the SetParams command to the controller to set up the drive geometry (nHeads, nSectors, etc.). static U32 hd_init(U8 drive) {

Part PV ■ The Operating System Source Code Listing 24.4. continued

U32 ere; /* set max heads, sectors and cylinders */ if (drive == 0) { /* Drive 0 */ hd_Cmd[2] = hd0_secpertrk; /* sector count */ hd_Cmd[6] = (drive « 4) J ( (hd0_heads-l ) & 0x0f) | 0xa0; /* hds & drv */ } else { /* Drive 1 */ hd_Cmd[2] = hd1_secpertrk; /* sector count */ hd_Cmd[6] = (drive « 4) | ( (hd1_heads-1 ) & 0x0f) | 0xa0; /* hds & drv */ } hd_Cmd[1] = 0; hd_Cmd[3] = 0; hd_Cmd[4] = 0; /* cyl = 0 for init */ hd_Cmd[5] = 0; /* cyl = 0 for init */ ere = send_command(HDC_SET_PARAMS) ; /* Send the command */ ere = hd_wait(); /* wait for interrupt */ if (!erc) ere = hd_status(HDC_SET_PARAMS) ; return(erc) ; } The hd_wait ( ) function has a good example of using the Alarm ( ) function. You expect the hard disk controller to come back in a short period of time by sending you a message from the ISR. You also set the Alarm ( ) function so you get a message at that exchange, even if the controller goes into Never-Never Land. See Listing 24.5. Listing 24.5. Continuation of IDE driver code. Wait for the hardware interrupt to occur. Time-out and return if no interrupt. ...**....***..**..*.*..*....*.*****.********/ static U32 hd_wait(void) { U32 ere; /* Set alarm for 3 seconds */ HDDInt = 0; KillAlarm(hd_exch) ; /* kill any pending alarm */ ere = Alarm(hd_exch, 300); /* Set it up again */ if (ere) return(erc); /* bad problem */ ere = WaitMsg(hd_exch, &hd_msg); KillAlarm(hd_exch);

Chapter 24 ■ Selected Device Driver Code 509 if (hdjnsg != 0xfffffff0) { /* HD interrupt sends fffffffO */ if (HDDInt) return(ErcMissHDDInt) ; else return ( ErcHDCTimeOut ) ; /* Alarm sends Oxffffffff } else { KillAlarm(hd_exch); return(ok) ; } Recalibrate the drive. r********************** static U32 hd_recal(U8 drive) { U32 ere; hd_Cmd[6] = (drive « 4) j (hd_head & 0x0f) | 0xa0; ere = send_command(HDC_RECAL) ; if (!erc) ere = hd_wait(); /* wait for interrupt */ if (!erc) ere = hd_status(HDC_RECAL); if (drive) hdstatus.LastRecalErd = ere; else ndstatus.LastRecalErc0 = ere; return(erc) ; } Send the command to the controller. Clear the Exchange of any left over alarm or int messages before we send a command. static U32 send_command(U8 Cmd) { U32 ere, msg[2] ; while (CheckMsg(hd_exch, &msg) == 0); /* Empty it */ /* bit 3 of HD_REG must be 1 for access to heads 8-15 */ if (hdjiead > 7) { hd_control = 0x08; OutByte(hd_control, HD_REG_PORT) ; /* set bit for head > 7 */ hd_control &= 0xf7; } ere = check_busy() ; if (!erc) 0utByte(hd_Cmd[1], HDPORT+1);

I
510 Part PV ■ The Operating System Source Code Listing 24.5. continued if (!erc) ere = check busy() if (!erc) 0utByte(hd_Cmd[2], HD_P0RT+2 if (!erc) ere = check busy() if (!erc) 0utByte(hd_Cmd[3], HD_P0RT+3 if (!erc) ere = check_busy() if (!erc) 0utByte(hd_Cmd[4] , HD_P0RT+4 if (!erc) ere = check_busy() if (!erc) 0utByte(hd_Cmd[5] , HD_P0RT+5 if (!erc) ere = check busy() if (!erc) 0utByte(hd_Cmd[6], HDPORT+6 if (!erc) ere = check busy() if (!erc) OutByte(Cmd, HD_P0RT+7); return(erc) ; This sets up the cylinder, head and sector variables for all commands that require them (read, write, verify, format, seek) nBlks CANNOT be greater than the hardware can handle. For IDE/MFM controllers this is 128 sectors. The calculated values are placed in the proper command byte in anticipation of the command being sent. static U32 setupseek(U32 dLBA, U32 nBlks) { U32 j; U16 cyl; if (nBlks > 256) return ErcTooManyBlks; if (nBlks == 0) return ErcZeroBlks; hd_nsectors = nBlks; if (hd_nsectors == 256) hd_nsectors = 0 0==256 for controller / if (hd_drive == 0) { / drive 0 / cyl = dLBA / (hd0_heads * hd0_secpertrk) ; j = dLBA % (hd0_heads * hd0_secpertrk) ; / remainder / / we now know what cylinder, calculate head and sector */ hd_head = j / hd0_secpertrk; hd_sector = j % hd0_secpertrk + 1 sector number start at 1 111 */ } else { drive 1 / cyl = dLBA / (hd1_heads * hd1_secpertrk) ; j = dLBA % (hdl_heads * hdl_secpertrk) ; / remainder / / We now know what cylinder. Calculate head and sector */ hdhead = j / hd1_secpertrk;

Chapter 24 ■ Selected Device Driver ( Zode J3 hd_sector = j % hd1_secpertrk + 1; /* sector number start at 1 I!! hd_Cmd[2] = nBlks; hd_Cmd[3] = hd_sector; hd_Cmd[4] = cyl & 0xff ; hd_Cmd[5] = (cyl » 8) & 0xff hd_Cmd[6] = (hd_drive « 4) | /* How many sectors */ /* Which sector to start on */ /* cylinder lobyte */ /* cylinder hibyte */ (hd_head & 0x0f) \ 0xa0; Move the head to the selected track (cylinder). static U32 { U32 ere; hd_seek(U32 dLBA) ere = setupseek(dLBA, 1); if (!erc) ere = send_command(HDC_SEEK) ; if (!erc) ere = hd_wait(); if (!erc) ere = hd_status(HDC_SEEK) ; hdstatus.LastSeekErc0 = ere; return(erc) ; /* sets up for implied seek */ /* Not implied anymore... */ /* wait for interrupt */ Called to read status and errors from the controller after an interrupt generated by a command we sent. The error checking is based on the command that we sent. This is done because certain bits in the status and error registers are actually not errors, but simply indicate status or indicate an action we must take next. ZERO returned indicates no errors for the command status we are checking. static U32 hd_status(U8 LastCmd) { U32 ere; U8 statbyte, errbyte; /* We shouldn't see the controller busy. After all, he interrupted us with status. ere = check_busy( ) ; if (!erc) statbyte = InByte(HD_P0RT+7) else return(erc) ; /* puts status byte into global StatByte */ if (hd_drive) hdstatus.LastStatBytel = statbyte;

0 Part PV ■ The Operating System Source Code Listing 24.5. continued else hdstatus.LastStatByte0 = statbyte; if ((statbyte & ERROR) == 0) { /* Error bit not set in status reg */ ere = ok; /* default */ switch (LastCmd) { case HDC_READ: case HDC_READ_LONG: case HDC_WRITE: case HDC_WRITE_LONG: case HDC_SEEK: case HDC_RECAL: if (statbyte & WRITE_FAULT) ere = ErcWriteFault; else if ((statbyte & SEEKOK) == 0) ere = ErcBadSeek; break; case HDC_SET_PARAMS : case HDC_VERIFY: case HDC_FORMAT: case HDC_DIAG: break; default: break; } return(erc) ; } else { ere = check_busy( ) ; if (!erc) errbyte = InByte(HD_P0RT+1 ) ; else return(erc) ; if (hd_drive) hdstatus.LastErcBytel = errbyte; else hdstatus.LastErcByte0 = errbyte; if (errbyte & BAD_ADDRESS) ere = ErcAddrMark; else if (errbyte & BAD_SEEK) ere = ErcBadSeek; else if (errbyte & BAD_CMD) ere = ErcBadCmd; else if (errbyte & BAD_IDMARK) ere = ErcSectNotFound; else if (errbyte & BAD_ECC) ere = ErcBadECC; else if (errbyte & BAD_SECTOR) ere = ErcBadBlock; else ere = ErcBadHDC; /* no error bits found but should have been! } return ere; } This is called for the DeviceOp code Read. This reads 1 or more whole sectors from the calculated values in hd_head, hdsector, and hd_cyl

Chapter 24 ■ Selected Device Driver Code static U32 hd_read(U32 dLBA, U32 dnBlocks, U8 pDataRet) { U32 ere, nleft, nBPS; nBPS = hdcb[hd_drive] .nBPB; / From nBytesPerBlock in DCB / nleft = dnBlocks; ere = setupseek(dLBA, dnBlocks); / sets up for implied seek / if (!erc) ere = send_command(HDC_READ) ; while ((nleft) && (!erc)) { ere = hd_wait(); / wait for interrupt / if (!erc^ ere = hd_status(HDC_READ); if (!erc) / && (statbyte & DATA_REQ)) */ { InWords(HD_PORT, pDataRet, nBPS); pDataRet+=nBPS; -nleft; } } return(erc) ; } This is called for the DeviceOp code Write. This writes 1 or more whole sectors from the calculated values in hd_head, hdsector, and hd_cyl static U32 hd_write(U32 dLBA, U32 dnBlocks, U8 pDataOut) { U32 ere, nSoFar, nBPS; nBPS = hdcb[hd_drive] .nBPB; / From n BytesPerBlock in DCB / nSoFar = 0; ere = setupseek(dLBA, dnBlocks); / sets up for implied seek / ere = send_command(HDC_WRITE) ; ere = check_busy() ; / No INT occurs for first sector of write / if ((!erc) && (statbyte & DATA_REQ)) { OutWords(HD_PORT, pDataOut, nBPS); pDataOut+=nBPS; nSoFar++; } while ((nSoFar < dnBlocks ) && (erc==ok)) { ere = hd_wait(); / wait for interrupt */ if (erc==ok) ere = hd_status(HDC_WRlTE) ; if ((erc==ok) && (statbyte & DATA_REQ)) { OutWords(HD_PORT, pDataOut, nBPS); pDataOut+=nBPS;

|314 Part TV ■ The Operating System Source Code Listing 24.5. continued nSoFarn (!erc) ere (!erc) ere hd_wait ( ) ; hd_status(HDC_WRITE) wait for final interrupt / return(erc) ; This formats the track beginning at the block address given in dLBA. dLBA must always be a multiple of the number of sectors per track minus 1 for the disk type. static U32 hd_format_track(U32 dLBA, U32 dnBlocks) { U32 ere; ere = setupseek(dLBA, dnBlocks); ere = send_command(HDC_FORMAT) ; ere = hd_wait() ; if (erc==ok) ere = hd_status(HDC_FORMAT) ; return(erc) ; } / sets up for implied seek / / wait for interrupt / ReadSector is the only device specific call in the IDE/MFM hard disk device driver. This allows you to read ONE sector specified by Cylinder, head and Sector number. Cylinder is LoWord of dLBA in DeviceOp call, Head is LoWord of dnBlocks in DeviceOp call, and Sector number is HiWord in dnBlocks. static U32 ReadSector(U32 Cylinder, { U32 ere; U16 cyl; cyl = Cylinder; hd_head = HdSect & 0xffff; hd_sector = (HdSect » 16) & Oxffff; U32 HdSect, U8 pDataRet) / For testing xprintf ("\r\nCYL %d, HD %d, / SEC%d\r\n”, cyl, hd_head, hd_sector); hd_Cmd[2] hd_Cmd[3] hd_Cmd[4] hd_Cmd|5] hd_Cmd[6l = 1 ; = hdsector; = cyl & 0xff; = (cyl » 8) & 0xff; / How many sectors / / Which sector to start on / / cylinder lobyte / / cylinder hibyte •/ (hd_drive « 4) ; (hd_head & 0x0f) ; 0xa0; ere ere send_command(HDC_READ) ; hd_wait( ) ; / wait for interrupt

Chapter 24 ■ Selected Device Driver Code H if (!erc) ere = hd_status(HDC_READ) ; if (!erc) InWords(HD_PORT, pDataRet, 512); return(erc) ; } Chapter 8, “Programming Interfaces,” discussed device driver interfaces; there I provided an overview of MMURTL's device driver interface. The three calls that all MMURTL device drivers must provide are next. See Listing 24.6. Listing 24.6. Continuation of IDE driver code. Called for all device operations. This assigns physical device from logical number that outside callers use. For Hard disk, 12=0 and 13=1. This will check to make sure a drive type is assigned and check to see if they are going to exceed max logical blocks. static U32 hddev_op(U32 dDevice, U32 dOpNum, U32 dLBA, U32 dnBlocks, U8 pData) { U32 ere; hdstatus.blocks_done = 0; / Reset values in Status record / ere = 0; / Set drive internal drive number / if (dDevice == 12) hd_drive = 0; else hd_drive = 1 ; / Check to see if we have a leftover interrupt message from last command. If so then we eat it (and do nothing) / CheckMsg(hd_exch, &hd_msg); / Ignore error */ if (hd_drive==0) { if (hd0_type==0) ere = ErcInvalidDrive; } else { if (hd1_type==0) ere = ErcInvalidDrive;

[316 Part JV ■ The Operating System Source Code Listing 24.6. continued /* make sure they don't exceed max blocks */ if (!erc) if (dLBA > hdcb[hd_drive] .nBlocks) ere = ErcBadLBA; if (!erc) { switch (dOpNum) { /* Null Command */ case(CmdNull) : ere = ok; break; case(CmdRead) : ere = hd_read(dLBA, dnBlocks, pData); break; case(CmdWrite) : ere = hd_write(dLBA, dnBlocks, pData); break; case(CmdVerify) : ere = ErcNotSupported; /* Read */ /* Write */ /* Verify */ /* hd_verify is not supported in this version of the driver */ /* ere = hd_verify(dLBA, dnBlocks, pData); */ break; case(CmdSeekTrk) : ere = hd_seek(dLBA) ; break; case(CmdFmtTrk) : ere = hd_format_track(dl_BA, dnBlocks); break; case(CmdResetHdw) : hd_reset() ; ere = 0; break; case(CmdReadSect) : ere = ReadSector(dLBA, dnBlocks, pData); break; default: ere = ErcBadOp; break; Seek Track */ /* Format Track */ Reset Ctrlr */ /* Read Sector(s) */ hdcb[hd_drive] .last_erc = ere; return(erc) ; /* update DCB ere Called for indepth status report on ctrlr and drive specified. Returns 64 byte block of data including current drive geometery. This is called by the PUBLIC call DeviceStat!

Chapter 24 ■ Selected Device Driver Code static U32 hddev_stat(U32 dDevice, S8 * pStatRet, U32 dStatusMax, U32 pdStatusRet) { U32 i; / Set status for proper device / if (dDevice == 12) { hdstatus.erc = hdcb[0] .last_erc; hdstatus.typenow = hdOtype; hdstatus.nCyl = hd0_cyls; hdstatus.nHead = hdOheads; hdstatus.nSectors = hd0_secpertrk; hdstatus.nBPS = hdcb[0] .nBPB; } else { hdstatus.erc = hdcb[1 ] .last_erc; hdstatus.type_now = hd1_type; hdstatus.nCyl = hd1_cyls; hdstatus.nHead = hd1_heads; hdstatus.nSectors = hd1_secpertrk; hdstatus.nBPS = hdcb[1 ] .nBPB; / Calculate size of status to return. Return no more than asked for! / if (dStatusMax <= sStatus) i = dStatusMax; else i = sStatus; CopyData(&hdstatus, pStatRet, i); / copy to their status block */ pdStatusRet = i; / tell em how much it was */ return ok; } Called to reset the hard disk controller and set drive parameters. The Initdata is a copy of the 64 byte status block that is read from status. The caller normally reads the block (DeviceStat) , makes changes to certain fields and calls Devicelnit pointing to the block for the changes to take effect. This should ONLY be called once for each HD to set its parameters before it is used the first time after the driver is loaded, or after a fatal error is received that indicates the controller may need to be reset (multiple timeouts etc.). The DCB values are updated if this is successful.

I
518 Part IV The Operating System Source Code Listing 24.6. continued This is called by the PUBLIC call Devicelnit static U32 hddev_init(U32 dDevice, S8 pInitData, U32 sdlnitData) { U32 ere, i; ere = 0; / Read the init status block in / if (sdlnitData > sStatus) i = sStatus; else i = sdlnitData; CopyData(pInitData, &HDStatTmp, i); / Set internal drive number / if (dDevice == 12) hd_drive=0; else hd_drive = 1 ; if (hd_drive==0) { hd0_type = HDStatTmp.typenow; if (hd0_type) { hd0_cyls = HDStatTmp.nCyl; hd0_heads = HDStatTmp.nHead; hd0_secpertrk = HDStatTmp.nSectors; } else ere = ErcInvalidDrive; } else { hd1_type = HDStatTmp.type_now; if (hd1_type) { hd1_cyls = HDStatTmp.nCyl; hdl_heads = HDStatTmp.nHead; hd1_secpertrk = HDStatTmp.nSectors; } else ere = ErcInvalidDrive; / no more than 64 bytes! / / copy in their init data / / If no error, initialize it and recal / if (!erc) ere = hd_init (hd_drive) ; if (!erc) ere = hd_recal(hd_drive) ; / If no error, update corresponding DCB values */ if (lerc) { hdcb[hd_drive] .nBPB = HDStatTmp.nBPS;

Chapter 24 ■ Selected Device Driver Code hdcb[hd_drive] . lasterc = 0; hdcb[hd_drive] .nBlocks = HDStatTmp.nCyl * HDStatTmp.nSectors * HDStatTmp.nHead; } hdcb[hd_drive] .last_erc = ere; /* update DCB ere */ return(erc) ; The RS-232 Asynchronous Device Driver The RS-232 driver is designed to drive two channels with 8250, 16450, or 16550 UARTs. It needs more work, such as complete flow control for XON/XOFF and CTS/RTS; but other than that, it is a fully functional driver. You may notice the differences between the IDE driver, which is a block-oriented random device, and this driver, which is a sequential byte-oriented device. They are two different animals with the same basic interface. Unlike the IDE disk device driver, a header file is included in the RS232 driver source file. This header file was also designed to be used by programs that use the driver, as well as by the driver itself. This same header file is used in the sample communications program in Chapter 16, “MMURTL Sample Software.” RS232.H The header file defines certain parameters to functions, as well as explains all the error or status codes that the driver will return. The C structure for the status record is defined here and will be needed to initialize, as well as status, the driver. See Listing 24.7. Listing 24.7. RS-232.h header file. Recv Buffer Empty */ Xmit Buffer never Emptied */ Receive buffer overrun */ Invalid port on OpenCommC */ Buffer full!! ! */ Channel not open. . . */ #def ine MIN BAUD 1501 #define MAX BAUD 384001 #define NO PAR 0 #def ine EV PAR 1 #define 0D_PAR 2 #define ErcRecvTimeout 800 / #define ErcXmitTimeout 801 / #define ErcRcvBufOvr 802 / #def ine ErcBadPort 803 / #define ErcRcvBufOvr 805 / #define ErcNotOpen 807 /

H Part TV ■ The Operating System Source Code Listing 24.7. continued #define #define #define #define #define #define #define #define #define ErcChannelOpen ErcNotOwner ErcBadBaud ErcBadParity ErcBadDataBits ErcBadStopBits ErcBadlOBase ErcBadCommlRQ ErcBadlnitSize 812 820 821 822 823 824 825 827 It's already open. . . / It's opened by someone else. 150-38400 0, 1 or Must be Must be if 0 < 3 2 5-8 1 or 2 / At least 40 bytes for this version */ struct statRecC{ unsigned long unsigned long unsigned long unsigned long unsigned char pa unsigned char unsigned char unsigned char unsigned long unsigned long unsigned long unsigned long unsigned long unsigned long }; commJob; LastErc; LastTotal; Baudrate; rity; databits; stopbits; IRQNum; IOBase; XBufSize; RBufSize; XTimeOut; RTimeOut; resvd[6] ;

  • Owner of this comms port, 0 for not
  • Result of last device operation */
  • Total bytes moved in last operation
  • Baudrate for this port, 150 - 38400 Parity for this port, 0=none, l=even,
  • nDatabits for this port, 5-8 */
  • stop bits for this port, 1 or 2 */
  • IRQNum for this channel */
  • 10 base address for hardware */
  • Size of Xmit buffer */
  • Size of Recv Buffer */
  • Xmit Timeout in 10ms increments */
  • Recv Timeout in 10ms increments */
  • out to 64 bytes / 2=odd / Device Driver interface commands (Op numbers) #define #def ine #def ine #def ine #def ine #define #define #def ine #def ine #def ine #def ine #define #define #def ine #def ine #def ine #def ine #def ine CmdReadRec 1 CmdWriteRec 2 CmdOpenC 10 CmdCloseC 11 CmdDiscardRcv 12 CmdSetRTO 13 CmdSetXTO 14 CmdSetDTR 15 CmdSetRTS 16 CmdReSetDTR 17 CmdReSetRTS 18 CmdBreak 19 CmdGetDC CmdGetDSR CmdGetCTS CmdGetRI CmdReadB CmdWriteB /* Read one or more bytes */ /* Write one or more bytes */ /* Open Comm Channel */ /* Close Comm Channel */ /* Trash input buffer */ /* Set Recv timeout 10ms incs in dLBA */ /* Set Xmit timeout 10ms incs in dLBA */ /* Set DTR (On) */ /* Set CTS (On) */ /* Set DTR (On) */ /* Set CTS (On) */ /* Send BREAK (10ms incs in dLBA) */ /* Returns byte TRUE to pData if CD ON */ /* Returns byte TRUE to pData if DSR ON */ /* Returns byte TRUE to pData if CTS ON */ /* Returns byte TRUE to pData if RI ON */ /* Recv a single byte */ /* Xmit a single byte */ The Device Driver, RS-232.C In the following code, you again will notice the shorthand for the rather lengthy C-type decla- rations. Further down in the file you will notice that commands are defined 1 through 32.

Chapter 24 ■ Selected Device Driver Code The only commands that are shared with the default device driver commands are for reading and writing blocks of data. These are commands 1 and 2. All other commands are specific to this driver. See Listing 24.8. 0 Listing 24.8. RS-232 device driver code. #define U32 unsigned long #define S32 long #define U16 unsigned int #define S16 int #define U8 unsigned char #define S8 char #define TRUE 1 #define FALSE 0 #include “RS232.h” /* MMURTL OS Prototypes */ extern far U32 AllocExch(U32 *pExchRet); extern far U32 InitDevDr(U32 dDevNum, S8 *pDCBs, U32 nDevices, U32 df Replace); extern far U32 AllocOSPage(U32 nPages, U8 **ppMemRet) ; extern far U32 DeAllocPage(U8 *pOrigMem, U32 nPages); extern far U32 UnMaskIRQ(U32 IRQNum) ; extern far U32 MaskIRQ(U32 IRQNum); extern far U32 SetIRQVector(U32 IRQNum, S8 *pIRQ) ; extern far U32 End0fIRQ(U32 IRQNum); extern far U32 SendMsg(U32 Exch, U32 msgl , U32 msg2); extern far U32 ISendMsg(U32 Exch, U32 msgl , U32 msg2); extern far U32 WaitMsg(U32 Exch, S8 *pMsgRet); extern far U32 CheckMsg(U32 Exch, S8 *pMsgRet); extern far U32 GetTimerTick(U32 *pTickRet); extern far U32 Alarm(U32 Exch, U32 count); extern far U32 KillAlarm(U32 Exch); extern far U32 Sleep(U32 count); extern far void MicroDelay(U32 uslScount); extern far void 0utByte(U8 Byte, U16 wPort); extern far U8 InByte(U16 wPort); extern far void CopyData(U8 *pSource, U8 *pDestination, U32 dBytes); extern far long GetJobNum(long *pJobNumRet) ; /* local prototypes. These will be called from the device driver interface. static U32 comdev_stat(U32 dDevice, S8 *pStatRet, U32 dStatusMax, U32 *pdStatusRet); static S32 comdev_init(U32 dDevice,

I
522 Part TV ■ The Operating System Source Code Listing 24.8. continued S8 pInitData, U32 sdlnitData) ; static U32 comdev op(U32 dDevice U32 dOpNum, U32 dLBA, U32 dnBlocks, U8 pData) ; #define CmdReadRec 1 #define CmdWriteRec 2 / Read one or more bytes / / Write one or more bytes / #define CmdOpenC 10 #define CmdCloseC 11 #define CmdDiscardRcv 12 #define CmdSetRTO 13 #define CmdSetXTO 14 #define CmdSetDTR 15 #define CmdSetRTS 16 #define CmdReSetDTR 17 #define CmdReSetRTS 18 #define CmdBreak 19 #define CmdGetDC 20 tfdefine CmdGetDSR 21 #define CmdGetCTS 22 #def ine CmdGetRI 23 / Open Comm Channel / / Close Comm Channel / / Set DTR (On) / / Set CTS (On) / / Set DTR (On) / / Set CTS (On) / #define CmdReadB 31 #define CmdWriteB 32 / The following definitions are used to identify, and reset signal line condition and functions. set #define CTS 0x10 / Clear To Send / #define DSR 0x20 / Data Set Ready / #define RI 0x40 / Ring Indicator / #define CD 0x80 / Carrier Detect / #define DTR 0x01 / Data Terminal Ready / #define RTS 0x02 / Request To Send / #define 0UT2 0x08 / Not used / / Values from IIR register * / #define MDMSTAT 0X00 #define NOINT 0X01 #define TXEMPTY 0x02 ^define RVCDATA 0x04 tfdefine RCVSTAT 0x06 /* For errors returned from Comms calls see RS-232.h */ ^define SSENDBUF 4096 #define SRECVBUF 4096 /* 1 Page Send Buf Default */ /* 1 Page Recv Buf Default */

Chapter 24 ■ Selected Device Driver Code H static U32 recv_timeout[2] = 1; static U32 xmit_timeout(2] = 10; /* variables for ISRs */ static U8 f16550[2]; static U8 stat_byte[2] ; static U8 mstat_byte[2] ; static U8 int_id[2]; static U8 fExpect!nt[2]; static U32 recv_error[2] static U8 static U32 static U32 static U32 *pSendBuf[2] head_send[2] tail_send[2] cSendBuf[2]; static U32 sSendBuf[2]; static U8 *pRecvBuf[2] static U32 head_recv[2] static U32 tail_recv[2] static U32 cRecvBuf [2]; static U32 sRecvBuf[2]; /* 10ms intervals */ /* 10ms intervals */ /* NON-ZERO = an error has occurred */ /* pointers to Xmit bufs */ /* Next char to send */ /* Where next char goes in buf */ /* Count of bytes in buf */ /* Size of buffer (allocated) */ /* pointers to Recv bufs */ /* Next char from chip */ /* Next char to read for caller */ /* Count of bytes in buf */ /* Size of buffer (allocated) */ static U8 control_byte[2]

/* array of registers from po static U16 THR[2] /* static U16 IER[2] /* static U16 IIR[2] /* static U16 FCR[2] /* static U16 LCR[2] /* static U16 MCR[2] /* static U16 LSR[2] /* static U16 MSR[2] /* static U16 DLAB L0[2]; /* static U16 DLAB H t[2]; Transmitter Holding Register */ Interrupt Enable Register */ Interrupt Id Register */ FIFO control for 16550 */ Line Control Register */ Modem Control Register */ Line Status Register */ Modem Status Register */ same address as THR */ /* same address as IER */ I saw register bits defined like those in Listing 24.9 in some documentation I saw while work- ing on an AM65C30 USART. I liked it very much because it was easy to read in a bit-wise fashion. You can easily see the break-out of the bits and follow what is being tested or written throughout the source code. Listing 24.9. RS-232 device driver code continued. /* Complete description of Register bits follows: THR - TX data, RX data, Divisor Latch LSB IER — Interrupt Enable, Divisor Latch MSB

j-24 I Part IV * The Operating System Source Code Listing 24.9. continued 7 6 5 4 3 2 1 0 J | _ Data Available | \ Xmit Holding Reg Empty \ Receiver Line Status \ Modem Status Always 0000 IIR — Interrupt Identification Register 7 6 5 4 3 2 10 \ \ \ \ \ _ 11 = no interrupt 0110 = rcvr status 0100 = rcvd data 0010 = THR empty 0000 = Modem status 1101 = Rev Fifo Timeout (16550) (16550 only) 00 = FIFOs disabled non-zero = 16550 enabled FIFO Control Register (16550 only) This is a write only port at the same address as the IIR on an 8250/16450 7 6 5 4 3 2 1 = 1 = FIFO Enable = 1 = Recv FIFO Reset = 1 = Xmit FIFO Reset = (DMA) 0 = Single char, = FIFO Rev Trigger Level 00 - 1 char 01 - 2 chars 10 - 4 chars 11-8 chars 1 = Multichar LCR Line Control Register 5 4 3 2 1 Word Length Select Bit 0. Word Length Select Bit 1 . Number Stop Bits (0=1, 1=2) Parity Enable Even Parity Select Stick Parity Set Break Divisor Latch Access Bit

Chapter 24 Selected Device Driver Code MCR — Modem Control Register 7 6 5 4 3 2 10 ! ! . ! _ \ Data Terminal Ready Request to Send Out 1 Out 2 (= 1 to enable ints.) Loop Always 0 LSR Line Status Register 7 6 5 4 3 2 1 _ Data Ready \ Overrun Error Parity Error Framing Error Break interrupt Transmitter Holding Reg Empty Transmitter Shift Reg Empty _ Recv FIFO Error (16550 Only) MSR Modem Status Register 7 6 5 4 3 2 1 Delta Clear to Send Delta Data Set Ready Trailing Edge Ring Indicator Delta Rx Line Signal Detect Clear to Send Data Set Ready Ring Indicator Receive Line Signal Detect The status record for each driver is specific to that particular driver. The members of this struc- ture are defined in the header file RS232.h. It is documented in the preceding section in this chapter. See Listing 24.10. Listing 24.10. RS-232 device driver code continued. /* Record for 64 byte status and init record */ /* This structure is peculiar to the comms driver */ #define sStatus 64 static struct statRecC comstat[2]; static struct statRecC *pCS;

[326 Part PV ■ The Operating System Source Code This is the same device control block (DCB) record structure used in all MMURTL device drivers. Note that there are two DCBs. There is one for each channel, and they are contigu- ously defined, as is required by MMURTL. See Listing 24.1 1. Listing 24.11. RS-232 device driver code continued. static struct dcbtype { S8 Name[12]; S8 sbName; S8 type; S16 nBPB; U32 last ere; U32 nBlocks; S8 *pDevOp; S8 *pDevInit; S8 pDevSt; S8 fDevReent; S8 fSingleUser; S16 wJob; U32 0S1 U32 0S2 U32 0S3 U32 0S4 U32 0S5 U32 0S6 static struct dcbtype comdcb[2]; / Two RS-232 ports / / THE COMMS INTERRUPT FUNCTION PROTOTYPES */ static void interrupt comISR0(void) static void interrupt comlSRI (void) Just as with the IDE hard disk device driver, the following initialization routine is called once to set up the driver. The routine calls InitDevDr( ) after it has completed filling out the device control blocks and setting up the interrupts. This means it's ready. See Listing 24.12. Listing 24.12. RS-232 device driver code continued. This is called ONCE to initialize the 2 default comms channels with the OS device driver interface. It sets up defaults for both channels to 9600, 8N1 . U32 comssetup(void) { U32 ere;

Chapter 24 ■ Selected Device Driver Code H /* first we set up the 2 DCBs in anticipation of calling InitDevDr */ comdcb[0] .Name[0]

‘C comdcb[0] .Name[l ] = ‘0’ comdcb[0] .Name[2]

‘IT comdcb[0] .Name[2]

‘1 ' comdcb[0] .sbName

4; comdcb[0] ■ type

2; /* Sequential */ comdcb[0] .nBPB

1 ; /* 1 byte per block */ comdcb[0] .nBlocks

0; /* 0 for Sequential devices */ comdcb[0] .pDevOp = &comdev .op; comdcb[0] .pDevInit = &comdev init; comdcb[0] .pDevSt = &comdev “stat; comdcbfl ] .Name[0]

‘C'j comdcb[1 ] .Name[1 ] = ‘0’ comdcb[1 ] .Name[2]

‘M’ comdcb[1 ] .Name [2]

‘2’ comdcb[1 ] .sbName = 4 comdcb[1 ] •type = 2 /* Sequential */ comdcb[1 ] .nBPB = 1 /* 1 byte per block */ comdcb[1 ] .nBlocks = 0 /* 0 for Sequential devices */ comdcb[1 ] .pDevOp = &comdev .op; comdcb[1 ] .pDevInit = &comdev init; comdcb[1] .pDevSt

&( :od fidev “stat; /* Set default comms params in stat records */ comstat[0] .Baudrate = 9600; comstat[0] .parity =0; comstat[0] .databits = 8; comstat[0] .stopbits = 1; comstat[0] .XTimeOut = 100; comstat[0] .RTimeOut = 2; comstat[0] .IOBase = 0x3F8; comstat[0] .IRQNum = 4; comstat[0] .XBufSize = 4096; comstat[0] .RBufSize = 4096; sSendBuf[0] = 4096; sRecvBuf[0] = 4096; /* none */ comstat[1 ] .Baudrate = 9600; comstat[1 ] .parity = 0; comstat[1] .databits = 8; comstat[1] .stopbits = 1; comstat[1 ] .XTimeOut =100; comstat[1 ] .RTimeOut = 2; comstat[1 ] .IOBase = 0x2F8; comstat[1 ] .IRQNum = 3; comstat[1 ] .XBufSize = 4096; comstat[1 ] .RBufSize = 4096; sSendBuf[1] = 4096; sRecvBuf [1] = 4096; /* none */ MaskIRQ(4); MaskIRQ(3);

I
528 Part TV ■ The Operating System Source Code Listing 24.12. continued SetIRQVector(4, &comISR0) SetIRQVector(3, &comISR1 ) /* C0M1 */ /* COM2 */ return(erc = InitDevDr(5, &comdcb, 2, 1)) The following function does all the work for the interrupt service routine functions, one for each channel. This code must be re-entrant for two channels. If you want to add channels, you must expand the variable arrays that this works with. Right now, there are two items for each array. Four channels would be very easy to implement. I broke my own rule on doing ISRs in assembler. I would have done this in assembler if I had had the time. Assembler would be more efficient. Just the same, I haven't had any problems with it, even at higher baud rates. See Listing 24.13. Listing 24.13. RS-232 device driver code continued. ,***< This does the grunt work for each of the two ISRs This MUST remain reentrant for two ISRs. static void handleISR(U32 i) { U8 *pRBuf, *pXBuf; pRBuf = pRecvBuf [i] ; pXBuf = pSendBuf [i] ; while (TRUE) { int_id[i] = InByte (IIR[i]) switch (int_id[i]) /* i is the device */ /* Get ID Byte from IIR */ case RCVSTAT: stat_byte[i] = InByte (LSR[i] ) ; /* clear error conditions */ break; case RVCDATA: if (cRecvBuf[i] == sRecvBuf[i]) { /* Overflow!! */ recv_error[i] = ErcRcvBufOvr; InByte (THR[i]); } else { #asm CLI #endasm /* Do not put in buf */ /* trash the byte */

Chapter 24 ■ Selected Device Driver Code H pRBuf [head_recv[i] ] = InByte (THR[i]); /* Get the byte */ ++cRecvBuf [i] ; if (++head_recv[i] == SRECVBUF) head_recv[i] = 0; #asm STI #endasm recv_error[i] = 0; } break; case TXEMPTY: #asm CLI #endasm if (cSendBuf [i]) { OutByte(pXBuf[tail_send[i]], THR[i]); /* Send the byte */ if (++tail_send[i] == sSendBuf[i]) tail_send[i] = 0; — cSendBuf [i] ; fExpectInt[i] = TRUE; } else fExpectInt[i] = FALSE; #asm STI #endasm break; case MDMSTAT: mstat_byte[i] = InByte (MSR[i]); /* Get Modem Status */ break; case NOINT: stat_byte[i] = InByte(LSR[i] ) ; /* clear error conditions */ default: return; static void interrupt comISR0(void) { ; /* ; needed if asm is first in function */ #asm STI #endasm handleISR(0); End0fIRQ(4); return; } static void interrupt comlSRI (void) {

Part TV ■ The Operating System Source Code Listing 24.1 3. continued. ; /* ; needed if asm is first in function */ #asm STI #endasm handleISR(1); End0fIRQ(3); return; } static long ReadByteC(U32 device, unsigned char *pByteRet) { U32 counter; U8 *pRBuf; pRBuf = pRecvBuf [device] ; if (recv_error[device] ) return (recv_error[device] ) ; if (cRecvBuf [device] ) { *pByteRet = pRBuf [tail_recv[device] ] ; if (++tail_recv[device] == sRecvBuf [device] ) tail_recv[device] = 0; — cRecvBuf [device] ; return (0); } counter = comstat [ device ] .RTimeOut; /* set up for timeout */ while (counter—) { Sleep(1); if (cRecvBuf [device] ) { *pByteRet = pRBuf [tail_recv[device] ] ; if (++tail_recv[device] == sRecvBuf [device] ) tail_recv[device] = 0; — cRecvBuf [device] ; return (0); } } return (ErcRecvTimeout) ; } ft*******************************************/ static long ReadRecordC(U32 device, unsigned char *pDataRet unsigned int sDataMax, unsigned int *pcbRet) { int ere, cb; ere = 0;

Chapter 24 ■ Selected Device Driver Code |ciT cb = 0; while ((cb < sDataMax) && (!erc)) { ere = ReadByteC(device, pDataRet++); if (!erc) ++cb; } pcbRet = cb; / tell em how many bytes */ return (ere); } static long WriteByteC(U32 device, unsigned char b) { U32 ere, counter; U8 pXBuf; ere = 0; pXBuf = pSendBuf [device] ; counter = comstat [device] .XTimeOut; / set up for timeout / while (cSendBuf [device] == sSendBuf [device] ) { Sleep(1); counter— ; if (Jcounter) return (ErcXmitTimeout) ; / never got sent / } #asm CLI tfendasm if ( IfExpectlntfdevice] ) { / Xmit buf empty, send ourself / OutByte(b, THR[device] ) ; fExpectInt[device] = TRUE; } else { pXBuf [head_send[device] ] = b; if (++head_send[device] == sSendBuf [device] ) headsendfdevice] = 0; ++cSendBuf [device] ; / one more in buf */ } #asm ST I #endasm return (ere); } static long WriteRecordC(U32 device, unsigned char *pSendData,

B Part TV ■ The Operating System Source Code Listing 24.13. continued unsigned int cbSendData) { int ere; ere = 0; while ((cbSendData) && (!erc)) { ere = WriteByteC(device, pSendData++) — cbSendData; } return (ere); } /******** **********************************! static long DiscardRecvC(U32 device) { U32 saveto, ere; U8 b; saveto = comstat [ device ] .RTimeOut; comstat [ device ] .RTimeOut = 1; ere = 0; while (!erc) ere = ReadByteC(device, &b); comstat[device] .RTimeOut = saveto; return (0); This sets comms params prior to opening, or while a channel is in use. static U32 SetParams(U32 device) { U32 divisor, speed; U8 c, parity, bits, stop_bit, temp; parity = comstat[device] .parity ; bits = comstat[device] .databits; stop_bit = comstat[device] .stopbits; speed = comstat[device] .Baudrate; /* Set up baud rate */ divisor = 1 15200/speed; #asm CLI #endasm c=InByte (LCR[device] ) ;

Chapter 24 ■ Selected Device Driver Code 0 OutByte ((c | 0x80), LCR(device) ) ; OutByte ((divisor & 0x00ff), DLABLO [ device ]) ; OutByte ( ( (divisor»8) & 0x00ff), DLAB_HI [device] ) OutByte (c, LCR[device] ) ; #asm STI #endasm /* set comms params */ temp = bits - 5; temp [= ((stop_bit == 1 ) ? 0x00 : 0x04); switch (parity) { case NO_PAR : temp case OD_PAR : temp case EV_PAR : temp } = 0x00; break; = 0x08; break; = 0x18; break; #asm CLI #endasm OutByte (temp, LCR[device]) #asm STI #endasm return (0); } This allocates buffers, sets up the ISR and IRQ values and open the channel for use. static U32 0penCommC(U32 device) { U32 ere; U16 port_base; U8 c; if (comstat[device] .commJob) return(ErcChannelOpen) ; GetJobNum(&comstat[device] .commJob) ; ere = AllocOSPage(comstat[device] .XBufSize/4096, &pSendBuf [device] ) ; if (!erc) { ere = AllocOSPage(comstat[device] .RBuf Size/4096, &pRecvBuf [device] ) ; if (ere) /* get rid of Xmit buf if we can't recv */

«/ I Part IV ■ The Operating System Source Code Listing 24.13. continued DeAllocPage(pSendBuf [device] , comstat[device] .XBufSize/4096) ; } if (ere) { comstat [ device ] .commjob = 0; return (ere); } port_base = comstat [device] . IOBase; /* Set up buffer variables for this port */ cSendBuf [device] = 0; head_send[device] = 0; tail_send[device] = 0; cRecvBuf [device] = 0; head_recv[device] = 0; tail_recv[device] = 0; recv_error[device] = 0; THR[device] = port_base; IER[device] = port_base + 1; IIR[device] = port_base + 2; FCR[device] = port_base + 2; LCR[device] = port_base + 3; MCR[device] = port_base + 4; LSR[device] = port_base + 5; MSR[device] = port_base + 6; DLABJHI [device] = port_base + 1; DLAB_L0[ device] = port_base; InByte(THR[device] ) ; /* reset any pending ints on chip */ InByte(LSR[device] ) ; #asm CLI #endasm control_byte[device] = RTS \ DTR \ 0UT2; OutByte(control_byte[device] , MCR[device] ) ; /* Mod Ctrl Reg */ OutByte(0x0F, IER[device] ) ; /* Int Enable Reg */ /* See if we have a 16550 and set it up if we do!! */ OutByte(0x03, FCR[device] ) ; c = InByte(IIR[device] ) ; if (c & 0xC0) /* we have a 16550 and it's set to go! */ fl6550[device] = 1; else f16550[device] = 0; /* 8250 or 16450 */ #asm ST I

Chapter 24 ■ Selected Device Driver Code H SetParams(device) ; UnMaskIRQ(comstat [device] . IRQNum) ; return (0); } This closes the port, sets the owner to 0 and deallocates the buffers. static int CloseCommC (U32 device) { U32 ere; MaskIRQ(comstat[device] .IRQNum) ; OutByte(0, MCR[device]) ; OutByte(0, IER[device]); ere = DeAllocPage(pSendBuf [device] , comstatf device] .XBufSize/4096) ere = DeAllocPage(pRecvBuf [device] , comstat[device] .RBuf Size/4096) comstat[device] .commJob = 0; return (ere); Just as with the IDE hard disk driver (and all other drivers in MMURTL), three functions provide the interface from the outside. They are the last functions defined in this file. Their offsets (entry points) have been defined in the device control block for each of the two devices. This was done before calling InitDevDr( ). This driver is re-entrant, and therefore all data must be associated with the particular comms port you are addressing. See Listing 24.14. Listing 24.14. RS-232 device driver code continued. Called for all device operations. This assigns physical device from logical number that outside callers use. For RS-232, 5=0 and 6=1 . static U32 comdev_op(U32 dDevice, U32 dOpNum, U32 dLBA, U32 dnBlocks, U8 *pData) { U32 ere; U32 Job, device;

I
536 Part TV ■ The Operating System Source Code Listing 24.14. continued U8 c; /* Set internal drive number */ /* 5 RS-232 1 C0M1 (OS built-in) */ /* 6 RS-232 2 COM2 (OS built-in) */ if (dDevice == 5) device = 0; else device = 1 ; GetJobNum(&Job); if (( !comstat[ device ] .commJob) && (dOpNum != CmdOpenC)) return(ErcNotOpen) ; if (comstat[ device] .commJob) { if ( (comstat[ device] .commJob != Job) && (Job != 1)) return(ErcNotOwner) ; } ere = 0; /* default error */ switch(dOpNum) { case(0) : break; /* Null Command */ case CmdReadB: ere = ReadByteC(device, pData); break; case CmdWriteB: ere = WriteByteC(device, *pData); break; case CmdReadRec: ere = ReadRecordC(device, pData, dnBlocks, &comstat[device] .LastTotal) ; break; case CmdWriteRec: ere = WriteRecordC(device, pData, dnBlocks); break; case CmdSetRTO: comstat[device] .RTimeOut = dLBA; /* 10ms intervals */ break; case CmdSetXTO: comstat[device] .XTimeOut = dLBA; /* 10ms intervals */ break; case CmdOpenC: ere = OpenCommC(device) ; break; case CmdCloseC: ere = CloseCommC(device) ; break; case CmdDiscardRcv:

Chapter 24 ■ Selected Device Driver Code VTyj ere = DiscardRecvC(device) ; break; case CmdSetDTR: control_byte[ device] |= DTR; OutByte(control_byte[device] , LCR[device] ) break; case CmdSetRTS: control_byte[device] = RTS; OutByte(control_byte[device] , MCR[device] ) break; case CmdReSetDTR: control_byte[device] &= -DTR; OutByte(control_byte[device] , LCR[device] ) case CmdReSetRTS: control_byte[device] &= -RTS; OutByte(control_byte[device] , MCR[device] ) break; case CmdBreak: c = InByte(LCR[ device] ) ; OutByte((c | 0x40), LCR[device] ) ; Sleep(dLBA); OutByte(c, LCR[device]) ; break; case CmdGetDC: *pData = mstat_byte [device] & CD; break; case CmdGetDSR: *pData = mstat_byte[device] & DSR; break; case CmdGetCTS: *pData = mstat_byte[ device] & CTS; break; case CmdGetRI: *pData = mstat_byte[ device] & RI; break; default: break; } comstat[device] .LastErc = ere; return(erc) ; Called for status report on comms channel. Returns 64 byte block for channel specified. This is called by the PUBLIC call DeviceStat static U32 comdev_stat(U32 dDevice, S8 *pStatRet, U32 dStatusMax, U32 pdStatusRet) { U32 i, device; / Set internal device number */

Part IV ■ The Operating System Source Code Listing 24.14. continued if (dDevice == 5) device = 0; else device = 1 ; if (dStatusMax > 64) i = 64; else i = dStatusMax; if (! device) { CopyData(&comstat[0] , pStatRet, i) ; /* copy the status data */ } else { CopyData(&comstat[1 ] , pStatRet, i); /* copy the status data */ } *pdStatusRet = dStatusMax; /* give em the size returned */ return(0) ; Called to set parameters for the comms channels prior to opening or while in use. If an invalid value is passed in, all params remain the same as before. Some comms channel params may not be changed while the channel is in use. This is called by the PUBLIC call Devicelnit. static S32 comdev_init(U32 dDevice, S8 *pInitData, U32 sdlnitData) { U32 ere, Xbufsize, Rbufsize, device; U32 speed, XTO, RTO; U16 port_base; U8 parity, bits, stop_bit, IRQNUM; if (dDevice == 5) device = 0; /* Set internal device number */ else device = 1 ; if (sdlnitData < 40) return(ErcBadlnitSize) ; pCS = plnitData; /* Get the callers new params */ speed ■ pCS->Baudrate; /* Non Volatile */

Chapter 24 ■ Selected Device Driver Code |
539 parity ■ pCS->parity; /* Non Volatile */ bits = pCS->databits; /* Non Volatile */ stop_bit = pCS->stopbits; /* Non Volatile */ XTO = pCS->XTimeOut; /* Non Volatile */ RTO = pCS->RTimeOut; /* Non Volatile */ port_base = pCS->IOBase; Xbufsize = pCS ->XBuf Size; Rbufsize = pCS->RBufSize; IRQNUM = pCS->IRQNum; /* Non Volatile params can be set whether or not the channel is open. Do these first and return errors. */ if ((speed > MAX_BAUD) || (speed < MIN_BAUD)) return (ErcBadBaud) ; if ((parity < NO_PAR) \ (parity > OD_PAR)) return (ErcBadParity) ; if ((bits < 5) ; | (bits > 8)) return (ErcBadDataBits) ; if ((stop_bit < 1) || (stop_bit > 2)) return (ErcBadStopBits) ; if (!XTO) XTO = 1; if (!RTO) RTO = 1; comstat [device] .Baudrate = speed; comstat[device] .parity = parity; comstat [device] .databits = bits; comstat[device] .stopbits = stop_bit; comstat [ device ] .XTimeOut = XTO; comstat[device] .RTimeOut = RTO; /* If we got here, the params are OK. Now we check to see if the channel is open and call SetParams if so. The channel is open if the JobNumber in the commstat record is NON -ZERO. */ if (comstat[device] .commJob) { /* Channel Open! */ SetParams(device) ; } /* Channel is not open so we check and set rest of params */ else { if (!port_base) return (ErcBadlOBase) ; if (IRQNUM < 3) return (ErcBadCommlRQ) ; /* We now round up buffer sizes to whole pages */

r/n Part F/ ■ The Operating System Source Code Listing 24.14. continued Xbufsize = Xbufsize/4096 * 4096; if (Xbufsize % 4096) Xbuf size+=4096; /* another page */ Rbufsize = Rbuf size/4096 * 4096; if (Rbufsize % 4096) Rbuf size+=4096; /* another page */ comstat [ device ]. IOBase = port_base; comstat [device]. IRQNum = IRQNUM; comstat [device] .XBufSize = Xbufsize; comstat[device] .RBufSize = Rbufsize; /* Local copies so we don't work from a structure in ISR */ sSendBuf [device] = Xbufsize; /* Size of buffer (allocated) */ sRecvBuf [device] = Rbufsize; /* Size of buffer (allocated) */ ere = 0; } return(erc) ; }

Powered by TurnKey Linux.