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)
According to the [PDP-11/20 Handbook (1971)](http://archive.pcjs.org/pubs/dec/pdp11/1120/PDP1120_Handbook_1971.pdf), p. 160,
notable features of PDP-11 BASIC included:
According to the [PDP-11/20 Handbook (1971)](http://archive.pcjs.org/pubs/dec/pdp11/1120/PDP1120_Handbook_1971.pdf),
p. 160, notable features of PDP-11 BASIC included:
- 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)
@ -47,19 +47,19 @@ Debugging Notes
### 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
the 16Kb of installed RAM. After a bit of poking around, I found BASIC's memory sizing code here:
When I first tried to run BASIC in a PDPjs machine, it crashed almost immediately. It was attempting to use memory
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
016146: 022626 CMP (SP)+,(SP)+
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 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
to do an instruction-by-instruction comparison with SIMH.
The code sets R1 to highest possible RAM address and starts scanning backwards for the first valid memory location.
However, 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 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
that contained:
After cloning the [SIMH project](https://github.com/simh/simh) and building the *pdp11* binary, I created a *pdp11.ini*
text file that contained:
ECHO Configuring PDP-11/20 with 16Kb of RAM...
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))
until I remembered that when the PDP-11 accesses an invalid address, it's supposed to trap to vector 000004, and that BASIC
must have modified vector 000004 to jump into the middle of this code.
until I remembered that when the PDP-11 accesses an invalid address, it's supposed to trap to vector 000004, and that
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
`CMP (SP)+,(SP)+` instruction was throwing away the PC and PSW that each trap pushed onto the stack, by effectively adding
4 to SP.
This code fragment was simply marching down the address space until it reached an address that didn't trap. The
odd-looking `CMP (SP)+,(SP)+` instruction was throwing away the PC and PSW that each trap pushed onto the stack, by
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
that, I verified with the PDPjs Debugger that the memory sizing code was working properly:
The problem with PDPjs was that it wasn't generating a trap to vector 000004 when an invalid address was accessed.
After fixing that, I verified with the PDPjs Debugger that the memory sizing code was working properly:
PDPjs v1.30.1
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
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
*previous PC*, so that the handler can eventually return with a simple `RTS PC`.
The first instruction, `MOV @SP,2(SP)`, copies the *previous PC* onto the *previous PSW*, which is where we'll
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
instruction onto the stack, overwriting the original *previous PC*.
Then `@(SP)+,-(SP)` fetches the TRAP instruction while also "popping" the original *previous PC* and then "pushing"
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
by shifting it left again, and then a large offset is added to it, transforming the TRAP instruction (which is now known to be
an *even* value) into a jump table index.
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 by shifting it left again, and then a large offset is added to it, transforming the TRAP instruction (which
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
instruction from the stack, leaving only the *previous PC* on the stack, so that when the TRAP handler is done, it can execute
`RTS PC` to return to the caller.
instruction from the stack, leaving only the *previous PC* on the stack, so that when the TRAP handler is done, it can
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.RK11.RK05 = ["RK", 203, 2, 12, 512, PDP11.RK11.RKDS.RK05 | PDP11.RK11.RKDS.SOK | PDP11.RK11.RKDS.RRDY];
PDP11.RL11.RL02K = ["RL", 512, 2, 40, 256, PDP11.RL11.RLMP.GS_ST.LOCKON | PDP11.RL11.RLMP.GS_BH | PDP11.RL11.RLMP.GS_HO];
PDP11.RX11.RX01 = [
"DX",
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_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
* these or SMALLER values (subject to the limits of what the controller supports, of course).
*/
drive.sName = configDrive[0] + iDrive;
drive.nCylinders = configDrive[1];
drive.nHeads = configDrive[2];
drive.nSectors = configDrive[3];
drive.cbSector = configDrive[4];
drive.status = configDrive[5];
drive.sName = configDrive[i++] + iDrive;
drive.nCylinders = configDrive[i++];
drive.nHeads = configDrive[i++];
drive.nSectors = configDrive[i++];
drive.cbSector = configDrive[i++];
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.
@ -817,7 +821,7 @@ class DriveController extends Component {
* a READY state is assured, and the readData() call shouldn't do anything to change that.
*/
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) {
this.notice("Unable to read the boot sector (" + err + ")");
}

View file

@ -217,7 +217,7 @@ class RX11 extends DriveController {
* @param {Object} drive
* @param {number} iCylinder
* @param {number} iHead
* @param {number} iSector (0-based)
* @param {number} iSector
* @param {number} nWords
* @param {number} addr
* @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 drive = this.aDrives[iDrive];
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);
if (disk) {
this.regRXES |= RX11.RXES.DRDY;
if (this.messageEnabled()) this.printMessage(this.type + ".readSector(" + iCylinder + ":" + iHead + ":" + iSector + ")", true, true);
this.assert(iSector); // RX sector numbers (unlike RK and RL) are supposed to be 1-based
var sector = disk.seek(iCylinder, iHead, iSector, true);
if (this.messageEnabled()) this.printMessage(this.type + ".readSector(" + iCylinder + ":" + iHead + ":" + nSector + ")", true, true);
this.assert(nSector); // RX sector numbers (unlike RK and RL) are supposed to be 1-based
var sector = disk.seek(iCylinder, iHead, nSector, true);
if (sector) {
var i = 0, nBytes = this.abBuffer.length;
while (i < nBytes) {
@ -334,15 +334,15 @@ class RX11 extends DriveController {
var iDrive = (this.regRXCS & RX11.RXCS.UNIT)? 1 : 0;
var drive = this.aDrives[iDrive];
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);
if (disk) {
this.regRXES |= RX11.RXES.DRDY;
if (this.messageEnabled()) this.printMessage(this.type + ".writeSector(" + iCylinder + ":" + iHead + ":" + iSector + ")", true, true);
this.assert(iSector); // RX sector numbers (unlike RK and RL) are supposed to be 1-based
var sector = disk.seek(iCylinder, iHead, iSector, true);
if (this.messageEnabled()) this.printMessage(this.type + ".writeSector(" + iCylinder + ":" + iHead + ":" + nSector + ")", true, true);
this.assert(nSector); // RX sector numbers (unlike RK and RL) are supposed to be 1-based
var sector = disk.seek(iCylinder, iHead, nSector, true);
if (sector) {
if (fDeleted) sector.deleted = true;
var i = 0, nBytes = this.abBuffer.length;