# /* Device driver for DO: DOS file access device under Ersatz-11 PDP-11 * simulator. * * It's a Unix character device, controlled by stty() calls (which means * regular Unix programs can't interact with it directly, it can only * be used by specially-written commands), because i) there's no way to * glue a foreign filesystem onto V6, and ii) there's no way to way to pass * a file name to an open on any kind of device. * * Note that reading the CSR clears the 'ready' (actually command completed * bit). So looping on the bit waiting for it to be 1 (as many other Unix * device drivers so with the 'ready' bit) isn't really viable here; all * operations - even command operations like open - use an interrupt to * signal completion. * * At the moment, only allows one process to open it at a time (so it doesn't * have to keep track of who has which handles open when a process exits); * this isn't really an issue given the current user access command. This * would be relatively easy to upgrade to one handle per process, of course * (an NPROC sized table of handles) - more than one per process would be a bit * more hair. * * Also, currently handles only one instance of the device; this, too, would * be pretty easy to upgrade (although the file name buffer should probably * be shared, which means an access control flag, waiting for it to clear, * etc, etc). * * JNC 5/Apr/2014 */ #include "../param.h" #include "../buf.h" #include "../conf.h" #include "../user.h" #include "../dos.h" /* Device configuration - register locations, priority, etc */ #define DOSREG 0176470 #define DOSPRI 04 /* Valid 07-04 */ #define DOSVEC 0110 /* Suggested 240 is 11/45 PIR */ /* Registers */ struct dosregs { int d_csr; int d_ba; int d_bae; /* Bits 0-5 only */ }; #define C_IE 01 /* RW - Interrupt enable */ #define C_GO 02 /* WO - write 1 to start command */ #define C_BSY 02 /* RO - device busy */ #define C_RDY 04 /* RO - command completed (cleared on read) */ #define C_UNUS1 010 /* Unused */ #define C_PRI 060 /* RO - interrupt priority level, 4-7 */ #define C_UNUS2 0100 /* Unused */ #define C_VEC 07600 /* RW - interrupt vector /4 */ #define C_UNUS3 010000 /* Unused */ #define C_PSHFT 4 /* Shift to move priority bits to field */ #define C_VSHFT 6 /* Shift to move vector bits to field */ /* Command packet format */ struct doscmd { int d_cmd; /* command code, -1::8 */ int d_rc; /* return code (-1=timeout, 0=OK, >0=error code) */ int d_hand; /* file handle (small integer whatever host OS) */ int d_parm; /* parameter (if defined by cmd) */ int d_len; /* length of data buffer in bytes */ char *d_addr; /* addr of data buffer (low 16 bits) */ int d_addre; /* addr extension of data buffer (high 6 bits) */ }; /* Other */ #define DOSMAXFL 256 /* Maximum file name length */ /* Prepared command words, etc. */ #define C_CMD (DOSVEC << C_VSHFT) #define PRICMD -60 /* Command sleep wakeup priority */ /* For now, assume device priority is 4. See comments on dosopen(). */ #define spldos spl4 /* Device state, etc */ #define DS_USE 01 /* In use */ #define DS_CMD 02 /* Current op is a command */ #define DS_RDWR 04 /* Current op is file read/write */ #define DS_FOPEN 0100 /* File open */ /* Static. For now, only one buffer header (used for read/write); if * we allow multiple users, will either i) have to interlock access to * it, or ii) provide one per user. */ struct buf dosbuf; /* Used for read/write (see below) */ struct doscmd doscmd; /* Command packet */ char dosfn[DOSMAXFL]; /* Temporary storage for file name */ int dosstate; int doshand; /* Handle of open file (if any) */ int dosrc; /* Last error return code */ /* Should probably eventually read device priority level from CSR, and * store a pointer to the appropriate splN() in dosspl, and then * redefine spldos() to call it. */ dosopen(dev) { if ((dosstate & DS_USE) != 0) { u.u_error = EACCES; return; } dosstate =| DS_USE; DOSREG->d_bae = 0; } /* First error 'should be impossible' since how does the user have * a file handle which leads here unless... */ dosclose(dev) { if ((dosstate & DS_USE) == 0) { u.u_error = ENODEV; return; } if ((dosstate & DS_FOPEN) != 0) dosfclose(); dosstate =& ~DS_USE; } /* Not really a strategy routine per se, as with normal block devices, * but I want to use physio() to set up reads and writes (since it does * all the mapping math, etc), and it needs a 'strategy' routine. * * Problem with physio() is that it's totally word oriented, so it does * the transfer size math in words. So we work around that by doing * the 'bytes transferred' math ourselves. * * Still a bug, though: physio() wants the buffer to be word-aligned, * and an even byte count. How to work around this? */ dosstrat(abp) struct buf *abp; { register struct buf *bp; bp = abp; doscmd.d_cmd = (((bp->b_flags & B_READ) != 0) ? CC_READ : CC_WRITE); doscmd.d_hand = doshand; doscmd.d_parm = 0; doscmd.d_len = u.u_count; doscmd.d_addr = bp->b_addr; doscmd.d_addre = bp->b_xmem; dosstart(DS_RDWR); } dosstart(type) { dosstate =| type; DOSREG->d_ba = &doscmd; DOSREG->d_csr = (C_CMD | C_GO | C_IE); } /* Clearing CSR to stop any further interrupts is snarfed from RT-11 * driver. * * This routine takes care of setting dosrc and i.u_error for read/write * operations. * * Effing physio() sets u.u_count to be bp->b_resid, a word count! * Just bypass (after call to physio returns). */ dosintr() { register struct buf *bp; DOSREG->d_csr = 0; if ((dosstate & (DS_CMD | DS_RDWR)) == 0) { printf("spurious dos: int\n"); return; } if ((dosstate & DS_CMD) != 0) { dosstate =& ~DS_CMD; wakeup(&doscmd); return; } dosstate =& ~DS_RDWR; bp = &dosbuf; bp->b_flags =| B_DONE; if (doscmd.d_rc != 0) { dosrc = doscmd.d_rc; bp->b_flags =| B_ERROR; } wakeup(bp); } dosread(dev) { dosio(dev, B_READ); } /* Not used for now, or tested either (but since it's so similar to * dosread() I'd be pretty surprised if it didn't work), but here it is * anyway. */ doswrite(dev) { dosio(dev, B_WRITE); } /* For length, see comment on dosstrat(). */ dosio(dev, cmd) { int count; if ((dosstate & DS_FOPEN) == 0) { u.u_error = EBADF; return; } count = u.u_count; physio(dosstrat, &dosbuf, dev, cmd); u.u_count = (count - doscmd.d_len); } /* Send a command to the device, and wait for it to be done; always runs in * 'interrupt on completion mode'. Command packet must have been set up prior * to calling this. * * This routine takes care of setting dosrc and i.u_error for all callers. * * If/when the device is extended to allow more than one process to have it * open at a time, since only a single command can be outstanding at any time, * there will have to be a flag bit to control access to the command packet * (in exactly the way raw disk drivers mediate access to the buffer header * used for raw reads); for now, with only a single process, and no * asynchronous I/O in V6, we know there will never be more than one call into * this driver at a time. */ dossend() { spldos(); dosstart(DS_CMD); while ((dosstate & DS_CMD) != 0) sleep(&doscmd, PRICMD); spl0(); if (doscmd.d_rc != 0) { dosrc = doscmd.d_rc; u.u_error = ENXIO; } } /* At the moment, only used for most file operations; eventually * could be extended to support full range of DOS: device semantics. */ dossgtty(dev, v) int *v; { if (v != 0) { *v = dosrc; return; } switch (u.u_arg[0].lobyte) { case DC_CREAT: dosname(CC_CREAT); return; case DC_FOPEN: dosname(CC_OPEN); return; case DC_SEEK: dosseek(); return; case DC_FCLOSE: dosfclose(); return; case DC_DEL: dosname(CC_DEL); return; case DC_HOST: dosname(CC_E11); return; default: u.u_error = EINVAL; return; } } /* Common routine used by all operations which include a file name. * Mode (if any) is in u.u_arg[0].hibyte, file name pointer is in * u.u_arg[1], name length in u.u_arg[2]. * Only open tested, not creat or delete. Given that the code is * entirely in common, they should probably work. */ dosname(ccode) { register char *ap, *np; int len, tlen, c; if ((ccode == CC_OPEN) && ((dosstate & DS_FOPEN) != 0)) { u.u_error = ENOENT; return; } ap = u.u_arg[1]; tlen = u.u_arg[2]; if (tlen > DOSMAXFL) tlen = DOSMAXFL; np = &dosfn[0]; for (len = 0; (len < tlen); len++) { c = fubyte(ap++); if (c == -1) { u.u_error = EFAULT; return; } *np++ = c; if (c == 0) break; } doscmd.d_cmd = ccode; doscmd.d_parm = u.u_arg[0].hibyte; doscmd.d_len = len; doscmd.d_addr = &dosfn[0]; doscmd.d_addre = 0; dossend(); if (ccode != CC_OPEN) return; doshand = doscmd.d_hand; if (doscmd.d_rc == 0) dosstate =| DS_FOPEN; } /* Origin code is in u.u_arg[0].hibyte, and long offset is in * u.u_arg[1]/u.u_arg[2]. * Not tested. */ dosseek() { if ((dosstate & DS_FOPEN) == 0) { u.u_error = ENOENT; return; } doscmd.d_cmd = CC_SEEK; doscmd.d_hand = doshand; doscmd.d_parm = u.u_arg[0].hibyte; doscmd.d_addr = u.u_arg[1]; doscmd.d_addre = u.u_arg[2]; dossend(); } /* Probably should so something intelligent on error return, but * what? */ dosfclose() { if ((dosstate & DS_FOPEN) == 0) { u.u_error = ENOENT; return; } doscmd.d_cmd = CC_CLOSE; doscmd.d_hand = doshand; dossend(); dosstate =& ~DS_FOPEN; }