Allow disk devices to specify their own boot requirements
This commit is contained in:
parent
09e5a97765
commit
6be79ae756
4 changed files with 67 additions and 45 deletions
|
|
@ -15,8 +15,8 @@ DEC PDP-11 BASIC
|
|||
|
||||
[](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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 + ")");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue