diff --git a/devices/pdp11/README.md b/devices/pdp11/README.md
index e273d1d63..4d74858df 100644
--- a/devices/pdp11/README.md
+++ b/devices/pdp11/README.md
@@ -12,7 +12,7 @@ PDP-11 Device Configurations
* [Control Panels](panel/)
* Serial Interface for Display Terminals [(DL11)](dl11/)
* High-Speed Paper Tape Reader/Punch [(PC11)](pc11/)
-* Disk Controllers ([RL11](rl11/))
+* Disk Controllers ([RK11](rk11/), [RL11](rl11/))
* [ROM Images](rom/)
Sample machines include a
diff --git a/devices/pdp11/machine/1170/monitor/debugger/machine.xml b/devices/pdp11/machine/1170/monitor/debugger/machine.xml
index 2b01babbd..6ed2cff4d 100644
--- a/devices/pdp11/machine/1170/monitor/debugger/machine.xml
+++ b/devices/pdp11/machine/1170/monitor/debugger/machine.xml
@@ -10,5 +10,6 @@
+
diff --git a/devices/pdp11/machine/1170/monitor/machine.xml b/devices/pdp11/machine/1170/monitor/machine.xml
index 31c78b123..f2029ac90 100644
--- a/devices/pdp11/machine/1170/monitor/machine.xml
+++ b/devices/pdp11/machine/1170/monitor/machine.xml
@@ -9,5 +9,6 @@
+
diff --git a/devices/pdp11/machine/1170/panel/debugger/machine.xml b/devices/pdp11/machine/1170/panel/debugger/machine.xml
index 59f91ad80..99d6016c4 100644
--- a/devices/pdp11/machine/1170/panel/debugger/machine.xml
+++ b/devices/pdp11/machine/1170/panel/debugger/machine.xml
@@ -11,5 +11,6 @@
+
diff --git a/devices/pdp11/machine/1170/panel/machine.xml b/devices/pdp11/machine/1170/panel/machine.xml
index 941d13781..677af169f 100644
--- a/devices/pdp11/machine/1170/panel/machine.xml
+++ b/devices/pdp11/machine/1170/panel/machine.xml
@@ -10,5 +10,6 @@
+
diff --git a/devices/pdp11/rk11/README.md b/devices/pdp11/rk11/README.md
new file mode 100644
index 000000000..7e543fab8
--- /dev/null
+++ b/devices/pdp11/rk11/README.md
@@ -0,0 +1,42 @@
+---
+layout: page
+title: RK11 Disk Controller
+permalink: /devices/pdp11/rk11/
+---
+
+RK11 Disk Controller
+--------------------
+
+The RK11 Disk Controller controls up to eight RK05 disk drives, which in turn read/write [RK03-KA](/disks/dec/rk03/)
+disk cartridges.
+
+Machines containing the [RK11 Component](/modules/pdp11/lib/rk11.js) include:
+
+- [PDP-11/70 Boot Monitor](/devices/pdp11/machine/1170/monitor/) (with [Debugger](/devices/pdp11/machine/1170/monitor/debugger/))
+- [PDP-11/70 with Front Panel](/devices/pdp11/machine/1170/panel/) (with [Debugger](/devices/pdp11/machine/1170/panel/debugger/))
+
+PCjs has archived a selection of [RK03 Disk Images](/disks/dec/rk03/) from [Paul Nankervis](http://skn.noip.me/pdp11/)
+for use by those machines, which are listed in the following RK11 Device XML file:
+
+- [Default](/devices/pdp11/rk11/default.xml)
+
+which is typically referenced by a Machine XML file as:
+
+
+
+Device XML files not only configure a device, but also list all the resource the device will use, and define UI elements
+used to control the device, such as choosing which disks should be "auto-mounted" by the RK11 device. For example:
+
+
+
+ Disk Drive Controls
+
+
+
+
+
+ Load
+
+
+
+
diff --git a/devices/pdp11/rk11/default.xml b/devices/pdp11/rk11/default.xml
new file mode 100644
index 000000000..36aec9d66
--- /dev/null
+++ b/devices/pdp11/rk11/default.xml
@@ -0,0 +1,13 @@
+
+
+ Disk Drive Controls
+
+
+
+
+
+ Load
+
+
+
+
diff --git a/devices/pdp11/rl11/README.md b/devices/pdp11/rl11/README.md
index 2ae4476af..e02c58118 100644
--- a/devices/pdp11/rl11/README.md
+++ b/devices/pdp11/rl11/README.md
@@ -15,8 +15,8 @@ Machines containing the [RL11 Component](/modules/pdp11/lib/pc11.js) include:
- [PDP-11/70 Boot Monitor](/devices/pdp11/machine/1170/monitor/) (with [Debugger](/devices/pdp11/machine/1170/monitor/debugger/))
- [PDP-11/70 with Front Panel](/devices/pdp11/machine/1170/panel/) (with [Debugger](/devices/pdp11/machine/1170/panel/debugger/))
-PCjs has archived a selection of [RL02K Disk Images](/disks/dec/rl02k/) for use by those machines, which are
-listed in the following RL11 Device XML file:
+PCjs has archived a selection of [RL02K Disk Images](/disks/dec/rl02k/) from [Paul Nankervis](http://skn.noip.me/pdp11/)
+for use by those machines, which are listed in the following RL11 Device XML file:
- [Default](/devices/pdp11/rl11/default.xml)
@@ -28,12 +28,12 @@ Device XML files not only configure a device, but also list all the resource the
used to control the device, such as choosing which disks should be "auto-mounted" by the RL11 device. For example:
-
+
Disk Drive Controls
-
+
Load
diff --git a/devices/pdp11/rl11/default.xml b/devices/pdp11/rl11/default.xml
index 70c06c0ac..f891cdca5 100644
--- a/devices/pdp11/rl11/default.xml
+++ b/devices/pdp11/rl11/default.xml
@@ -1,10 +1,10 @@
-
+
Disk Drive Controls
-
+
Load
diff --git a/disks/dec/rk03/README.md b/disks/dec/rk03/README.md
new file mode 100644
index 000000000..ed66d88cf
--- /dev/null
+++ b/disks/dec/rk03/README.md
@@ -0,0 +1,14 @@
+---
+layout: page
+title: DEC RK03 Disk Images
+permalink: /disks/dec/rk03/
+---
+
+DEC RK03 Disk Images
+--------------------
+
+RK03 disks are single-platter cartridges with 203 tracks per side, 12 sectors per track, and a sector size of
+256 words (512 bytes), for a total capacity of 2.38Mb (2,494,464 bytes). They are used with an
+[RK11 Disk Controller](/devices/pdp11/rk11/).
+
+* [XXDP+ Diagnostics](xxdp/) [[source](http://skn.noip.me/pdp11/rk2.dsk)]
diff --git a/disks/dec/rl02k/README.md b/disks/dec/rl02k/README.md
index 105c5ab70..849ff07e4 100644
--- a/disks/dec/rl02k/README.md
+++ b/disks/dec/rl02k/README.md
@@ -10,4 +10,4 @@ DEC RL02K Disk Images
RL02K disks are single-platter cartridges with 512 tracks per side, 40 sectors per track, and a sector size of 256 bytes,
for a total capacity of 10Mb. They are used with an [RL11 Disk Controller](/devices/pdp11/rl11/).
-* [XXDP+ Diagnostics](xxdp/) [[source](http://skn.noip.me/pdp11/)]
+* [XXDP+ Diagnostics](xxdp/) [[source](http://skn.noip.me/pdp11/rl3.dsk)]
diff --git a/docs/pcx86/examples/pcx86-dbg.js b/docs/pcx86/examples/pcx86-dbg.js
index fcd4d776d..7755492b7 100644
--- a/docs/pcx86/examples/pcx86-dbg.js
+++ b/docs/pcx86/examples/pcx86-dbg.js
@@ -43,9 +43,9 @@
http://pcjs.org/modules/shared/lib/state.js (C) Jeff Parsons 2012-2016
http://pcjs.org/modules/shared/lib/save.js (C) Jeff Parsons 2012-2016
*/
-var l,aa,ba={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],5242880:[256,2,40,256],10485760:[512,2,40,256]},p={uo:1,dl:3,vo:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39,"(":40,")":41,"*":42,"+":43,",":44,"-":45,".":46,"/":47,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,":":58,";":59,"<":60,"=":61,">":62,"?":63,"@":64,ue:65,Bi:66,Ci:67,
-Di:68,E:69,Ei:70,Fi:71,Gi:72,Hi:73,Ii:74,Ji:75,Ki:76,Li:77,Mi:78,Ni:79,Oi:80,Q:81,Pi:82,Qi:83,Ri:84,Si:85,Ti:86,Ui:87,Vi:88,Wi:89,og:90,"[":91,"\\":92,"]":93,"^":94,_:95,"`":96,ve:97,ol:98,ql:99,d:100,e:101,Al:102,Bl:103,Cl:104,Dl:105,Tm:106,k:107,Um:108,Ym:109,n:110,en:111,p:112,q:113,r:114,lo:115,t:116,oo:117,po:118,qo:119,x:120,y:121,z:122,"{":123,"|":124,"}":125,"~":126},ca={};ca[186]=p[";"];ca[187]=p["="];ca[188]=p[","];ca[189]=p["-"];ca[190]=p["."];ca[191]=p["/"];ca[192]=p["`"];ca[219]=p["["];
-ca[220]=p["\\"];ca[221]=p["]"];ca[222]=p["'"];ca[173]=p["-"];var da={};da[p["1"]]=p["!"];da[p["2"]]=p["@"];da[p["3"]]=p["#"];da[p["4"]]=p.$;da[p["5"]]=p["%"];da[p["6"]]=p["^"];da[p["7"]]=p["&"];da[p["8"]]=p["*"];da[p["9"]]=p["("];da[p["0"]]=p[")"];da[186]=p[":"];da[187]=p["+"];da[188]=p["<"];da[189]=p._;da[190]=p[">"];da[191]=p["?"];da[192]=p["~"];da[219]=p["{"];da[220]=p["|"];da[221]=p["}"];da[222]=p['"'];da[173]=p._;da[61]=p["+"];da[59]=p[":"];
+var l,aa,ba={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],2494464:[203,2,12,512],5242880:[256,2,40,256],10485760:[512,2,40,256]},p={uo:1,dl:3,vo:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39,"(":40,")":41,"*":42,"+":43,",":44,"-":45,".":46,"/":47,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,":":58,";":59,"<":60,"=":61,">":62,"?":63,"@":64,
+ue:65,Bi:66,Ci:67,Di:68,E:69,Ei:70,Fi:71,Gi:72,Hi:73,Ii:74,Ji:75,Ki:76,Li:77,Mi:78,Ni:79,Oi:80,Q:81,Pi:82,Qi:83,Ri:84,Si:85,Ti:86,Ui:87,Vi:88,Wi:89,og:90,"[":91,"\\":92,"]":93,"^":94,_:95,"`":96,ve:97,ol:98,ql:99,d:100,e:101,Al:102,Bl:103,Cl:104,Dl:105,Tm:106,k:107,Um:108,Ym:109,n:110,en:111,p:112,q:113,r:114,lo:115,t:116,oo:117,po:118,qo:119,x:120,y:121,z:122,"{":123,"|":124,"}":125,"~":126},ca={};ca[186]=p[";"];ca[187]=p["="];ca[188]=p[","];ca[189]=p["-"];ca[190]=p["."];ca[191]=p["/"];ca[192]=p["`"];
+ca[219]=p["["];ca[220]=p["\\"];ca[221]=p["]"];ca[222]=p["'"];ca[173]=p["-"];var da={};da[p["1"]]=p["!"];da[p["2"]]=p["@"];da[p["3"]]=p["#"];da[p["4"]]=p.$;da[p["5"]]=p["%"];da[p["6"]]=p["^"];da[p["7"]]=p["&"];da[p["8"]]=p["*"];da[p["9"]]=p["("];da[p["0"]]=p[")"];da[186]=p[":"];da[187]=p["+"];da[188]=p["<"];da[189]=p._;da[190]=p[">"];da[191]=p["?"];da[192]=p["~"];da[219]=p["{"];da[220]=p["|"];da[221]=p["}"];da[222]=p['"'];da[173]=p._;da[61]=p["+"];da[59]=p[":"];
function ea(a,b){var c;if(a){b||(b=10);var d=a.charAt(0),e=0>=1;return c}function ha(a,b,c){var d="";if(!b||4>=8;return(c?"0b":"")+d}
function ia(a,b,c){var d="";b?11>=3;return(c?"0o":"")+d}function r(a,b,c){var d="";b?8=e?48:55),d=String.fromCharCode(e)+d;a>>=4}return(c?"0x":"")+d}function u(a){return r(a,2,!0)}function v(a){return r(a,4,!0)}
diff --git a/docs/pcx86/examples/pcx86.js b/docs/pcx86/examples/pcx86.js
index 9a95d6116..3f25fa2a7 100644
--- a/docs/pcx86/examples/pcx86.js
+++ b/docs/pcx86/examples/pcx86.js
@@ -43,9 +43,9 @@
http://pcjs.org/modules/shared/lib/state.js (C) Jeff Parsons 2012-2016
http://pcjs.org/modules/shared/lib/save.js (C) Jeff Parsons 2012-2016
*/
-var l,ba,ca={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],5242880:[256,2,40,256],10485760:[512,2,40,256]},n={Tm:1,Sj:3,Um:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39,"(":40,")":41,"*":42,"+":43,",":44,"-":45,".":46,"/":47,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,":":58,";":59,"<":60,"=":61,">":62,"?":63,"@":64,Jd:65,wh:66,xh:67,
-yh:68,E:69,zh:70,Ah:71,Bh:72,Ch:73,Dh:74,Eh:75,Fh:76,Gh:77,Ih:78,Kh:79,Mh:80,Q:81,Ph:82,Rh:83,Th:84,Vh:85,Xh:86,Zh:87,ai:88,ci:89,vf:90,"[":91,"\\":92,"]":93,"^":94,_:95,"`":96,Ld:97,bk:98,ck:99,d:100,e:101,mk:102,nk:103,pk:104,qk:105,Bl:106,k:107,Cl:108,Fl:109,n:110,Kl:111,p:112,q:113,r:114,Mm:115,t:116,Pm:117,Qm:118,Rm:119,x:120,y:121,z:122,"{":123,"|":124,"}":125,"~":126},da={};da[186]=n[";"];da[187]=n["="];da[188]=n[","];da[189]=n["-"];da[190]=n["."];da[191]=n["/"];da[192]=n["`"];da[219]=n["["];
-da[220]=n["\\"];da[221]=n["]"];da[222]=n["'"];da[173]=n["-"];var p={};p[n["1"]]=n["!"];p[n["2"]]=n["@"];p[n["3"]]=n["#"];p[n["4"]]=n.$;p[n["5"]]=n["%"];p[n["6"]]=n["^"];p[n["7"]]=n["&"];p[n["8"]]=n["*"];p[n["9"]]=n["("];p[n["0"]]=n[")"];p[186]=n[":"];p[187]=n["+"];p[188]=n["<"];p[189]=n._;p[190]=n[">"];p[191]=n["?"];p[192]=n["~"];p[219]=n["{"];p[220]=n["|"];p[221]=n["}"];p[222]=n['"'];p[173]=n._;p[61]=n["+"];p[59]=n[":"];
+var l,ba,ca={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],2494464:[203,2,12,512],5242880:[256,2,40,256],10485760:[512,2,40,256]},n={Tm:1,Sj:3,Um:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39,"(":40,")":41,"*":42,"+":43,",":44,"-":45,".":46,"/":47,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,":":58,";":59,"<":60,"=":61,">":62,"?":63,"@":64,
+Jd:65,wh:66,xh:67,yh:68,E:69,zh:70,Ah:71,Bh:72,Ch:73,Dh:74,Eh:75,Fh:76,Gh:77,Ih:78,Kh:79,Mh:80,Q:81,Ph:82,Rh:83,Th:84,Vh:85,Xh:86,Zh:87,ai:88,ci:89,vf:90,"[":91,"\\":92,"]":93,"^":94,_:95,"`":96,Ld:97,bk:98,ck:99,d:100,e:101,mk:102,nk:103,pk:104,qk:105,Bl:106,k:107,Cl:108,Fl:109,n:110,Kl:111,p:112,q:113,r:114,Mm:115,t:116,Pm:117,Qm:118,Rm:119,x:120,y:121,z:122,"{":123,"|":124,"}":125,"~":126},da={};da[186]=n[";"];da[187]=n["="];da[188]=n[","];da[189]=n["-"];da[190]=n["."];da[191]=n["/"];da[192]=n["`"];
+da[219]=n["["];da[220]=n["\\"];da[221]=n["]"];da[222]=n["'"];da[173]=n["-"];var p={};p[n["1"]]=n["!"];p[n["2"]]=n["@"];p[n["3"]]=n["#"];p[n["4"]]=n.$;p[n["5"]]=n["%"];p[n["6"]]=n["^"];p[n["7"]]=n["&"];p[n["8"]]=n["*"];p[n["9"]]=n["("];p[n["0"]]=n[")"];p[186]=n[":"];p[187]=n["+"];p[188]=n["<"];p[189]=n._;p[190]=n[">"];p[191]=n["?"];p[192]=n["~"];p[219]=n["{"];p[220]=n["|"];p[221]=n["}"];p[222]=n['"'];p[173]=n._;p[61]=n["+"];p[59]=n[":"];
function ea(a,b){var c;if(a){b||(b=10);var d=a.charAt(0),e=0=e?48:55),d=String.fromCharCode(e)+d;a>>=4}return(c?"0x":"")+d}function ga(a,b){var c=a,d=a.lastIndexOf("/");0<=d&&(c=a.substr(d+1));d=c.indexOf("&");0":">",'"':""","'":"'"};function ka(a){return a.replace(/[&<>"']/g,function(a){return ja[a]})}function la(a){return String.prototype.trim?a.trim():a.replace(/^\s+|\s+$/g,"")}
diff --git a/modules/pdp11/README.md b/modules/pdp11/README.md
index 75a973959..8a2703415 100644
--- a/modules/pdp11/README.md
+++ b/modules/pdp11/README.md
@@ -31,6 +31,7 @@ PDPjs is currently comprised of the following non-shared components, as listed i
* [serialport.js](/modules/pdp11/lib/serialport.js)
* [pc11.js](/modules/pdp11/lib/pc11.js)
* [disk.js](/modules/pdp11/lib/disk.js)
+* [rk11.js](/modules/pdp11/lib/rk11.js)
* [rl11.js](/modules/pdp11/lib/rl11.js)
* [debugger.js](/modules/pdp11/lib/debugger.js)
* [computer.js](/modules/pdp11/lib/computer.js)
diff --git a/modules/pdp11/lib/defines.js b/modules/pdp11/lib/defines.js
index ba86d7025..c61a5969d 100644
--- a/modules/pdp11/lib/defines.js
+++ b/modules/pdp11/lib/defines.js
@@ -476,6 +476,15 @@ var PDP11 = {
RLMP: 0o174406, // RL11 Multi-Purpose Register
RLBE: 0o174410, // RL11 Bus (Address) Extension Register (RLV12 controller only)
+ RKDS: 0o177400, // RK11 Drive Status Register
+ RKER: 0o177402, // RK11 Error Register
+ RKCS: 0o177404, // RK11 Control Status Register
+ RKWC: 0o177406, // RK11 Word Count Register
+ RKBA: 0o177410, // RK11 Bus Address Register
+ RKDA: 0o177412, // RK11 Disk Address Register
+ // NOTE: 177414 is unused
+ RKDB: 0o177416, // RK11 Data Buffer Register
+
LKS: 0o177546, // KW11-L Clock Status
PRS: 0o177550, // PC11 (and PR11) Reader Status Register
@@ -644,6 +653,84 @@ var PDP11 = {
BAUD: 600
},
},
+ RK11: { // RK11 Disk Controller
+ PRI: 5,
+ VEC: 0o220,
+ RKDS: { // 177400: Drive Status Register
+ SC: 0x000F, // Sector Counter
+ SCESA: 0x0010, // Sector Counter Equals Sector Address
+ WPS: 0x0020, // Write Protected Status (set if write-protected)
+ RRDY: 0x0040, // Read/Write/Seek Ready
+ DRDY: 0x0080, // Drive Ready
+ SOK: 0x0100, // Sector Counter OK
+ SIN: 0x0200, // Seek Incomplete
+ DRU: 0x0400, // Drive Unsafe
+ RK05: 0x0800, // RK05 is the selected disk drive (always set)
+ DPL: 0x1000, // Drive Power Low
+ ID: 0xE000, // Drive ID (logical drive number of an interrupting drive)
+ SHIFT: {
+ ID: 13
+ }
+ },
+ RKER: { // 177402: Error Register
+ WCE: 0x0001, // Write Check Error
+ CSE: 0x0002, // Checksum Error
+ UNUSED: 0x001C, // unused (returns zero)
+ NXS: 0x0020, // Non-Existent Sector
+ NXC: 0x0040, // Non-Existent Cylinder
+ NXD: 0x0080, // Non-Existent Disk
+ TE: 0x0100, // Timing Error
+ DLT: 0x0200, // Date Late
+ NXM: 0x0400, // Non-Existent Memory
+ PGE: 0x0800, // Programming Error
+ SKE: 0x1000, // Seek Error
+ WLO: 0x2000, // Write Lock-Out Violation
+ OVR: 0x4000, // Overrun
+ DRE: 0x8000 // Drive Error
+ },
+ RKCS: { // 177404: Control Status Register
+ GO: 0x0001, // Go (W/O)
+ FUNC: 0x000E, // Function Code (F2,F1,F0) (R/W)
+ MEX: 0x0030, // Memory Extension (R/W)
+ IE: 0x0040, // Interrupt Enable (R/W)
+ CRDY: 0x0080, // Controller Ready (R/O)
+ SSE: 0x0100, // Stop on Soft Error (R/W)
+ EXB: 0x0200, // Extra Bit (R/W)
+ FMT: 0x0400, // Format (R/W)
+ IBA: 0x0800, // Inhibit RKBA Increment (R/W)
+ SCP: 0x2000, // Search Complete (R/O)
+ HE: 0x4000, // Hard Error (R/O)
+ ERR: 0x8000, // Composite Error (R/O) (set when any RKER bit is set)
+ UNUSED: 0x1200, // unused
+ RMASK: 0xEFFE, // bits readable
+ WMASK: 0x0F7F, // bits writable
+ SHIFT: {
+ FUNC: 1,
+ MEX: 4
+ }
+ },
+ RKDA: { // 177412: Disk Address Register
+ SA: 0x000F, // Sector Address
+ HS: 0x0010, // Head Select (aka SUR: clear for upper disk head, set for lower)
+ CA: 0x1FE0, // Cylinder Address (aka CYL ADDR)
+ DS: 0xE000, // Drive Select (aka DR SEL)
+ SHIFT: {
+ HS: 4,
+ CA: 5,
+ DS: 13
+ }
+ },
+ FUNC: { // NOTE: These function codes are pre-shifted to read/write directly from/to RKCS.FUNC
+ CRESET: 0b0000, // Controller Reset
+ WRITE: 0b0010, // Write
+ READ: 0b0100, // Read
+ WCHK: 0b0110, // Write Check
+ SEEK: 0b1000, // Seek
+ RCHK: 0b1010, // Read Check
+ DRESET: 0b1100, // Drive Reset
+ WLOCK: 0b1110 // Write Lock
+ }
+ },
RL11: { // RL11 Disk Controller
PRI: 5,
VEC: 0o160,
@@ -663,6 +750,7 @@ var PDP11 = {
WMASK: 0x03FE, // bits writable
SHIFT: {
FUNC: 1,
+ BAE: 4,
DS: 8
}
},
diff --git a/modules/pdp11/lib/device.js b/modules/pdp11/lib/device.js
index 96abf095e..fa33c1b73 100644
--- a/modules/pdp11/lib/device.js
+++ b/modules/pdp11/lib/device.js
@@ -1167,6 +1167,10 @@ DevicePDP11.init = function()
device = new RL11(parmsDevice);
Component.bindComponentControls(device, eDevice, PDP11.APPCLASS);
break;
+ case 'rk11':
+ device = new RK11(parmsDevice);
+ Component.bindComponentControls(device, eDevice, PDP11.APPCLASS);
+ break;
}
}
};
diff --git a/modules/pdp11/lib/disk.js b/modules/pdp11/lib/disk.js
index 663b9693c..f17028796 100644
--- a/modules/pdp11/lib/disk.js
+++ b/modules/pdp11/lib/disk.js
@@ -73,7 +73,7 @@ if (NODE) {
*
* @constructor
* @extends Component
- * @param {RL11} controller
+ * @param {RK11|RL11} controller
* @param {Object} drive
* @param {string} mode
*/
diff --git a/modules/pdp11/lib/messages.js b/modules/pdp11/lib/messages.js
index e9e7d72ac..080177085 100644
--- a/modules/pdp11/lib/messages.js
+++ b/modules/pdp11/lib/messages.js
@@ -45,7 +45,8 @@ var MessagesPDP11 = {
KEYBOARD: 0x00010000,
KEYS: 0x00020000,
PAPER: 0x00100000,
- DISK: 0x00400000,
+ DISK: 0x00200000,
+ RK11: 0x00400000,
RL11: 0x00800000,
SERIAL: 0x01000000,
SPEAKER: 0x02000000,
@@ -84,6 +85,7 @@ MessagesPDP11.CATEGORIES = {
"key": MessagesPDP11.KEYS, // using "key" instead of "keys", since the latter is a method on JavasScript objects
"paper": MessagesPDP11.PAPER,
"disk": MessagesPDP11.DISK,
+ "rk11": MessagesPDP11.RK11,
"rl11": MessagesPDP11.RL11,
"serial": MessagesPDP11.SERIAL,
"speaker": MessagesPDP11.SPEAKER,
diff --git a/modules/pdp11/lib/rk11.js b/modules/pdp11/lib/rk11.js
new file mode 100644
index 000000000..54e8d426b
--- /dev/null
+++ b/modules/pdp11/lib/rk11.js
@@ -0,0 +1,1281 @@
+/**
+ * @fileoverview Implements the RK11 Disk Controller (for RK03 Disks)
+ * @author Jeff Parsons
+ * @copyright © Jeff Parsons 2012-2016
+ *
+ * This file is part of PCjs, a computer emulation software project at .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every modified copy of this work
+ * and to display that copyright notice when the software starts running; see COPYRIGHT in
+ * .
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of PCjs
+ * for purposes of the GNU General Public License, and the author does not claim any copyright
+ * as to their contents.
+ */
+
+"use strict";
+
+if (NODE) {
+ var str = require("../../shared/lib/strlib");
+ var web = require("../../shared/lib/weblib");
+ var DiskAPI = require("../../shared/lib/diskapi");
+ var Component = require("../../shared/lib/component");
+ var State = require("../../shared/lib/state");
+ var PDP11 = require("./defines");
+ var MessagesPDP11 = require("./messages");
+ var DiskPDP11 = require("./disk");
+}
+
+/**
+ * RK11(parms)
+ *
+ * The RK11 component has the following component-specific (parms) properties:
+ *
+ * autoMount: one or more JSON-encoded objects, each containing 'name' and 'path' properties
+ *
+ * @constructor
+ * @extends Component
+ * @param {Object} parms
+ */
+function RK11(parms)
+{
+ Component.call(this, "RK11", parms, RK11, MessagesPDP11.RK11);
+
+ /*
+ * We record any 'autoMount' object now, but we no longer parse it until initBus(),
+ * because the Computer's getMachineParm() service may have an override for us.
+ */
+ this.configMount = parms['autoMount'] || null;
+ this.cAutoMount = 0;
+
+ /*
+ * The RK11 has three Drive Select bits, representing up to eight drives.
+ */
+ this.nDrives = 8;
+ this.aDrives = new Array(this.nDrives);
+
+ this.fLocalDisks = (!web.isMobile() && window && 'FileReader' in window);
+}
+
+Component.subclass(RK11);
+
+/*
+ * There's nothing super special about these values, except that NONE should be falsey and the others should not.
+ */
+RK11.SOURCE = {
+ NONE: "",
+ LOCAL: "?",
+ REMOTE: "??"
+};
+
+/**
+ * setBinding(sType, sBinding, control, sValue)
+ *
+ * @this {RK11}
+ * @param {string|null} sType is the type of the HTML control (eg, "button", "list", "text", etc)
+ * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listDisks")
+ * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
+ * @param {string} [sValue] optional data value
+ * @return {boolean} true if binding was successful, false if unrecognized binding request
+ */
+RK11.prototype.setBinding = function(sType, sBinding, control, sValue)
+{
+ var rk11 = this;
+
+ switch (sBinding) {
+
+ case "listDisks":
+ this.bindings[sBinding] = control;
+
+ control.onchange = function onChangeListDisks(event) {
+ var controlDesc = rk11.bindings["descDisk"];
+ var controlOption = control.options[control.selectedIndex];
+ if (controlDesc && controlOption) {
+ var dataValue = {};
+ var sValue = controlOption.getAttribute("data-value");
+ if (sValue) {
+ try {
+ dataValue = eval("(" + sValue + ")");
+ } catch (e) {
+ Component.error("RK11 option error: " + e.message);
+ }
+ }
+ var sHTML = dataValue['desc'];
+ if (sHTML === undefined) sHTML = "";
+ var sHRef = dataValue['href'];
+ if (sHRef !== undefined) sHTML = "" + sHTML + "";
+ controlDesc.innerHTML = sHTML;
+ }
+ };
+ return true;
+
+ case "descDisk":
+ case "listDrives":
+ this.bindings[sBinding] = control;
+ /*
+ * I tried going with onclick instead of onchange, so that if you wanted to confirm what's
+ * loaded in a particular drive, you could click the drive control without having to change it.
+ * However, that doesn't seem to work for all browsers, so I've reverted to onchange.
+ */
+ control.onchange = function onChangeListDrives(event) {
+ var iDrive = str.parseInt(control.value, 10);
+ if (iDrive != null) rk11.displayDisk(iDrive);
+ };
+ return true;
+
+ case "loadDrive":
+ this.bindings[sBinding] = control;
+
+ control.onclick = function onClickLoadDrive(event) {
+ var controlDisks = rk11.bindings["listDisks"];
+ if (controlDisks) {
+ var sDiskName = controlDisks.options[controlDisks.selectedIndex].text;
+ var sDiskPath = controlDisks.value;
+ rk11.loadSelectedDrive(sDiskName, sDiskPath);
+ }
+ };
+ return true;
+
+ case "saveDrive":
+ /*
+ * Yes, technically, this feature does not require "Local disk support" (which is really a reference
+ * to FileReader support), but since fLocalDisks is also false for all mobile devices, and since there
+ * is an "orthogonality" to disabling both features in tandem, let's just let it slide, OK?
+ */
+ if (!this.fLocalDisks) {
+ if (DEBUG) this.log("Local disk support not available");
+ /*
+ * We could also simply hide the control; eg:
+ *
+ * control.style.display = "none";
+ *
+ * but removing the control altogether seems better.
+ */
+ control.parentNode.removeChild(/** @type {Node} */ (control));
+ return false;
+ }
+
+ this.bindings[sBinding] = control;
+
+ control.onclick = function onClickSaveDrive(event) {
+ var controlDrives = rk11.bindings["listDrives"];
+ if (controlDrives && controlDrives.options && rk11.aDrives) {
+ var iDriveSelected = str.parseInt(controlDrives.value, 10) || 0;
+ var drive = rk11.aDrives[iDriveSelected];
+ if (drive) {
+ /*
+ * Note the similarity (and hence factoring opportunity) between this code and the HDC's "saveHD*" binding.
+ */
+ var disk = drive.disk;
+ if (disk) {
+ if (DEBUG) rk11.println("saving disk " + disk.sDiskPath + "...");
+ var sAlert = web.downloadFile(disk.encodeAsBase64(), "octet-stream", true, disk.sDiskFile.replace(".json", ".img"));
+ web.alertUser(sAlert);
+ } else {
+ rk11.notice("No disk loaded in drive.");
+ }
+ } else {
+ rk11.notice("No disk drive selected.");
+ }
+ }
+ };
+ return true;
+
+ case "mountDrive":
+ if (!this.fLocalDisks) {
+ if (DEBUG) this.log("Local disk support not available");
+ /*
+ * We could also simply hide the control; eg:
+ *
+ * control.style.display = "none";
+ *
+ * but removing the control altogether seems better.
+ */
+ control.parentNode.removeChild(/** @type {Node} */ (control));
+ return false;
+ }
+
+ this.bindings[sBinding] = control;
+
+ /*
+ * Enable "Mount" button only if a file is actually selected
+ */
+ control.addEventListener('change', function() {
+ var fieldset = control.children[0];
+ var files = fieldset.children[0].files;
+ var submit = fieldset.children[1];
+ submit.disabled = !files.length;
+ });
+
+ control.onsubmit = function(event) {
+ var file = event.currentTarget[1].files[0];
+ if (file) {
+ var sDiskPath = file.name;
+ var sDiskName = str.getBaseName(sDiskPath, true);
+ rk11.loadSelectedDrive(sDiskName, sDiskPath, file);
+ }
+ /*
+ * Prevent reloading of web page after form submission
+ */
+ return false;
+ };
+ return true;
+
+ default:
+ break;
+ }
+ return false;
+};
+
+/**
+ * initBus(cmp, bus, cpu, dbg)
+ *
+ * @this {RK11}
+ * @param {ComputerPDP11} cmp
+ * @param {BusPDP11} bus
+ * @param {CPUStatePDP11} cpu
+ * @param {DebuggerPDP11} dbg
+ */
+RK11.prototype.initBus = function(cmp, bus, cpu, dbg)
+{
+ this.cmp = cmp;
+ this.bus = bus;
+ this.cpu = cpu;
+ this.dbg = dbg;
+
+ this.configMount = this.cmp.getMachineParm('autoMount') || this.configMount;
+
+ if (this.configMount) {
+ if (typeof this.configMount == "string") {
+ try {
+ /*
+ * The most likely source of any exception will be right here, where we're parsing
+ * this JSON-encoded data.
+ */
+ this.configMount = eval("(" + this.configMount + ")");
+ } catch (e) {
+ Component.error("RK11 auto-mount error: " + e.message + " (" + this.configMount + ")");
+ this.configMount = null;
+ }
+ }
+ }
+
+ /*
+ * If we didn't need auto-mount support, we could defer controller initialization until we received
+ * a powerUp() notification, at which point reset() would call initController(), or restore() would restore
+ * the controller; in that case, all we'd need to do here is call setReady().
+ */
+ this.initController();
+
+ this.triggerInterrupt = this.cpu.addTrigger(PDP11.RK11.VEC, PDP11.RK11.PRI);
+
+ bus.addIOTable(this, RK11.UNIBUS_IOTABLE);
+ bus.addResetHandler(this.reset.bind(this));
+
+ this.addDisk("None", RK11.SOURCE.NONE, true);
+ if (this.fLocalDisks) this.addDisk("Local Disk", RK11.SOURCE.LOCAL);
+ this.addDisk("Remote Disk", RK11.SOURCE.REMOTE);
+
+ if (!this.autoMount()) this.setReady();
+};
+
+/**
+ * getDriveName(iDrive)
+ *
+ * @this {RK11}
+ * @param {number} iDrive
+ * @return {string}
+ */
+RK11.prototype.getDriveName = function(iDrive)
+{
+ return "RK" + iDrive;
+};
+
+/**
+ * getDriveNumber(sDrive)
+ *
+ * @this {RK11}
+ * @param {string} sDrive
+ * @return {number} (0-3, or -1 if error)
+ */
+RK11.prototype.getDriveNumber = function(sDrive)
+{
+ var iDrive = -1;
+ if (sDrive) {
+ iDrive = sDrive.charCodeAt(sDrive.length - 1) - 0x30;
+ if (iDrive < 0 || iDrive > 9) iDrive = -1;
+ }
+ return iDrive;
+};
+
+/**
+ * powerUp(data, fRepower)
+ *
+ * @this {RK11}
+ * @param {Object|null} data
+ * @param {boolean} [fRepower]
+ * @return {boolean} true if successful, false if failure
+ */
+RK11.prototype.powerUp = function(data, fRepower)
+{
+ if (!fRepower) {
+ if (!data || !this.restore) {
+ this.reset();
+ if (this.cmp.fReload) {
+ /*
+ * If the computer's fReload flag is set, we're required to toss all currently
+ * loaded disks and remount all disks specified in the auto-mount configuration.
+ */
+ this.unloadAllDrives(true);
+ this.autoMount(true);
+ }
+ } else {
+ if (!this.restore(data)) return false;
+ }
+ /*
+ * Populate the HTML controls to match the actual (well, um, specified) number of floppy drives.
+ */
+ var controlDrives;
+ if ((controlDrives = this.bindings['listDrives'])) {
+ while (controlDrives.firstChild) {
+ controlDrives.removeChild(controlDrives.firstChild);
+ }
+ controlDrives.value = "";
+ for (var iDrive = 0; iDrive < this.nDrives; iDrive++) {
+ var controlOption = document.createElement("option");
+ controlOption.value = iDrive;
+ controlOption.text = this.getDriveName(iDrive);
+ controlDrives.appendChild(controlOption);
+ }
+ if (this.nDrives > 0) {
+ controlDrives.value = "0";
+ this.displayDisk(0);
+ }
+ }
+ }
+ return true;
+};
+
+/**
+ * powerDown(fSave, fShutdown)
+ *
+ * @this {RK11}
+ * @param {boolean} [fSave]
+ * @param {boolean} [fShutdown]
+ * @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
+ */
+RK11.prototype.powerDown = function(fSave, fShutdown)
+{
+ return fSave? this.save() : true;
+};
+
+/**
+ * reset()
+ *
+ * @this {RK11}
+ */
+RK11.prototype.reset = function()
+{
+ this.initController();
+};
+
+/**
+ * save()
+ *
+ * This implements save support for the RK11 component.
+ *
+ * @this {RK11}
+ * @return {Object}
+ */
+RK11.prototype.save = function()
+{
+ var state = new State(this);
+ // state.set(0, this.saveController());
+ return state.data();
+};
+
+/**
+ * restore(data)
+ *
+ * This implements restore support for the RK11 component.
+ *
+ * @this {RK11}
+ * @param {Object} data
+ * @return {boolean} true if successful, false if failure
+ */
+RK11.prototype.restore = function(data)
+{
+ return this.initController(data[0]);
+};
+
+/**
+ * saveController()
+ *
+ * @this {RK11}
+ * @return {Array}
+ */
+RK11.prototype.saveController = function()
+{
+ return [];
+};
+
+/**
+ * autoMount(fRemount)
+ *
+ * @this {RK11}
+ * @param {boolean} [fRemount] is true if we're remounting all auto-mounted disks
+ * @return {boolean} true if one or more disk images are being auto-mounted, false if none
+ */
+RK11.prototype.autoMount = function(fRemount)
+{
+ if (!fRemount) this.cAutoMount = 0;
+ if (this.configMount) {
+ for (var sDrive in this.configMount) {
+ var configDrive = this.configMount[sDrive];
+ var sDiskPath = configDrive['path'] || "";
+ var sDiskName = configDrive['name'] || this.findDisk(sDiskPath);
+ if (sDiskPath && sDiskName) {
+ var iDrive = this.getDriveNumber(sDrive);
+ if (iDrive >= 0 && iDrive < this.aDrives.length) {
+ if (!this.loadDrive(iDrive, sDiskName, sDiskPath, true) && fRemount) {
+ this.setReady(false);
+ }
+ continue;
+ }
+ }
+ this.notice("Incorrect auto-mount settings for drive " + sDrive + " (" + JSON.stringify(configDrive) + ")");
+ }
+ }
+ return !!this.cAutoMount;
+};
+
+/**
+ * loadSelectedDrive(sDiskName, sDiskPath, file)
+ *
+ * @this {RK11}
+ * @param {string} sDiskName
+ * @param {string} sDiskPath
+ * @param {File} [file] is set if there's an associated File object
+ */
+RK11.prototype.loadSelectedDrive = function(sDiskName, sDiskPath, file)
+{
+ var controlDrives = this.bindings["listDrives"];
+ var iDrive = controlDrives && str.parseInt(controlDrives.value, 10);
+
+ if (iDrive === undefined || iDrive < 0 || iDrive >= this.aDrives.length) {
+ this.notice("Unable to load the selected drive");
+ return;
+ }
+
+ if (!sDiskPath) {
+ this.unloadDrive(iDrive);
+ return;
+ }
+
+ if (sDiskPath == RK11.SOURCE.LOCAL) {
+ this.notice('Use "Choose File" and "Mount" to select and load a local disk.');
+ return;
+ }
+
+ /*
+ * If the special RK11.SOURCE.REMOTE path is selected, then we want to prompt the user for a URL.
+ * Oh, and make sure we pass an empty string as the 2nd parameter to prompt(), so that IE won't display
+ * "undefined" -- because after all, undefined and "undefined" are EXACTLY the same thing, right?
+ *
+ * TODO: This is literally all I've done to support remote disk images. There's probably more
+ * I should do, like dynamically updating "listDisks" to include new entries, and adding new entries
+ * to the save/restore data.
+ */
+ if (sDiskPath == RK11.SOURCE.REMOTE) {
+ sDiskPath = window.prompt("Enter the URL of a remote disk image.", "") || "";
+ if (!sDiskPath) return;
+ sDiskName = str.getBaseName(sDiskPath);
+ this.status("Attempting to load " + sDiskPath + " as \"" + sDiskName + "\"");
+ this.sDiskSource = RK11.SOURCE.REMOTE;
+ }
+ else {
+ this.sDiskSource = sDiskPath;
+ }
+
+ this.loadDrive(iDrive, sDiskName, sDiskPath, false, file);
+};
+
+/**
+ * loadDrive(iDrive, sDiskName, sDiskPath, fAutoMount, file)
+ *
+ * NOTE: If sDiskPath is already loaded, nothing needs to be done.
+ *
+ * @this {RK11}
+ * @param {number} iDrive
+ * @param {string} sDiskName
+ * @param {string} sDiskPath
+ * @param {boolean} [fAutoMount]
+ * @param {File} [file] is set if there's an associated File object
+ * @return {number} 1 if disk loaded, 0 if queued up (or busy), -1 if already loaded
+ */
+RK11.prototype.loadDrive = function(iDrive, sDiskName, sDiskPath, fAutoMount, file)
+{
+ var nResult = -1;
+ var drive = this.aDrives[iDrive];
+
+ if (drive.sDiskPath.toLowerCase() != sDiskPath.toLowerCase()) {
+
+ nResult++;
+ this.unloadDrive(iDrive, true);
+
+ if (drive.fBusy) {
+ this.notice("RK11 busy");
+ }
+ else {
+ // this.status("disk queued: " + sDiskName);
+ drive.fBusy = true;
+ if (fAutoMount) {
+ drive.fAutoMount = true;
+ this.cAutoMount++;
+ if (this.messageEnabled()) this.printMessage("auto-loading disk: " + sDiskName);
+ }
+ drive.fLocal = !!file;
+ var disk = new DiskPDP11(this, drive, DiskAPI.MODE.PRELOAD);
+ if (disk.load(sDiskName, sDiskPath, file, this.finishLoadDrive)) {
+ nResult++;
+ }
+ }
+ }
+ return nResult;
+};
+
+/**
+ * finishLoadDrive(drive, disk, sDiskName, sDiskPath, fAutoMount)
+ *
+ * The disk parameter is set if the disk was successfully loaded, null if not.
+ *
+ * @this {RK11}
+ * @param {Object} drive
+ * @param {DiskPDP11} disk
+ * @param {string} sDiskName
+ * @param {string} sDiskPath
+ * @param {boolean} [fAutoMount]
+ */
+RK11.prototype.finishLoadDrive = function onLoadDrive(drive, disk, sDiskName, sDiskPath, fAutoMount)
+{
+ var aDiskInfo;
+
+ drive.fBusy = false;
+
+ if (disk) {
+ /*
+ * We shouldn't mount the disk unless we're sure the drive is able to handle it.
+ */
+ aDiskInfo = disk.info();
+ if (disk && aDiskInfo[0] > drive.nCylinders || aDiskInfo[1] > drive.nHeads /* || aDiskInfo[2] > drive.nSectors */) {
+ this.notice("Disk \"" + sDiskName + "\" too large for drive " + this.getDriveName(drive.iDrive));
+ disk = null;
+ }
+ }
+
+ if (disk) {
+ drive.disk = disk;
+ drive.sDiskName = sDiskName;
+ drive.sDiskPath = sDiskPath;
+
+ /*
+ * Adding local disk image names to the disk list seems like a nice idea, but it's too confusing,
+ * because then it looks like the "Mount" button should be able to (re)load them, and that can NEVER
+ * happen, for security reasons; local disk images can ONLY be loaded via the "Mount" button after
+ * the user has selected them via the "Choose File" button.
+ *
+ * this.addDisk(sDiskName, sDiskPath);
+ *
+ * So we're going to take a different approach: when displayDisk() is asked to display the name
+ * of a local disk image, it will map all such disks to "Local Disk", and any attempt to "Mount" such
+ * a disk, will essentially result in a "Disk not found" error.
+ */
+
+ // this.addDiskHistory(sDiskName, sDiskPath, disk);
+
+ /*
+ * For a local disk (ie, one loaded via mountDrive()), the disk.restore() performed by addDiskHistory()
+ * may have altered the disk geometry, so refresh the disk info.
+ */
+ // aDiskInfo = disk.info();
+
+ /*
+ * Clearly, a successful mount implies a disk change, and I suppose that, technically, an *unsuccessful*
+ * mount should imply the same, but what would the real-world analog be? Inserting a piece of cardboard
+ * instead of an actual disk? In any case, if we can do the user a favor by pretending (as far as the
+ * disk change line is concerned) that an unsuccessful mount never happened, let's do it.
+ *
+ * Successful unmounts are a different story, however; those *do* trigger a change. See unloadDrive().
+ */
+
+ // this.regInput |= FDC.REG_INPUT.DISK_CHANGE;
+
+ /*
+ * With the addition of notify(), users are now "alerted" whenever a disk has finished loading;
+ * notify() is selective about its output, using print() if a print window is open, alert() otherwise.
+ */
+ this.notice("Mounted disk \"" + sDiskName + "\" in drive " + this.getDriveName(drive.iDrive), drive.fAutoMount || fAutoMount);
+
+ /*
+ * Since you usually want the Computer to have focus again after loading a new disk, let's try automatically
+ * updating the focus after a successful load.
+ */
+ if (this.cmp) this.cmp.setFocus();
+ }
+ else {
+ drive.fLocal = false;
+ }
+
+ if (drive.fAutoMount) {
+ drive.fAutoMount = false;
+ if (!--this.cAutoMount) this.setReady();
+ }
+
+ this.displayDisk(drive.iDrive);
+};
+
+/**
+ * addDisk(sName, sPath, fTop)
+ *
+ * @this {RK11}
+ * @param {string} sName
+ * @param {string} sPath
+ * @param {boolean} [fTop] (default is bottom)
+ */
+RK11.prototype.addDisk = function(sName, sPath, fTop)
+{
+ var controlDisks = this.bindings["listDisks"];
+ if (controlDisks && controlDisks.options) {
+ for (var i = 0; i < controlDisks.options.length; i++) {
+ if (controlDisks.options[i].value == sPath) return;
+ }
+ var controlOption = document.createElement("option");
+ controlOption.text = sName;
+ controlOption.value = sPath;
+ if (fTop && controlDisks.childNodes[0]) {
+ controlDisks.insertBefore(controlOption, controlDisks.childNodes[0]);
+ } else {
+ controlDisks.appendChild(controlOption);
+ }
+ }
+};
+
+/**
+ * findDisk(sPath)
+ *
+ * This is used to deal with mount requests (eg, autoMount) that supply a path without a name;
+ * if we can find the path in the "listDisks" control, then we return the associated disk name.
+ *
+ * @this {RK11}
+ * @param {string} sPath
+ * @return {string|null}
+ */
+RK11.prototype.findDisk = function(sPath)
+{
+ var controlDisks = this.bindings["listDisks"];
+ if (controlDisks && controlDisks.options) {
+ for (var i = 0; i < controlDisks.options.length; i++) {
+ var control = controlDisks.options[i];
+ if (control.value == sPath) return control.text;
+ }
+ }
+ return str.getBaseName(sPath, true);
+};
+
+/**
+ * displayDisk(iDrive, fUpdateDrive)
+ *
+ * @this {RK11}
+ * @param {number} iDrive (unvalidated)
+ * @param {boolean} [fUpdateDrive] is true to update the drive list to match the specified drive (eg, the auto-mount case)
+ */
+RK11.prototype.displayDisk = function(iDrive, fUpdateDrive)
+{
+ /*
+ * First things first: validate iDrive.
+ */
+ if (iDrive >= 0 && iDrive < this.aDrives.length) {
+ var drive = this.aDrives[iDrive];
+ var controlDisks = this.bindings["listDisks"];
+ var controlDrives = this.bindings["listDrives"];
+ /*
+ * Next, make sure controls for both drives and disks exist.
+ */
+ if (controlDisks && controlDrives && controlDisks.options && controlDrives.options) {
+ /*
+ * Next, make sure the drive whose disk we're updating is the currently selected drive.
+ */
+ var i;
+ var iDriveSelected = str.parseInt(controlDrives.value, 10);
+ var sTargetPath = (drive.fLocal? RK11.SOURCE.LOCAL : drive.sDiskPath);
+ if (!isNaN(iDriveSelected) && iDriveSelected == iDrive) {
+ for (i = 0; i < controlDisks.options.length; i++) {
+ if (controlDisks.options[i].value == sTargetPath) {
+ if (controlDisks.selectedIndex != i) {
+ controlDisks.selectedIndex = i;
+ }
+ break;
+ }
+ }
+ if (i == controlDisks.options.length) controlDisks.selectedIndex = 0;
+ }
+ if (fUpdateDrive) {
+ for (i = 0; i < controlDrives.options.length; i++) {
+ if (str.parseInt(controlDrives.options[i].value, 10) == drive.iDrive) {
+ if (controlDrives.selectedIndex != i) {
+ controlDrives.selectedIndex = i;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+};
+
+/**
+ * unloadDrive(iDrive, fLoading)
+ *
+ * @this {RK11}
+ * @param {number} iDrive
+ * @param {boolean} [fLoading]
+ */
+RK11.prototype.unloadDrive = function(iDrive, fLoading)
+{
+ var drive = this.aDrives[iDrive];
+
+ if (drive.disk || fLoading === false) {
+
+ /*
+ * Before we toss the disk's information, capture any deltas that may have occurred.
+ */
+ // this.updateDiskHistory(drive.sDiskName, drive.sDiskPath, drive.disk);
+
+ drive.sDiskName = "";
+ drive.sDiskPath = "";
+ drive.disk = null;
+ drive.fLocal = false;
+
+ // this.regInput |= FDC.REG_INPUT.DISK_CHANGE;
+
+ if (!fLoading) {
+ this.notice("Drive " + this.getDriveName(iDrive) + " unloaded", fLoading);
+ this.sDiskSource = RK11.SOURCE.NONE;
+ this.displayDisk(iDrive);
+ }
+ }
+};
+
+/**
+ * unloadAllDrives(fDiscard)
+ *
+ * @this {RK11}
+ * @param {boolean} fDiscard to discard all disk history before unloading
+ */
+RK11.prototype.unloadAllDrives = function(fDiscard)
+{
+ // if (fDiscard) this.aDiskHistory = [];
+
+ for (var iDrive = 0; iDrive < this.aDrives.length; iDrive++) {
+ this.unloadDrive(iDrive, true);
+ }
+};
+
+/**
+ * initController(data)
+ *
+ * @this {RK11}
+ * @param {Array} [data]
+ * @return {boolean} true if successful, false if failure
+ */
+RK11.prototype.initController = function(data)
+{
+ var i = 0;
+ if (!data) data = [];
+ this.dsr = data[i++] || 0x09C0;
+ this.err = data[i++] || 0;
+ this.csr = data[i++] || 0x80;
+ this.wcr = data[i++] || 0;
+ this.bar = data[i++] || 0;
+ this.dar = data[i++] || 0;
+ this.dbr = data[i] || 0;
+
+ var fSuccess = true;
+ for (var iDrive = 0; iDrive < this.aDrives.length; iDrive++) {
+ var drive = this.aDrives[iDrive];
+ if (drive === undefined) {
+ drive = this.aDrives[iDrive] = {};
+ }
+ if (!this.initDrive(drive, iDrive)) {
+ fSuccess = false;
+ }
+ }
+ return fSuccess;
+};
+
+/**
+ * initDrive(drive, iDrive, data)
+ *
+ * TODO: Consider a separate Drive class that any drive controller can use, since there's a lot of commonality
+ * between the drive objects created by disk controllers. This will clean up overall drive management and allow
+ * us to factor out some common Drive methods.
+ *
+ * @this {RK11}
+ * @param {Object} drive
+ * @param {number} iDrive
+ * @param {Array|undefined} [data]
+ * @return {boolean} true if successful, false if failure
+ */
+RK11.prototype.initDrive = function(drive, iDrive, data)
+{
+ var i = 0;
+ var fSuccess = true;
+
+ drive.iDrive = iDrive;
+ drive.fBusy = drive.fLocal = false;
+
+ drive.name = this.idComponent;
+ drive.nCylinders = 203;
+ drive.nHeads = 2;
+ drive.nSectors = 12;
+ drive.cbSector = 512;
+ drive.fRemovable = true;
+
+ /*
+ * The next group of properties are set by various controller command sequences.
+ */
+ drive.bHead = 0;
+ drive.bCylinder = 0;
+ drive.bSector = 1;
+ drive.bSectorEnd = drive.nSectors; // aka EOT
+ drive.nBytes = drive.cbSector;
+
+ /*
+ * The next group of properties are managed by worker functions (eg, doRead()) to maintain state across DMA requests.
+ */
+ drive.ibSector = 0;
+ drive.sector = null;
+
+ if (!drive.disk) {
+ drive.sDiskPath = ""; // ensure this is initialized to a default that displayDisk() can deal with
+ }
+
+ drive.status = PDP11.RK11.RKDS.RK05 | PDP11.RK11.RKDS.SOK | PDP11.RK11.RKDS.DRDY | PDP11.RK11.RKDS.RRDY;
+
+ return fSuccess;
+};
+
+/**
+ * processCommand()
+ *
+ * @this {RK11}
+ */
+RK11.prototype.processCommand = function()
+{
+ var fnReadWrite;
+ var fInterrupt = true;
+ var iDrive = (this.dar & PDP11.RK11.RKDA.DS) >> PDP11.RK11.RKDA.SHIFT.DS;
+ var drive = this.aDrives[iDrive];
+ var iCylinder, iHead, iSector, nWords, addr;
+
+ this.csr &= ~PDP11.RK11.RKCS.CRDY;
+
+ switch(this.csr & PDP11.RK11.RKCS.FUNC) {
+
+ case PDP11.RK11.FUNC.CRESET:
+ this.dsr = drive.status;
+ this.err = 0;
+ this.csr = PDP11.RK11.RKCS.CRDY;
+ this.dar = 0;
+ break;
+
+ case PDP11.RK11.FUNC.READ:
+ fnReadWrite = this.readData;
+ /* falls through */
+
+ case PDP11.RK11.FUNC.WRITE:
+ if (!fnReadWrite) fnReadWrite = this.writeData;
+ iCylinder = (this.dar & PDP11.RK11.RKDA.CA) >> PDP11.RK11.RKDA.SHIFT.CA;
+ iHead = (this.dar & PDP11.RK11.RKDA.HS) >> PDP11.RK11.RKDA.SHIFT.HS;
+ iSector = this.dar & PDP11.RK11.RKDA.SA;
+ if (iCylinder >= drive.nCylinders) {
+ this.err |= PDP11.RK11.RKER.DRE | PDP11.RK11.RKER.NXC;
+ this.csr |= PDP11.RK11.RKCS.HE | PDP11.RK11.RKCS.ERR;
+ break;
+ }
+ if (iSector >= drive.nSectors) {
+ this.err |= PDP11.RK11.RKER.DRE | PDP11.RK11.RKER.NXS;
+ this.csr |= PDP11.RK11.RKCS.HE | PDP11.RK11.RKCS.ERR;
+ break;
+ }
+ addr = (((this.csr & PDP11.RK11.RKCS.MEX)) << (16 - PDP11.RK11.RKCS.SHIFT.MEX)) | this.bar;
+ nWords = (0x10000 - this.wcr) & 0xffff;
+ fInterrupt = fnReadWrite.call(this, drive, iCylinder, iHead, iSector, nWords, addr, this.endReadWrite.bind(this));
+ break;
+
+ default:
+ break;
+ }
+
+ /*
+ * TODO: Determine what's up with the "dar % 9"....
+ */
+ this.dsr = drive.status | (iDrive << PDP11.RK11.RKDS.SHIFT.ID) | ((this.dar % 9) & PDP11.RK11.RKDS.SC);
+
+ if (fInterrupt) {
+ this.csr &= ~PDP11.RK11.RKCS.GO;
+ this.csr |= PDP11.RK11.RKCS.CRDY;
+ if (this.csr & PDP11.RK11.RKCS.IE) {
+ this.cpu.setTrigger(this.triggerInterrupt);
+ }
+ }
+};
+
+
+
+/**
+ * endReadWrite(err, iCylinder, iHead, iSector, nWords, addr)
+ *
+ * @this {RK11}
+ * @param {number} err
+ * @param {number} iCylinder
+ * @param {number} iHead
+ * @param {number} iSector
+ * @param {number} nWords
+ * @param {number} addr
+ * @return {boolean}
+ */
+RK11.prototype.endReadWrite = function(err, iCylinder, iHead, iSector, nWords, addr)
+{
+ this.bar = addr & 0xffff;
+ this.csr = (this.csr & ~PDP11.RK11.RKCS.MEX) | ((addr >> (16 - PDP11.RK11.RKCS.SHIFT.MEX)) & PDP11.RK11.RKCS.MEX);
+ this.wcr = (0x10000 - nWords) & 0xffff;
+ if (err) {
+ this.err |= err | PDP11.RK11.RKER.DRE;
+ this.csr |= PDP11.RK11.RKCS.HE | PDP11.RK11.RKCS.ERR;
+ }
+ return true;
+};
+
+/**
+ * readData(drive, iCylinder, iHead, iSector, nWords, addr, done)
+ *
+ * @this {RK11}
+ * @param {Object} drive
+ * @param {number} iCylinder
+ * @param {number} iHead
+ * @param {number} iSector
+ * @param {number} nWords
+ * @param {number} addr
+ * @param {function(...)} done
+ * @return {boolean} true if complete, false if queued
+ */
+RK11.prototype.readData = function(drive, iCylinder, iHead, iSector, nWords, addr, done)
+{
+ var err = 0;
+ var disk = drive.disk;
+ var sector = null, ibSector;
+
+ if (!disk) {
+ err = PDP11.RK11.RKER.NXD;
+ nWords = 0;
+ }
+
+ while (nWords--) {
+ if (!sector) {
+ sector = drive.disk.seek(iCylinder, iHead, iSector + 1);
+ if (!sector) {
+ err = PDP11.RK11.RKER.SKE;
+ break;
+ }
+ ibSector = 0;
+ }
+ var b0, b1;
+ if ((b0 = drive.disk.read(sector, ibSector++)) < 0 || (b1 = drive.disk.read(sector, ibSector++)) < 0) {
+ err = PDP11.RK11.RKER.NXS;
+ break;
+ }
+ var data = this.bus.setWordDirect(addr, b0 | (b1 << 8));
+ if (this.bus.checkFault()) {
+ err = PDP11.RK11.RKER.NXM;
+ break;
+ }
+ addr += 2;
+ if (ibSector >= disk.cbSector) {
+ sector = null;
+ if (++iSector >= disk.nSectors) {
+ iSector = 0;
+ if (++iHead >= disk.nHeads) {
+ iHead = 0;
+ if (++iCylinder >= disk.nCylinders) {
+ err = PDP11.RK11.RKER.NXC;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return done(err, iCylinder, iHead, iSector, nWords, addr);
+};
+
+/**
+ * writeData(drive, iCylinder, iHead, iSector, nWords, addr, done)
+ *
+ * @this {RK11}
+ * @param {Object} drive
+ * @param {number} iCylinder
+ * @param {number} iHead
+ * @param {number} iSector
+ * @param {number} nWords
+ * @param {number} addr
+ * @param {function(...)} done
+ * @return {boolean} true if complete, false if queued
+ */
+RK11.prototype.writeData = function(drive, iCylinder, iHead, iSector, nWords, addr, done)
+{
+ var err = 0;
+ var disk = drive.disk;
+ var sector = null, ibSector;
+
+ if (!disk) {
+ err = PDP11.RK11.RKER.NXD;
+ nWords = 0;
+ }
+
+ while (nWords--) {
+ var data = this.bus.getWordDirect(addr);
+ if (this.bus.checkFault()) {
+ err = PDP11.RK11.RKER.NXM;
+ break;
+ }
+ addr += 2;
+ if (!sector) {
+ sector = drive.disk.seek(iCylinder, iHead, iSector + 1, true);
+ if (!sector) {
+ err = PDP11.RK11.RKER.SKE;
+ break;
+ }
+ ibSector = 0;
+ }
+ if (!drive.disk.write(sector, ibSector++, data & 0xff) || !drive.disk.write(sector, ibSector++, data >> 8)) {
+ err = PDP11.RK11.RKER.NXS;
+ break;
+ }
+ if (ibSector >= disk.cbSector) {
+ sector = null;
+ if (++iSector >= disk.nSectors) {
+ iSector = 0;
+ if (++iHead >= disk.nHeads) {
+ iHead = 0;
+ if (++iCylinder >= disk.nCylinders) {
+ err = PDP11.RK11.RKER.NXC;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return done(err, iCylinder, iHead, iSector, nWords, addr);
+};
+
+/**
+ * readRKDS(addr)
+ *
+ * @this {RK11}
+ * @param {number} addr (eg, PDP11.UNIBUS.RKDS or 177400)
+ * @return {number}
+ */
+RK11.prototype.readRKDS = function(addr)
+{
+ return this.dsr;
+};
+
+/**
+ * writeRKDS(data, addr)
+ *
+ * @this {RK11}
+ * @param {number} data
+ * @param {number} addr (eg, PDP11.UNIBUS.RKDS or 177400)
+ */
+RK11.prototype.writeRKDS = function(data, addr)
+{
+ /*
+ * This is a read-only register
+ */
+};
+
+/**
+ * readRKER(addr)
+ *
+ * @this {RK11}
+ * @param {number} addr (eg, PDP11.UNIBUS.RKER or 177402)
+ * @return {number}
+ */
+RK11.prototype.readRKER = function(addr)
+{
+ return this.err;
+};
+
+/**
+ * writeRKER(data, addr)
+ *
+ * @this {RK11}
+ * @param {number} data
+ * @param {number} addr (eg, PDP11.UNIBUS.RKER or 177402)
+ */
+RK11.prototype.writeRKER = function(data, addr)
+{
+ /*
+ * This is a read-only register
+ */
+};
+
+/**
+ * readRKCS(addr)
+ *
+ * @this {RK11}
+ * @param {number} addr (eg, PDP11.UNIBUS.RKCS or 177404)
+ * @return {number}
+ */
+RK11.prototype.readRKCS = function(addr)
+{
+ return this.csr & PDP11.RK11.RKCS.RMASK;
+};
+
+/**
+ * writeRKCS(data, addr)
+ *
+ * @this {RK11}
+ * @param {number} data
+ * @param {number} addr (eg, PDP11.UNIBUS.RKCS or 177404)
+ */
+RK11.prototype.writeRKCS = function(data, addr)
+{
+ this.csr = (this.csr & ~PDP11.RK11.RKCS.WMASK) | (data & PDP11.RK11.RKCS.WMASK);
+
+ if (this.csr & PDP11.RK11.RKCS.GO) this.processCommand();
+};
+
+/**
+ * readRKWC(addr)
+ *
+ * @this {RK11}
+ * @param {number} addr (eg, PDP11.UNIBUS.RKWC or 177406)
+ * @return {number}
+ */
+RK11.prototype.readRKWC = function(addr)
+{
+ return this.wcr;
+};
+
+/**
+ * writeRKWC(data, addr)
+ *
+ * @this {RK11}
+ * @param {number} data
+ * @param {number} addr (eg, PDP11.UNIBUS.RKWC or 177406)
+ */
+RK11.prototype.writeRKWC = function(data, addr)
+{
+ this.wcr = data;
+};
+
+/**
+ * readRKBA(addr)
+ *
+ * @this {RK11}
+ * @param {number} addr (eg, PDP11.UNIBUS.RKBA or 177410)
+ * @return {number}
+ */
+RK11.prototype.readRKBA = function(addr)
+{
+ return this.bar;
+};
+
+/**
+ * writeRKBA(data, addr)
+ *
+ * @this {RK11}
+ * @param {number} data
+ * @param {number} addr (eg, PDP11.UNIBUS.RKBA or 177410)
+ */
+RK11.prototype.writeRKBA = function(data, addr)
+{
+ this.bar = data;
+};
+
+/**
+ * readRKDA(addr)
+ *
+ * @this {RK11}
+ * @param {number} addr (eg, PDP11.UNIBUS.RKDA or 177412)
+ * @return {number}
+ */
+RK11.prototype.readRKDA = function(addr)
+{
+ return this.dar;
+};
+
+/**
+ * writeRKDA(data, addr)
+ *
+ * @this {RK11}
+ * @param {number} data
+ * @param {number} addr (eg, PDP11.UNIBUS.RKDA or 177412)
+ */
+RK11.prototype.writeRKDA = function(data, addr)
+{
+ this.dar = data;
+};
+
+/**
+ * readRKDB(addr)
+ *
+ * @this {RK11}
+ * @param {number} addr (eg, PDP11.UNIBUS.RKDB or 177416)
+ * @return {number}
+ */
+RK11.prototype.readRKDB = function(addr)
+{
+ return this.dbr;
+};
+
+/**
+ * writeRKDB(data, addr)
+ *
+ * @this {RK11}
+ * @param {number} data
+ * @param {number} addr (eg, PDP11.UNIBUS.RKDB or 177416)
+ */
+RK11.prototype.writeRKDB = function(data, addr)
+{
+ this.dbr = data;
+};
+
+/*
+ * ES6 ALERT: As you can see below, I've finally started using computed property names.
+ */
+RK11.UNIBUS_IOTABLE = {
+ [PDP11.UNIBUS.RKDS]: /* 177400 */ [null, null, RK11.prototype.readRKDS, RK11.prototype.writeRKDS, "RKDS"],
+ [PDP11.UNIBUS.RKER]: /* 177402 */ [null, null, RK11.prototype.readRKER, RK11.prototype.writeRKER, "RKER"],
+ [PDP11.UNIBUS.RKCS]: /* 177404 */ [null, null, RK11.prototype.readRKCS, RK11.prototype.writeRKCS, "RKCS"],
+ [PDP11.UNIBUS.RKWC]: /* 177406 */ [null, null, RK11.prototype.readRKWC, RK11.prototype.writeRKWC, "RKWC"],
+ [PDP11.UNIBUS.RKBA]: /* 177410 */ [null, null, RK11.prototype.readRKBA, RK11.prototype.writeRKBA, "RKBA"],
+ [PDP11.UNIBUS.RKDA]: /* 177412 */ [null, null, RK11.prototype.readRKDA, RK11.prototype.writeRKDA, "RKDA"],
+ [PDP11.UNIBUS.RKDB]: /* 177416 */ [null, null, RK11.prototype.readRKDB, RK11.prototype.writeRKDB, "RKDB"]
+};
+
+if (NODE) module.exports = RK11;
diff --git a/modules/pdp11/lib/rl11.js b/modules/pdp11/lib/rl11.js
index cb1af4489..ef89740b5 100644
--- a/modules/pdp11/lib/rl11.js
+++ b/modules/pdp11/lib/rl11.js
@@ -980,7 +980,7 @@ RL11.prototype.processCommand = function()
RL11.prototype.endReadWrite = function(err, iCylinder, iHead, iSector, nWords, addr)
{
this.bar = addr & 0xffff;
- this.csr = (this.csr & ~PDP11.RL11.RLCS.BAE) | ((addr >> 12) & PDP11.RL11.RLCS.BAE);
+ this.csr = (this.csr & ~PDP11.RL11.RLCS.BAE) | ((addr >> (16 - PDP11.RL11.RLCS.SHIFT.BAE)) & PDP11.RL11.RLCS.BAE);
this.ber = (addr >> 16) & PDP11.RL11.RLBE.MASK; // 22 bit mode
this.dar = (iCylinder << PDP11.RL11.RLDA.SHIFT.RW_CA) | (iHead? PDP11.RL11.RLDA.RW_HS : 0) | (iSector & PDP11.RL11.RLDA.RW_SA);
this.darInternal = this.dar;
diff --git a/modules/shared/lib/diskapi.js b/modules/shared/lib/diskapi.js
index 29449e98a..655c6291a 100644
--- a/modules/shared/lib/diskapi.js
+++ b/modules/shared/lib/diskapi.js
@@ -95,6 +95,7 @@ DiskAPI.DISK_FORMATS = {
/*
* Assorted DEC disk pack formats.
*/
+ 2494464: [203,2,12,512], // RK03 single-platter disk cartridge: 203 tracks, 2 heads, 12 sectors/track, 512 bytes/sector, for a total of 2494464 bytes
5242880: [256,2,40,256], // RL01K single-platter disk cartridge: 256 tracks, 2 heads, 40 sectors/track, 256 bytes/sector, for a total of 5242880 bytes
10485760:[512,2,40,256] // RL02K single-platter disk cartridge: 512 tracks, 2 heads, 40 sectors/track, 256 bytes/sector, for a total of 10485760 bytes
};
diff --git a/package.json b/package.json
index 2a02fc11f..6ad49843b 100644
--- a/package.json
+++ b/package.json
@@ -200,6 +200,7 @@
"./modules/pdp11/lib/serialport.js",
"./modules/pdp11/lib/pc11.js",
"./modules/pdp11/lib/disk.js",
+ "./modules/pdp11/lib/rk11.js",
"./modules/pdp11/lib/rl11.js",
"./modules/shared/lib/debugger.js",
"./modules/pdp11/lib/debugger.js",