/*- * $Id: device.c,v 1.5 90/01/27 20:34:43 Rhialto Exp $ * $Log: device.c,v $ * Revision 1.5 90/01/27 20:34:43 Rhialto * Commented out #undef DEBUG * * Revision 1.4 90/01/23 00:24:50 Rhialto * io_Error=0 for immediate commands * * Revision 1.3 89/12/17 21:29:37 Rhialto * Revision 1.1 89/12/17 20:03:55 Rhialto * * DEVICE.C * * The messydisk.device code that makes it a real Exec .device. * Mostly based on the 1.1 RKM example and Matt Dillon's library code. * * This code is (C) Copyright 1989 by Olaf Seibert. All rights reserved. May * not be used or copied without a licence. -*/ #include "dev.h" #include "device.h" /*#undef DEBUG /**/ #ifdef DEBUG # define debug(x) dbprintf x #else # define debug(x) #endif /* * The first executable location. This should return an error in case * someone tried to run you as a program (instead of loading you as a * device) */ /* INDENT OFF */ #asm moveq.l #20,d0 rts #endasm /* INDENT ON */ /* * A romtag structure. Both "exec" and "ramlib" look for this structure to * discover magic constants about you (such as where to start running you * from...). */ /* INDENT OFF */ #asm public __H0_end _EndCode equ __H0_end public _RomTag _RomTag: dc.w $4AFC ; RTC_MATCHWORD dc.l _RomTag ; rt_MatchTag dc.l __H0_end ; rt_EndSkip dc.b 0 ; rt_Flags (no RTF_AUTOINIT) dc.b VERSION ; rt_Version dc.b 3 ; rt_Type NT_DEVICE dc.b RTPRI ; rt_Pri dc.l _DevName ; rt_Name dc.l _idString ; rt_IdString dc.l _Init ; rt_Init #endasm /* INDENT ON */ char DevName[] = "messydisk.device"; char idString[] = "messydisk.device $Revision: 1.5 $ $Date: 90/01/27 20:34:43 $\r\n"; /* * -30-6*X Library vectors: */ void (*LibVectors[]) () = { _DevOpen, _DevClose, _DevExpunge, _LibNull, _DevBeginIO, _DevAbortIO, (void (*) ()) -1 }; /* * Device commands: */ void (*funcTable[]) () = { CMD_Invalid, CMD_Reset, CMD_Read, CMD_Write, CMD_Update, CMD_Clear, CMD_Stop, CMD_Start, CMD_Flush, TD_Motor, TD_Seek, TD_Format, TD_Remove, TD_Changenum, TD_Changestate, TD_Protstatus, TD_Rawread, TD_Rawwrite, TD_Getdrivetype, TD_Getnumtracks, TD_Addchangeint, TD_Remchangeint, }; /* * Here begin the system interface commands. When the user calls * OpenDevice/CloseDevice/RemDevice, this eventually gets trahslated into * a call to the following routines (Open/Close/Expunge). Exec has * already put our device pointer in A6 for us. Exec has turned off task * switching while in these routines (via Forbid/Permit), so we should not * take too long in them. */ /* INDENT OFF */ #asm public _Init _Init: ;a0=segment list movem.l D2-D3/A0/A6,-(sp) jsr _CInit movem.l (sp)+,D2-D3/A0/A6 rts public __DevOpen __DevOpen: ;d0=unitnum,d1=flags,a1=ioreq,a6=device movem.l D0-D3/A1/A6,-(sp) jsr _DevOpen movem.l (sp)+,D0-D3/A1/A6 rts public __DevClose __DevClose: ;a1=ioreq,a6=device movem.l D2-D3/A1/A6,-(sp) jsr _DevClose movem.l (sp)+,D2-D3/A1/A6 rts public __DevExpunge __DevExpunge: ;a6=device movem.l D2-D3/A6,-(sp) jsr _DevExpunge movem.l (sp)+,D2-D3/A6 rts public __LibNull __LibNull: clr.l d0 rts public __DevBeginIO __DevBeginIO: ;a1=ioreq,a6=device movem.l D2-D3/A1/A6,-(sp) jsr _DevBeginIO movem.l (sp)+,D2-D3/A1/A6 rts public __DevAbortIO __DevAbortIO: ;a1=ioreq,a6=device movem.l D2-D3/A1/A6,-(sp) jsr _DevAbortIO movem.l (sp)+,D2-D3/A1/A6 rts #endasm #ifdef HANDLE_IO_QUICK #asm ;;;; ; ; C interface to the atomic set bit and test old value instruction. ; ; Called as BSET_ACTIVE(byte *address). ; ; Old value of the bit returned all over d0.w _BSET_ACTIVE: move.l 4(sp),a0 bset #0,(a0) ; UNITB_ACTIVE sne d0 rts #endasm #endif /* INDENT ON */ long SysBase; /* Argh! A global variable! */ /* * The Initialization routine is given only a seglist pointer. Since we * are NOT AUTOINIT we must construct and add the device ourselves and * return either NULL or the device pointer. Exec has Forbid() for us * during the call. * * If you have an extended device structure you must specify the size of the * extended structure in MakeLibrary(). */ DEV * CInit(D2, D3, segment) ulong D2, D3; long segment; { DEV *dev; SysBase = *(long *) 4; #ifdef DEBUG dbinit(); #endif dev = MakeLibrary(LibVectors, NULL, NULL, (long) sizeof (DEV), NULL); if (DevInit(dev)) { dev->dev_Node.ln_Type = NT_DEVICE; dev->dev_Node.ln_Name = DevName; dev->dev_Flags = LIBF_CHANGED | LIBF_SUMUSED; dev->dev_Version = VERSION; dev->dev_Revision = REVISION; dev->dev_IdString = (APTR) idString; dev->md_Seglist = segment; AddDevice(dev); return (dev); } FreeMem((char *) dev - dev->dev_NegSize, dev->dev_NegSize + dev->dev_PosSize); return NULL; } /* * Open is given the device pointer, unitno and flags. Either return the * device pointer or NULL. Remove the DELAYED-EXPUNGE flag. Exec has * Forbid() for us during the call. */ void DevOpen(unitno, flags, D2, D3, ioreq, dev) ulong unitno; ulong flags; ulong D2, D3; struct IOStdReq *ioreq; DEV *dev; { UNIT *unit; debug(("OpenDevice unit %ld, flags %lx\n", unitno, flags)); if (unitno >= MD_NUMUNITS) goto error; if ((unit = dev->md_Unit[unitno]) == NULL) { if ((unit = UnitInit(dev, unitno)) == NULL) goto error; dev->md_Unit[unitno] = unit; } ioreq->io_Unit = (struct Unit *) unit; ++unit->mu_OpenCnt; ++dev->dev_OpenCnt; dev->dev_Flags &= ~LIBF_DELEXP; return; error: ioreq->io_Error = IOERR_OPENFAIL; } /* * Close is given the device pointer and the io request. Be sure not to * decrement the open count if already zero. If the open count is or * becomes zero AND there is a LIBF_DELEXP, we expunge the device and * return the seglist. Otherwise we return NULL. * * Note that this routine never sets LIBF_DELEXP on its own. * * Exec has Forbid() for us during the call. */ long DevClose(D2, D3, ioreq, dev) ulong D2, D3; struct IOStdReq *ioreq; DEV *dev; { UNIT *unit; unit = (UNIT *) ioreq->io_Unit; debug(("CloseDevice io %08lx unit %08lx\n", ioreq, unit)); /* * See if the unit is still in use. If not, close it down. This may * need to do an update, which requires the ioreq. */ if (unit->mu_OpenCnt && --unit->mu_OpenCnt == 0) { dev->md_Unit[unit->mu_UnitNr] = NULL; UnitCloseDown(ioreq, dev, unit); } /* * Make sure the ioreq is not used again. */ ioreq->io_Unit = (void *) -1; ioreq->io_Device = (void *) -1; if (dev->dev_OpenCnt && --dev->dev_OpenCnt) return (NULL); if (dev->dev_Flags & LIBF_DELEXP) return (DevExpunge(D2, D3, dev)); return (NULL); } /* * We expunge the device and return the Seglist ONLY if the open count is * zero. If the open count is not zero we set the DELAYED-EXPUNGE * flag and return NULL. * * Exec has Forbid() for us during the call. NOTE ALSO that Expunge might be * called from the memory allocator and thus we CANNOT DO A Wait() or * otherwise take a long time to complete (straight from RKM). * * Apparently RemLibrary(lib) calls our expunge routine and would therefore * freeze if we called it ourselves. As far as I can tell from RKM, * DevExpunge(lib) must remove the device itself as shown below. */ long DevExpunge(D2, D3, dev) ulong D2, D3; DEV *dev; { long Seglist; if (dev->dev_OpenCnt) { dev->dev_Flags |= LIBF_DELEXP; return (NULL); } Remove(dev); DevCloseDown(dev); /* Should be quick! */ #ifdef DEBUG dbuninit(); #endif Seglist = dev->md_Seglist; FreeMem((char *) dev - dev->dev_NegSize, (long) dev->dev_NegSize + dev->dev_PosSize); return (Seglist); } /* * BeginIO entry point. We don't handle any QUICK requests, we just send * the request to the proper unit to handle. */ void DevBeginIO(D2, D3, ioreq, dev) ulong D2, D3; register struct IOStdReq *ioreq; DEV *dev; { UNIT *unit; /* * Bookkeeping. */ unit = (UNIT *) ioreq->io_Unit; debug(("BeginIO: io %08lx dev %08lx u %08lx\n", ioreq, dev, unit)); /* * See if the io command is within range. */ if (STRIP(ioreq->io_Command) > TD_LASTCOMM) goto NoCmd; #ifdef HANDLE_IO_QUICK Forbid(); /* Disable(); is a bit too strong for us. */ #endif /* * Process all immediate commands no matter what. Don't even require * an exclusive lock on the unit. */ if (IMMEDIATE & (1L << STRIP(ioreq->io_Command))) goto Immediate; /* * We don't handle any QUICK I/O since that only gives trouble with * message ports and so. Other devices normally would include the code * below. */ #ifdef HANDLE_IO_QUICK /* * See if the user does not request QUICK IO. If not, it is likely to * be async and therefore we don't do it sync. */ if (!(ioreq->io_Flags & IOF_QUICK)) goto NoQuickRequested; /* * See if the unit is STOPPED. If so, queue the msg. */ if (unit->mu_Flags & UNITF_STOPPED) goto QueueMsg; /* * This is not an immediate command. See if the device is busy. If * not, process the action in this (the caller's) context. */ if (!BSET_ACTIVE(&unit->mu_Flags)) goto Immediate; #endif /* * We need to queue the device. Clear the QUICK flag. */ QueueMsg: ioreq->io_Flags &= ~IOF_QUICK; NoQuickRequested: #ifdef HANDLE_IO_QUICK Permit(); /* Enable(); is a bit too strong for us. */ #endif PutMsg(&unit->mu_Port, ioreq); return; Immediate: #ifdef HANDLE_IO_QUICK Permit(); /* Enable(); is a bit too strong for us. */ #endif debug(("BeginIO: Immediate\n")); ioreq->io_Error = TDERR_NoError; PerformIO(ioreq, unit); return; NoCmd: ioreq->io_Error = IOERR_NOCMD; TermIO(ioreq); return; } /* * Terminate an io request. Called (normally) for every BeginIO. 'Funny' * commands that don't call TermIO, or call it multiple times, may not be * properly handled unless you are careful. TD_ADDCHANGEINT and * TD_REMCHANGEINT are obvious examples. */ void TermIO(ioreq) register struct IOStdReq *ioreq; { register UNIT *unit; unit = (UNIT *) ioreq->io_Unit; debug(("TermIO: io %08lx u %08lx %ld %d\n", ioreq, unit, ioreq->io_Actual, ioreq->io_Error)); #ifdef HANDLE_IO_QUICK /* * Since immediate commands don't even require an exclusive lock on * the unit, don't unlock it. */ if (IMMEDIATE & (1L << STRIP(ioreq->io_Command))) goto Immediate; /* * We may need to turn the active (lock) bit off, but not if we are * within the task. */ if (unit->mu_Flags & UNITF_INTASK) goto Immediate; unit->mu_Flags &= ~UNITF_ACTIVE; /* * The task may have work to do that came in while we were processing * in the caller's context. */ if (unit->mu_Flags & UNITF_WAKETASK) { unit->mu_Flags &= ~UNITF_WAKETASK; WakePort(&unit->mu_Port); } #endif Immediate: /* * If the quick bit is still set then wen don't need to reply the msg * -- just return to the user. */ if (!(ioreq->io_Flags & IOF_QUICK)) ReplyMsg(&ioreq->io_Message); return; } /* * AbortIO entry point. We don't abort IO here. */ long DevAbortIO(D2, D3, ioreq, dev) ulong D2, D3; DEV *dev; struct IOStdReq *ioreq; { return 1; } void WakePort(port) register struct MsgPort *port; { Signal(port->mp_SigTask, 1L << port->mp_SigBit); } /* * This is the main loop of the Unit tasks. It must be very careful with * global data. */ void UnitTask() { /* DEV *dev; */ UNIT *unit; long waitmask; struct IOExtTD *ioreq; { struct Task *task, *FindTask(); task = FindTask(NULL); unit = (UNIT *) task->tc_UserData; /* dev = unit->mu_Dev; */ task->tc_UserData = NULL; } /* * Now finish initializing the message ports and other signal things */ { byte sigbit; unit->mu_DiskReplyPort.mp_SigBit = AllocSignal(-1L); unit->mu_DiskReplyPort.mp_Flags = PA_SIGNAL; sigbit = AllocSignal(-1L); unit->mu_Port.mp_SigBit = sigbit; unit->mu_Port.mp_Flags = PA_SIGNAL; waitmask = 1L << sigbit; unit->mu_DmaSignal = AllocSignal(-1L); } for (;;) { debug(("Task: Waiting... ")); Wait(waitmask); /* * See if we are stopped. */ if (unit->mu_Flags & UNITF_STOPPED) continue; #ifdef HANDLE_IO_QUICK /* * Lock the device. If it fails, we have set a flag such that the * TermIO wakes us again. */ unit->mu_Flags |= UNITF_WAKETASK; if (BSET_ACTIVE(&unit->mu_Flags)) continue; unit->mu_Flags |= UNITF_INTASK; #endif while (ioreq = (struct IOExtTD *) GetMsg(&unit->mu_Port)) { debug(("Task: io %08lx %x\n", ioreq, ioreq->iotd_Req.io_Command)); ioreq->iotd_Req.io_Error = 0; PerformIO((&ioreq->iotd_Req), unit); } #ifdef HANDLE_IO_QUICK unit->mu_Flags &= ~(UNITF_ACTIVE | UNITF_INTASK | UNITF_WAKETASK); #endif } } void CMD_Invalid(ioreq, unit) struct IOStdReq *ioreq; UNIT *unit; { ioreq->io_Error = IOERR_NOCMD; TermIO(ioreq); } void CMD_Stop(ioreq, unit) struct IOExtTD *ioreq; UNIT *unit; { unit->mu_Flags |= UNITF_STOPPED; TermIO(ioreq); } void CMD_Start(ioreq, unit) struct IOExtTD *ioreq; UNIT *unit; { unit->mu_Flags &= ~UNITF_STOPPED; WakePort(&unit->mu_Port); TermIO(ioreq); } void CMD_Flush(ioreq, unit) struct IOExtTD *ioreq; UNIT *unit; { register struct IOStdReq *req; /* Flush our own command queue */ Forbid(); while (req = (struct IOStdReq *) GetMsg(unit->mu_Port)) { req->io_Error = IOERR_ABORTED; ReplyMsg(&req->io_Message); } Permit(); WakePort(&unit->mu_Port); TermIO(ioreq); } void TrackdiskGateway(ioreq, unit) register struct IOExtTD *ioreq; UNIT *unit; { register struct IOExtTD *tdioreq; debug(("Trackdisk: %x ", ioreq->iotd_Req.io_Command)); tdioreq = unit->mu_DiskIOReq; /* * Clone almost the entire io request to relay to the * trackdisk.device. */ tdioreq->iotd_Req.io_Command = ioreq->iotd_Req.io_Command; tdioreq->iotd_Req.io_Flags = ioreq->iotd_Req.io_Flags | IOF_QUICK; tdioreq->iotd_Req.io_Length = ioreq->iotd_Req.io_Length; tdioreq->iotd_Req.io_Data = ioreq->iotd_Req.io_Data; tdioreq->iotd_Req.io_Offset = ioreq->iotd_Req.io_Offset; if (ioreq->iotd_Req.io_Command & TDF_EXTCOM) { tdioreq->iotd_Count = ioreq->iotd_Count; tdioreq->iotd_SecLabel = ioreq->iotd_SecLabel; } BeginIO(tdioreq); WaitIO(tdioreq); ioreq->iotd_Req.io_Error = tdioreq->iotd_Req.io_Error; ioreq->iotd_Req.io_Actual = tdioreq->iotd_Req.io_Actual; TermIO(ioreq); } #ifdef DEBUG /* DEBUGGING */ struct MsgPort *Dbport; /* owned by the debug process */ struct MsgPort *Dback; /* owned by the DOS device driver */ short DBEnable; struct SignalSemaphore PortUse; #define CTOB(x) (void *)(((long)(x))>>2) /* BCPL conversion */ /* * DEBUGGING CODE. You cannot make DOS library calls that access * other devices from within a device driver because the caller may not be * a process. If you need to make such calls you must create a port and * construct the DOS messages yourself. I do not do this. To get * debugging info out another PROCESS is created to which debugging * messages can be sent. The replyport gets a new SigTask for every * dbprintf call, therefore the semaphore. */ extern void debugproc(); struct Library *DOSBase, *OpenLibrary(); dbinit() { struct Task *task = FindTask(NULL); DOSBase = OpenLibrary("dos.library", 0L); Dback = CreatePort("Dback", -1L); FreeSignal((long) Dback->mp_SigBit); Dback->mp_SigBit = 2; InitSemaphore(&PortUse); CreateProc("messydisk_DB", (long) TASKPRI + 1, CTOB(debugproc), 2000L); WaitPort(Dback); /* handshake startup */ GetMsg(Dback); /* remove dummy msg */ DBEnable = 1; dbprintf("Debugger running V1.11\n"); } dbuninit() { struct Message killmsg; if (Dbport) { killmsg.mn_Length = 0; /* 0 means die */ ObtainSemaphore(&PortUse); Dback->mp_SigTask = FindTask(NULL); PutMsg(Dbport, &killmsg); WaitPort(Dback); /* He's dead jim! */ GetMsg(Dback); ReleaseSemaphore(&PortUse); Dback->mp_SigBit = -1; DeletePort(Dback); /* * Since the debug process is running at a greater priority, I am * pretty sure that it is guarenteed to be completely removed * before this task gets control again. */ } CloseLibrary(DOSBase); } dbprintf(a, b, c, d, e, f, g, h, i, j) long a, b, c, d, e, f, g, h, i, j; { struct { struct Message msg; char buf[256]; } msgbuf; register struct Message *msg = &msgbuf.msg; register long len; if (Dbport && DBEnable) { ObtainSemaphore(&PortUse); /* sprintf is not re-entrant */ sprintf(msgbuf.buf, a, b, c, d, e, f, g, h, i, j); len = strlen(msgbuf.buf) + 1; msg->mn_Length = len; /* Length NEVER 0 */ Dback->mp_SigTask = FindTask(NULL); PutMsg(Dbport, msg); WaitPort(Dback); GetMsg(Dback); ReleaseSemaphore(&PortUse); } } /* * BTW, the DOS library used by debugmain() was actually opened by the * opener of the device driver. */ debugmain() { register struct Message *msg; register long len; register void *fh, *Open(); void *fh2; struct Message DummyMsg; Dbport = CreatePort("Dbport", -1L); fh = Open("CON:0/20/640/101/Device debug", MODE_NEWFILE); fh2 = Open("PAR:", MODE_OLDFILE); PutMsg(Dback, &DummyMsg); for (;;) { WaitPort(Dbport); msg = GetMsg(Dbport); len = msg->mn_Length; if (len == 0) break; --len; /* Fix length up */ if (DBEnable & 1) Write(fh, msg + 1, len); if (DBEnable & 2) Write(fh2, msg + 1, len); PutMsg(Dback, msg); } Close(fh); Close(fh2); DeletePort(Dbport); PutMsg(Dback, msg); /* Kill handshake */ } /* * The assembly tag for the DOS process: CNOP causes alignment problems * with the Aztec assembler for some reason. I assume then, that the * alignment is unknown. Since the BCPL conversion basically zero's the * lower two bits of the address the actual code may start anywhere within * 8 bytes of address (remember the first longword is a segment pointer * and skipped). Sigh.... (see CreateProc() above). */ /* INDENT OFF */ #asm public _debugproc public _debugmain cseg _debugproc: nop nop nop nop nop movem.l D2-D7/A2-A6,-(sp) jsr _debugmain movem.l (sp)+,D2-D7/A2-A6 rts #endasm #endif /* DEBUG */