From dcea4fb9054d565b3e322dc99ca35b7399e9bdeb Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Mon, 21 Nov 2016 18:10:20 -0800 Subject: [PATCH] First stab at RK11 support --- devices/pdp11/README.md | 2 +- .../machine/1170/monitor/debugger/machine.xml | 1 + .../pdp11/machine/1170/monitor/machine.xml | 1 + .../machine/1170/panel/debugger/machine.xml | 1 + devices/pdp11/machine/1170/panel/machine.xml | 1 + devices/pdp11/rk11/README.md | 42 + devices/pdp11/rk11/default.xml | 13 + devices/pdp11/rl11/README.md | 8 +- devices/pdp11/rl11/default.xml | 4 +- disks/dec/rk03/README.md | 14 + disks/dec/rl02k/README.md | 2 +- docs/pcx86/examples/pcx86-dbg.js | 6 +- docs/pcx86/examples/pcx86.js | 6 +- modules/pdp11/README.md | 1 + modules/pdp11/lib/defines.js | 88 ++ modules/pdp11/lib/device.js | 4 + modules/pdp11/lib/disk.js | 2 +- modules/pdp11/lib/messages.js | 4 +- modules/pdp11/lib/rk11.js | 1281 +++++++++++++++++ modules/pdp11/lib/rl11.js | 2 +- modules/shared/lib/diskapi.js | 1 + package.json | 1 + 22 files changed, 1468 insertions(+), 17 deletions(-) create mode 100644 devices/pdp11/rk11/README.md create mode 100644 devices/pdp11/rk11/default.xml create mode 100644 disks/dec/rk03/README.md create mode 100644 modules/pdp11/lib/rk11.js 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",