Allow disk devices to specify their own boot requirements

This commit is contained in:
Jeff Parsons 2017-02-03 09:40:50 -08:00 • committed by Jeff Parsons
commit 6be79ae756
4 changed files with 67 additions and 45 deletions

View file

@ -15,8 +15,8 @@ DEC PDP-11 BASIC
[![DEC-11-AJPB-PB](DEC-11-AJPB-PB.jpg)](DEC-11-AJPB-PB.json) [![DEC-11-AJPB-PB](DEC-11-AJPB-PB.jpg)](DEC-11-AJPB-PB.json)
According to the [PDP-11/20 Handbook (1971)](http://archive.pcjs.org/pubs/dec/pdp11/1120/PDP1120_Handbook_1971.pdf), p. 160, According to the [PDP-11/20 Handbook (1971)](http://archive.pcjs.org/pubs/dec/pdp11/1120/PDP1120_Handbook_1971.pdf),
notable features of PDP-11 BASIC included: p. 160, notable features of PDP-11 BASIC included:
- Use of BASIC statements in immediate mode (no line number) - Use of BASIC statements in immediate mode (no line number)
- Ability to use any BASIC command (RUN, LIST, etc.) in deferred mode (with a line number) - Ability to use any BASIC command (RUN, LIST, etc.) in deferred mode (with a line number)
@ -47,19 +47,19 @@ Debugging Notes
### PDPjs Debugger vs. SIMH ### PDPjs Debugger vs. SIMH
When I first tried to run BASIC in a PDPjs machine, it crashed almost immediately. It was attempting to use memory beyond When I first tried to run BASIC in a PDPjs machine, it crashed almost immediately. It was attempting to use memory
the 16Kb of installed RAM. After a bit of poking around, I found BASIC's memory sizing code here: beyond the 16Kb of installed RAM. After a bit of poking around, I found BASIC's memory sizing code here:
016142: 012701 160000 MOV #160000,R1 016142: 012701 160000 MOV #160000,R1
016146: 022626 CMP (SP)+,(SP)+ 016146: 022626 CMP (SP)+,(SP)+
016150: 014111 MOV -(R1),@R1 016150: 014111 MOV -(R1),@R1
The code sets R1 to highest possible RAM address and starts scanning backwards for the first valid memory location. However, The code sets R1 to highest possible RAM address and starts scanning backwards for the first valid memory location.
the scanning process wasn't clear to me at first glance, and the `CMP (SP)+,(SP)+` was a bit of a head-scratcher, so I decided However, the scanning process wasn't clear to me at first glance, and the `CMP (SP)+,(SP)+` was a bit of a
to do an instruction-by-instruction comparison with SIMH. head-scratcher, so I decided to do an instruction-by-instruction comparison with SIMH.
After cloning the [SIMH project](https://github.com/simh/simh) and building the *pdp11* binary, I created a *pdp11.ini* text file After cloning the [SIMH project](https://github.com/simh/simh) and building the *pdp11* binary, I created a *pdp11.ini*
that contained: text file that contained:
ECHO Configuring PDP-11/20 with 16Kb of RAM... ECHO Configuring PDP-11/20 with 16Kb of RAM...
SET CPU 11/20 SET CPU 11/20
@ -154,15 +154,15 @@ After typing several "do tr" commands, I was surprised to see SIMH execution con
Step expired, PC: 016150 (MOV -(R1),(R1)) Step expired, PC: 016150 (MOV -(R1),(R1))
until I remembered that when the PDP-11 accesses an invalid address, it's supposed to trap to vector 000004, and that BASIC until I remembered that when the PDP-11 accesses an invalid address, it's supposed to trap to vector 000004, and that
must have modified vector 000004 to jump into the middle of this code. BASIC must have modified vector 000004 to jump into the middle of this code.
This code fragment was simply marching down the address space until it reached an address that didn't trap. The odd-looking This code fragment was simply marching down the address space until it reached an address that didn't trap. The
`CMP (SP)+,(SP)+` instruction was throwing away the PC and PSW that each trap pushed onto the stack, by effectively adding odd-looking `CMP (SP)+,(SP)+` instruction was throwing away the PC and PSW that each trap pushed onto the stack, by
4 to SP. effectively adding 4 to SP.
The problem with PDPjs was that it wasn't generating a trap to vector 000004 when an invalid address was accessed. After fixing The problem with PDPjs was that it wasn't generating a trap to vector 000004 when an invalid address was accessed.
that, I verified with the PDPjs Debugger that the memory sizing code was working properly: After fixing that, I verified with the PDPjs Debugger that the memory sizing code was working properly:
PDPjs v1.30.1 PDPjs v1.30.1
Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org> Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
@ -263,18 +263,19 @@ When the code starts, the TRAP instruction has already pushed two words onto the
0(SP): previous PC 0(SP): previous PC
2(SP): previous PSW 2(SP): previous PSW
The first instruction, `MOV @SP,2(SP)`, copies the *previous PC* onto the *previous PSW*, which is where we'll eventually want The first instruction, `MOV @SP,2(SP)`, copies the *previous PC* onto the *previous PSW*, which is where we'll
*previous PC*, so that the handler can eventually return with a simple `RTS PC`. eventually want *previous PC*, so that the handler can eventually return with a simple `RTS PC`.
The next instruction, `SUB #2,@SP`, subtracts 2 from the original *previous PC*, so that it now points to the TRAP instruction. The next instruction, `SUB #2,@SP`, subtracts 2 from the original *previous PC*, so that it now points to the TRAP
instruction.
Then `@(SP)+,-(SP)` fetches the TRAP instruction while also "popping" the original *previous PC* and then "pushing" TRAP Then `@(SP)+,-(SP)` fetches the TRAP instruction while also "popping" the original *previous PC* and then "pushing"
instruction onto the stack, overwriting the original *previous PC*. TRAP instruction onto the stack, overwriting the original *previous PC*.
The next few instructions shift the TRAP right to see if bit 0 is set, and if it is not, then the TRAP instruction is restored The next few instructions shift the TRAP right to see if bit 0 is set, and if it is not, then the TRAP instruction
by shifting it left again, and then a large offset is added to it, transforming the TRAP instruction (which is now known to be is restored by shifting it left again, and then a large offset is added to it, transforming the TRAP instruction (which
an *even* value) into a jump table index. is now known to be an *even* value) into a jump table index.
The final instruction, `MOV @(SP)+,PC`, moves the address at the jump table index into PC, while also removing the TRAP The final instruction, `MOV @(SP)+,PC`, moves the address at the jump table index into PC, while also removing the TRAP
instruction from the stack, leaving only the *previous PC* on the stack, so that when the TRAP handler is done, it can execute instruction from the stack, leaving only the *previous PC* on the stack, so that when the TRAP handler is done, it can
`RTS PC` to return to the caller. execute `RTS PC` to return to the caller.

View file

@ -950,9 +950,26 @@ var PDP11 = {
} }
}; };
PDP11.RX11.RX01 = ["DX", 77, 1, 26, 128, 0]; PDP11.RX11.RX01 = [
PDP11.RK11.RK05 = ["RK", 203, 2, 12, 512, PDP11.RK11.RKDS.RK05 | PDP11.RK11.RKDS.SOK | PDP11.RK11.RKDS.RRDY]; "DX",
PDP11.RL11.RL02K = ["RL", 512, 2, 40, 256, PDP11.RL11.RLMP.GS_ST.LOCKON | PDP11.RL11.RLMP.GS_BH | PDP11.RL11.RLMP.GS_HO]; 77, 1, 26, 128, // disk geometry (CHSN: cylinders, heads, sectors/track, and bytes/sector)
1, 0, 0, 128, // boot code location (cylinder, head, sector index (NOT sector number), and number of bytes)
0 // default drive status
];
PDP11.RK11.RK05 = [
"RK",
203, 2, 12, 512, // disk geometry (CHSN: cylinders, heads, sectors/track, and bytes/sector)
0, 0, 0, 512, // boot code location (cylinder, head, sector index (NOT sector number), and number of bytes)
PDP11.RK11.RKDS.RK05 | PDP11.RK11.RKDS.SOK | PDP11.RK11.RKDS.RRDY
];
PDP11.RL11.RL02K = [
"RL",
512, 2, 40, 256, // disk geometry (CHSN: cylinders, heads, sectors/track, and bytes/sector)
0, 0, 0, 256, // boot code location (cylinder, head, sector index (NOT sector number), and number of bytes)
PDP11.RL11.RLMP.GS_ST.LOCKON | PDP11.RL11.RLMP.GS_BH | PDP11.RL11.RLMP.GS_HO
];
PDP11.ACCESS.READ_WORD = PDP11.ACCESS.WORD | PDP11.ACCESS.READ; // formerly READ_MODE (2) PDP11.ACCESS.READ_WORD = PDP11.ACCESS.WORD | PDP11.ACCESS.READ; // formerly READ_MODE (2)
PDP11.ACCESS.READ_BYTE = PDP11.ACCESS.BYTE | PDP11.ACCESS.READ; // formerly READ_MODE (2) | BYTE_MODE (1) PDP11.ACCESS.READ_BYTE = PDP11.ACCESS.BYTE | PDP11.ACCESS.READ; // formerly READ_MODE (2) | BYTE_MODE (1)

View file

@ -555,12 +555,16 @@ class DriveController extends Component {
* NOTE: We initialize the following drive properties to their MAXIMUMs; disks may have * NOTE: We initialize the following drive properties to their MAXIMUMs; disks may have
* these or SMALLER values (subject to the limits of what the controller supports, of course). * these or SMALLER values (subject to the limits of what the controller supports, of course).
*/ */
drive.sName = configDrive[0] + iDrive; drive.sName = configDrive[i++] + iDrive;
drive.nCylinders = configDrive[1]; drive.nCylinders = configDrive[i++];
drive.nHeads = configDrive[2]; drive.nHeads = configDrive[i++];
drive.nSectors = configDrive[3]; drive.nSectors = configDrive[i++];
drive.cbSector = configDrive[4]; drive.cbSector = configDrive[i++];
drive.status = configDrive[5]; drive.iCylinderBoot = configDrive[i++];
drive.iHeadBoot = configDrive[i++];
drive.iSectorBoot = configDrive[i++];
drive.cbSectorBoot = configDrive[i++];
drive.status = configDrive[i++];
/* /*
* The next group of properties are set by various controller command sequences. * The next group of properties are set by various controller command sequences.
@ -817,7 +821,7 @@ class DriveController extends Component {
* a READY state is assured, and the readData() call shouldn't do anything to change that. * a READY state is assured, and the readData() call shouldn't do anything to change that.
*/ */
this.cpu.setReset(0, true); this.cpu.setReset(0, true);
var err = this.readData(drive, 0, 0, 0, 512, 0x0000, 2); var err = this.readData(drive, drive.iCylinderBoot, drive.iHeadBoot, drive.iSectorBoot, drive.cbSectorBoot, 0x0000, 2);
if (err) { if (err) {
this.notice("Unable to read the boot sector (" + err + ")"); this.notice("Unable to read the boot sector (" + err + ")");
} }

View file

@ -217,7 +217,7 @@ class RX11 extends DriveController {
* @param {Object} drive * @param {Object} drive
* @param {number} iCylinder * @param {number} iCylinder
* @param {number} iHead * @param {number} iHead
* @param {number} iSector (0-based) * @param {number} iSector
* @param {number} nWords * @param {number} nWords
* @param {number} addr * @param {number} addr
* @param {number} inc (normally 2, unless inhibited, in which case it's 0) * @param {number} inc (normally 2, unless inhibited, in which case it's 0)
@ -293,15 +293,15 @@ class RX11 extends DriveController {
var iDrive = (this.regRXCS & RX11.RXCS.UNIT)? 1 : 0; var iDrive = (this.regRXCS & RX11.RXCS.UNIT)? 1 : 0;
var drive = this.aDrives[iDrive]; var drive = this.aDrives[iDrive];
var disk = drive && drive.disk; var disk = drive && drive.disk;
var iCylinder = this.regRXTA & RX11.RXTA.MASK, iHead = 0, iSector = this.regRXSA & RX11.RXSA.MASK; var iCylinder = this.regRXTA & RX11.RXTA.MASK, iHead = 0, nSector = this.regRXSA & RX11.RXSA.MASK;
this.regRXES &= ~(RX11.RXES.CRC | RX11.RXES.PARITY | RX11.RXES.DEL | RX11.RXES.DRDY); this.regRXES &= ~(RX11.RXES.CRC | RX11.RXES.PARITY | RX11.RXES.DEL | RX11.RXES.DRDY);
if (disk) { if (disk) {
this.regRXES |= RX11.RXES.DRDY; this.regRXES |= RX11.RXES.DRDY;
if (this.messageEnabled()) this.printMessage(this.type + ".readSector(" + iCylinder + ":" + iHead + ":" + iSector + ")", true, true); if (this.messageEnabled()) this.printMessage(this.type + ".readSector(" + iCylinder + ":" + iHead + ":" + nSector + ")", true, true);
this.assert(iSector); // RX sector numbers (unlike RK and RL) are supposed to be 1-based this.assert(nSector); // RX sector numbers (unlike RK and RL) are supposed to be 1-based
var sector = disk.seek(iCylinder, iHead, iSector, true); var sector = disk.seek(iCylinder, iHead, nSector, true);
if (sector) { if (sector) {
var i = 0, nBytes = this.abBuffer.length; var i = 0, nBytes = this.abBuffer.length;
while (i < nBytes) { while (i < nBytes) {
@ -334,15 +334,15 @@ class RX11 extends DriveController {
var iDrive = (this.regRXCS & RX11.RXCS.UNIT)? 1 : 0; var iDrive = (this.regRXCS & RX11.RXCS.UNIT)? 1 : 0;
var drive = this.aDrives[iDrive]; var drive = this.aDrives[iDrive];
var disk = drive && drive.disk; var disk = drive && drive.disk;
var iCylinder = this.regRXTA & RX11.RXTA.MASK, iHead = 0, iSector = this.regRXSA & RX11.RXSA.MASK; var iCylinder = this.regRXTA & RX11.RXTA.MASK, iHead = 0, nSector = this.regRXSA & RX11.RXSA.MASK;
this.regRXES &= ~(RX11.RXES.CRC | RX11.RXES.PARITY | RX11.RXES.DEL | RX11.RXES.DRDY); this.regRXES &= ~(RX11.RXES.CRC | RX11.RXES.PARITY | RX11.RXES.DEL | RX11.RXES.DRDY);
if (disk) { if (disk) {
this.regRXES |= RX11.RXES.DRDY; this.regRXES |= RX11.RXES.DRDY;
if (this.messageEnabled()) this.printMessage(this.type + ".writeSector(" + iCylinder + ":" + iHead + ":" + iSector + ")", true, true); if (this.messageEnabled()) this.printMessage(this.type + ".writeSector(" + iCylinder + ":" + iHead + ":" + nSector + ")", true, true);
this.assert(iSector); // RX sector numbers (unlike RK and RL) are supposed to be 1-based this.assert(nSector); // RX sector numbers (unlike RK and RL) are supposed to be 1-based
var sector = disk.seek(iCylinder, iHead, iSector, true); var sector = disk.seek(iCylinder, iHead, nSector, true);
if (sector) { if (sector) {
if (fDeleted) sector.deleted = true; if (fDeleted) sector.deleted = true;
var i = 0, nBytes = this.abBuffer.length; var i = 0, nBytes = this.abBuffer.length;