2Copyright 2020-2021 Intel Corporation
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
16@description Intel AMT IDER module
17@author Ylian Saint-Hilaire
23/*jshint strict:false */
25/*jshint esversion: 6 */
28// Construct a Intel AMT IDER object
29module.exports.CreateAmtRemoteIder = function (webserver, meshcentral) {
30 const fs = require('fs');
33 obj.protocol = 3; // IDER
36 obj.rx_timeout = 30000; // Default 30000
37 obj.tx_timeout = 0; // Default 0
38 obj.heartbeat = 20000; // Default 20000
45 obj.iderStart = 0; // OnReboot = 0, Graceful = 1, Now = 2
48 obj.floppyReady = false;
49 obj.cdromReady = false;
50 //obj.pingTimer = null;
51 obj.sectorStats = null;
55 function debug() { if (obj.debug) { console.log(...arguments); } }
58 var IDE_ModeSence_LS120Disk_Page_Array = Buffer.from([0x00, 0x26, 0x31, 0x80, 0x00, 0x00, 0x00, 0x00, 0x05, 0x1E, 0x10, 0xA9, 0x08, 0x20, 0x02, 0x00, 0x03, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD0, 0x00, 0x00]);
59 var IDE_ModeSence_3F_LS120_Array = Buffer.from([0x00, 0x5c, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x16, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x05, 0x1E, 0x10, 0xA9, 0x08, 0x20, 0x02, 0x00, 0x03, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD0, 0x00, 0x00, 0x08, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x11, 0x24, 0x31]);
60 var IDE_ModeSence_FloppyDisk_Page_Array = Buffer.from([0x00, 0x26, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x05, 0x1E, 0x04, 0xB0, 0x02, 0x12, 0x02, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD0, 0x00, 0x00]);
61 var IDE_ModeSence_3F_Floppy_Array = Buffer.from([0x00, 0x5c, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x16, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x05, 0x1e, 0x04, 0xb0, 0x02, 0x12, 0x02, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd0, 0x00, 0x00, 0x08, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x11, 0x24, 0x31]);
62 var IDE_ModeSence_CD_1A_Array = Buffer.from([0x00, 0x12, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
63 //var IDE_ModeSence_CD_1B_Array = Buffer.from([0x00, 0x12, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
64 var IDE_ModeSence_CD_1D_Array = Buffer.from([0x00, 0x12, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1D, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
65 var IDE_ModeSence_CD_2A_Array = Buffer.from([0x00, 0x20, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x18, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
66 //var IDE_ModeSence_CD_01_Array = Buffer.from([0x00, 0x0E, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00]);
67 var IDE_ModeSence_3F_CD_Array = Buffer.from([0x00, 0x28, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x18, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
70 var IDE_CD_ConfigArrayHeader = Buffer.from([0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x08]);
71 var IDE_CD_ConfigArrayProfileList = Buffer.from([0x00, 0x00, 0x03, 0x04, 0x00, 0x08, 0x01, 0x00]);
72 var IDE_CD_ConfigArrayCore = Buffer.from([0x00, 0x01, 0x03, 0x04, 0x00, 0x00, 0x00, 0x02]);
73 var IDE_CD_Morphing = Buffer.from([0x00, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00]);
74 var IDE_CD_ConfigArrayRemovable = Buffer.from([0x00, 0x03, 0x03, 0x04, 0x29, 0x00, 0x00, 0x02]);
75 var IDE_CD_ConfigArrayRandom = Buffer.from([0x00, 0x10, 0x01, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00]);
76 var IDE_CD_Read = Buffer.from([0x00, 0x1E, 0x03, 0x00]);
77 var IDE_CD_PowerManagement = Buffer.from([0x01, 0x00, 0x03, 0x00]);
78 var IDE_CD_Timeout = Buffer.from([0x01, 0x05, 0x03, 0x00]);
81 var IDE_ModeSence_FloppyError_Recovery_Array = Buffer.from([0x00, 0x12, 0x24, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]);
82 var IDE_ModeSence_Ls120Error_Recovery_Array = Buffer.from([0x00, 0x12, 0x31, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]);
83 var IDE_ModeSence_CDError_Recovery_Array = Buffer.from([0x00, 0x0E, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00]);
85 // CD info and performance
86 var RD_CD_DiskInfo = Buffer.from([0x00, 0x20, 0x0e, 0x01, 0x01, 0x01, 0x01, 0x20, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
87 var RD_CD_Performance = Buffer.from([0x00, 0x00, 0x00, 0x04, 0x02, 0x00, 0x00, 0x00]);
89 // Private method, called by parent when it change state
90 obj.xxStateChange = function (newstate) {
91 if (obj.debug) console.log("IDER-StateChange", newstate);
92 if (newstate == 0) { obj.Stop(); }
93 if (newstate == 3) { obj.Start(); }
96 obj.diskSetup = function (floppyPath, cdromPath) {
97 debug(floppyPath, cdromPath);
100 if (floppyPath != null) {
102 if (fs.existsSync(floppyPath) == false) { return 1; } // Floppy disk image does not exist
103 var stats = fs.statSync(floppyPath);
104 if ((stats.size % 512) != 0) { return 2; } // Invalid floppy disk image
105 obj.floppy = { size: stats.size, ptr: fs.openSync(floppyPath, 'r') };
106 } catch (ex) { return 3; } // Unable to open floppy disk image
110 if (cdromPath != null) {
112 if (fs.existsSync(cdromPath) == false) { return 4; } // CDROM disk image does not exist
113 var stats = fs.statSync(cdromPath);
114 if ((stats.size % 512) != 0) { return 5; } // Invalid CDROM disk image
115 obj.cdrom = { size: stats.size, ptr: fs.openSync(cdromPath, 'r') };
116 } catch (ex) { return 6; } // Unable to open CDROM disk image
119 if ((obj.cdrom == null) && (obj.floppy == null)) { return 7; } // Can't do IDER with no disk images
124 obj.Start = function () {
125 if (obj.debug) { console.log('IDER-Start'); console.log(obj.floppy, obj.cdrom); }
127 obj.bytesFromAmt = 0;
132 // Send first command, OPEN_SESSION
133 obj.SendCommand(0x40, Buffer.concat([ShortToStrX(obj.rx_timeout), ShortToStrX(obj.tx_timeout), ShortToStrX(obj.heartbeat), IntToStrX(obj.version)]));
136 if (obj.sectorStats) {
137 obj.sectorStats(0, 0, obj.floppy ? (obj.floppy.size >> 9) : 0);
138 obj.sectorStats(0, 1, obj.cdrom ? (obj.cdrom.size >> 11) : 0);
141 // Setup the ping timer
142 //obj.pingTimer = setInterval(function () { obj.SendCommand(0x44); }, 5000);
145 obj.Stop = function () {
146 if (obj.debug) console.log('IDER-Stop');
147 //if (obj.pingTimer) { clearInterval(obj.pingTimer); obj.pingTimer = null; }
152 obj.ProcessData = function (data) {
153 data = Buffer.from(data, 'binary');
154 obj.bytesFromAmt += data.length;
155 if (obj.acc == null) { obj.acc = data; } else { obj.acc = Buffer.concat([obj.acc, data]); }
156 if (obj.debug) console.log('IDER-ProcessData', obj.acc.length, obj.acc.toString('hex'));
158 // Process as many commands as possible
159 while (obj.acc != null) {
160 var len = obj.ProcessDataEx();
161 if (len == 0) return;
162 if (obj.inSequence != ReadIntX(obj.acc, 4)) {
163 if (obj.debug) console.log('ERROR: Out of sequence', obj.inSequence, ReadIntX(obj.acc, 4));
168 if (len == obj.acc.length) { obj.acc = null; } else { obj.acc = obj.acc.slice(len); }
173 obj.SendCommand = function (cmdid, data, completed, dma) {
174 if (data == null) { data = Buffer.alloc(0); }
175 var attributes = ((cmdid > 50) && (completed == true)) ? 2 : 0;
176 if (dma) { attributes += 1; }
177 var x = Buffer.concat([Buffer.from([cmdid, 0, 0, attributes]), IntToStrX(obj.outSequence++), data]);
178 obj.parent.xxSend(x);
179 obj.bytesToAmt += x.length;
180 //if (cmdid != 0x4B) { console.log('IDER-SendData', x.length, x.toString('hex')); }
183 // CommandEndResponse (SCSI_SENSE)
184 obj.SendCommandEndResponse = function (error, sense, device, asc, asq) {
185 if (error) { obj.SendCommand(0x51, Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xc5, 0, 3, 0, 0, 0, device, 0x50, 0, 0, 0]), true); }
186 else { obj.SendCommand(0x51, Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87, (sense << 4), 3, 0, 0, 0, device, 0x51, sense, asc, asq]), true); }
189 // DataToHost (SCSI_READ)
190 obj.SendDataToHost = function (device, completed, data, dma) {
191 var dmalen = (dma) ? 0 : data.length;
192 if (completed == true) {
193 obj.SendCommand(0x54, Buffer.concat([Buffer.from([0, (data.length & 0xff), (data.length >> 8), 0, dma ? 0xb4 : 0xb5, 0, 2, 0, (dmalen & 0xff), (dmalen >> 8), device, 0x58, 0x85, 0, 3, 0, 0, 0, device, 0x50, 0, 0, 0, 0, 0, 0]), data]), completed, dma);
195 obj.SendCommand(0x54, Buffer.concat([Buffer.from([0, (data.length & 0xff), (data.length >> 8), 0, dma ? 0xb4 : 0xb5, 0, 2, 0, (dmalen & 0xff), (dmalen >> 8), device, 0x58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), data]), completed, dma);
199 // GetDataFromHost (SCSI_CHUNK)
200 obj.SendGetDataFromHost = function (device, chunksize) {
201 obj.SendCommand(0x52, Buffer.from([0, (chunksize & 0xff), (chunksize >> 8), 0, 0xb5, 0, 0, 0, (chunksize & 0xff), (chunksize >> 8), device, 0x58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), false);
204 // DisableEnableFeatures (STATUS_DATA)
205 // If type is REGS_TOGGLE (3), 4 bytes of data must be provided.
206 obj.SendDisableEnableFeatures = function (type, data) { if (data == null) { data = ''; } obj.SendCommand(0x48, Buffer.concat([Buffer.from([type]), data])); }
209 obj.ProcessDataEx = function () {
210 if (obj.acc.length < 8) return 0;
212 // First 8 bytes are the header
213 // CommandID + 0x000000 + Sequence Number
214 //console.log('ProcessDataEx', obj.acc[0], obj.acc);
216 switch (obj.acc[0]) {
217 case 0x41: // OPEN_SESSION
218 if (obj.acc.length < 30) return 0;
219 var len = obj.acc[29];
220 if (obj.acc.length < (30 + len)) return 0;
222 obj.iderinfo.major = obj.acc[8];
223 obj.iderinfo.minor = obj.acc[9];
224 obj.iderinfo.fwmajor = obj.acc[10];
225 obj.iderinfo.fwminor = obj.acc[11];
226 obj.iderinfo.readbfr = ReadShortX(obj.acc, 16);
227 obj.iderinfo.writebfr = ReadShortX(obj.acc, 18);
228 obj.iderinfo.proto = obj.acc[21];
229 obj.iderinfo.iana = ReadIntX(obj.acc, 25);
230 if (obj.debug) console.log(obj.iderinfo);
232 if (obj.iderinfo.proto != 0) {
233 if (obj.debug) console.log("Unknown proto", obj.iderinfo.proto);
236 if (obj.iderinfo.readbfr > 8192) {
237 if (obj.debug) console.log("Illegal read buffer size", obj.iderinfo.readbfr);
240 if (obj.iderinfo.writebfr > 8192) {
241 if (obj.debug) console.log("Illegal write buffer size", obj.iderinfo.writebfr);
245 if (obj.iderStart == 0) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x08)); } // OnReboot
246 else if (obj.iderStart == 1) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x10)); } // Graceful
247 else if (obj.iderStart == 2) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x18)); } // Now
248 //obj.SendDisableEnableFeatures(1); // GetSupportedFeatures
251 if (obj.debug) console.log('CLOSE');
254 case 0x44: // KEEPALIVEPING
255 obj.SendCommand(0x45); // Send PONG back
257 case 0x45: // KEEPALIVEPONG
258 if (obj.debug) console.log('PONG');
260 case 0x46: // RESETOCCURED
261 if (obj.acc.length < 9) return 0;
262 var resetMask = obj.acc[8];
263 if (g_media === null) {
264 // No operations are pending
265 obj.SendCommand(0x47); // Send ResetOccuredResponse
266 if (obj.debug) console.log('RESETOCCURED1', resetMask);
268 // Operations are being done, sent the reset once completed.
270 if (obj.debug) console.log('RESETOCCURED2', resetMask);
273 case 0x49: // STATUS_DATA - DisableEnableFeaturesReply
274 if (obj.acc.length < 13) return 0;
275 var type = obj.acc[8];
276 var value = ReadIntX(obj.acc, 9);
277 if (obj.debug) console.log('STATUS_DATA', type, value);
279 case 1: // REGS_AVAIL
281 if (obj.iderStart == 0) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x08)); } // OnReboot
282 else if (obj.iderStart == 1) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x10)); } // Graceful
283 else if (obj.iderStart == 2) { obj.SendDisableEnableFeatures(3, IntToStrX(0x01 + 0x18)); } // Now
286 case 2: // REGS_STATUS
287 obj.enabled = (value & 2) ? true : false;
288 if (obj.debug) console.log("IDER Status: " + obj.enabled);
290 case 3: // REGS_TOGGLE
292 if (obj.debug) console.log("Register toggle failure");
293 } //else { obj.SendDisableEnableFeatures(2); }
297 case 0x4A: // ERROR OCCURED
298 if (obj.acc.length < 11) return 0;
299 if (obj.debug) console.log('IDER: ABORT', obj.acc[8]);
302 case 0x4B: // HEARTBEAT
303 //console.log('HEARTBEAT');
305 case 0x50: // COMMAND WRITTEN
306 if (obj.acc.length < 28) return 0;
307 var device = (obj.acc[14] & 0x10) ? 0xB0 : 0xA0;
308 var deviceFlags = obj.acc[14];
309 var cdb = obj.acc.slice(16, 28);
310 var featureRegister = obj.acc[9];
311 if (obj.debug) console.log('SCSI_CMD', device, cdb.toString('hex'), featureRegister, deviceFlags);
312 handleSCSI(device, cdb, featureRegister, deviceFlags);
314 case 0x53: // DATA FROM HOST
315 if (obj.acc.length < 14) return 0;
316 var len = ReadShortX(obj.acc, 9);
317 if (obj.acc.length < (14 + len)) return 0;
318 if (obj.debug) console.log('SCSI_WRITE, len = ' + (14 + len));
319 obj.SendCommand(0x51, Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x70, 0x03, 0x00, 0x00, 0x00, 0xa0, 0x51, 0x07, 0x27, 0x00]), true);
322 if (obj.debug) console.log('Unknown IDER command', obj.acc[0]);
329 function handleSCSI(dev, cdb, featureRegister, deviceFlags) {
334 case 0x00: // TEST_UNIT_READY:
335 if (obj.debug) console.log("SCSI: TEST_UNIT_READY", dev);
337 case 0xA0: // DEV_FLOPPY
338 if (obj.floppy == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
339 if (obj.floppyReady == false) { obj.floppyReady = true; obj.SendCommandEndResponse(1, 0x06, dev, 0x28, 0x00); return -1; } // Switch to ready
341 case 0xB0: // DEV_CDDVD
342 if (obj.cdrom == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
343 if (obj.cdromReady == false) { obj.cdromReady = true; obj.SendCommandEndResponse(1, 0x06, dev, 0x28, 0x00); return -1; } // Switch to ready
346 if (obj.debug) console.log("SCSI Internal error 3", dev);
349 obj.SendCommandEndResponse(1, 0x00, dev, 0x00, 0x00); // Indicate ready
352 lba = ((cdb[1] & 0x1f) << 16) + (cdb[2] << 8) + cdb[3];
354 if (len == 0) { len = 256; }
355 if (obj.debug) console.log("SCSI: READ_6", dev, lba, len);
356 sendDiskData(dev, lba, len, featureRegister);
358 case 0x0a: // WRITE_6
359 lba = ((cdb[1] & 0x1f) << 16) + (cdb[2] << 8) + cdb[3];
361 if (len == 0) { len = 256; }
362 if (obj.debug) console.log("SCSI: WRITE_6", dev, lba, len);
363 obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); // Write is not supported, remote no medium.
366 case 0x15: // MODE_SELECT_6:
367 console.log("SCSI ERROR: MODE_SELECT_6", dev);
368 obj.SendCommandEndResponse(1, 0x05, dev, 0x20, 0x00);
371 case 0x1a: // MODE_SENSE_6
372 if (obj.debug) console.log("SCSI: MODE_SENSE_6", dev);
373 if ((cdb[2] == 0x3f) && (cdb[3] == 0x00)) {
376 case 0xA0: // DEV_FLOPPY
377 if (obj.floppy == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
379 b = 0x80; // Read only = 0x80, Read write = 0x00
381 case 0xB0: // DEV_CDDVD
382 if (obj.cdrom == null) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
387 if (obj.debug) console.log("SCSI Internal error 6", dev);
390 obj.SendDataToHost(dev, true, Buffer.from([0, a, b, 0]), featureRegister & 1);
393 obj.SendCommandEndResponse(1, 0x05, dev, 0x24, 0x00);
395 case 0x1b: // START_STOP (Called when you eject the CDROM)
396 //var immediate = cdb[1] & 0x01;
397 //var loej = cdb[4] & 0x02;
398 //var start = cdb[4] & 0x01;
399 obj.SendCommandEndResponse(1, 0, dev);
401 case 0x1e: // LOCK_UNLOCK - ALLOW_MEDIUM_REMOVAL
402 if (obj.debug) console.log("SCSI: ALLOW_MEDIUM_REMOVAL", dev);
403 if ((dev == 0xA0) && (obj.floppy == null)) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
404 if ((dev == 0xB0) && (obj.cdrom == null)) { obj.SendCommandEndResponse(1, 0x02, dev, 0x3a, 0x00); return -1; }
405 obj.SendCommandEndResponse(1, 0x00, dev, 0x00, 0x00);
407 case 0x23: // READ_FORMAT_CAPACITIES (Floppy only)
408 if (obj.debug) console.log("SCSI: READ_FORMAT_CAPACITIES", dev);
409 var buflen = ReadShort(cdb, 7);
410 var mediaStatus = 0, sectors;
411 var mcSize = buflen / 8; // Capacity descriptor size is 8
414 case 0xA0: // DEV_FLOPPY
415 if ((obj.floppy == null) || (obj.floppy.size == 0)) { obj.SendCommandEndResponse(0, 0x05, dev, 0x24, 0x00); return -1; }
416 sectors = (obj.floppy.size >> 9) - 1;
418 case 0xB0: // DEV_CDDVD
419 if ((obj.cdrom == null) || (obj.cdrom.size == 0)) { obj.SendCommandEndResponse(0, 0x05, dev, 0x24, 0x00); return -1; }
420 sectors = (obj.cdrom.size >> 11) - 1; // Number 2048 byte blocks
423 if (obj.debug) console.log("SCSI Internal error 4", dev);
427 obj.SendDataToHost(dev, true, Buffer.concat([IntToStr(8), Buffer.from([0x00, 0x00, 0x0b, 0x40, 0x02, 0x00, 0x02, 0x00])]), featureRegister & 1);
429 case 0x25: // READ_CAPACITY
430 if (obj.debug) console.log("SCSI: READ_CAPACITY", dev);
433 case 0xA0: // DEV_FLOPPY
434 if ((obj.floppy == null) || (obj.floppy.size == 0)) { obj.SendCommandEndResponse(0, 0x02, dev, 0x3a, 0x00); return -1; }
435 if (obj.floppy != null) { len = (obj.floppy.size >> 9) - 1; }
436 if (obj.debug) console.log('DEV_FLOPPY', len); // Number 512 byte blocks
438 case 0xB0: // DEV_CDDVD
439 if ((obj.cdrom == null) || (obj.cdrom.size == 0)) { obj.SendCommandEndResponse(0, 0x02, dev, 0x3a, 0x00); return -1; }
440 if (obj.cdrom != null) { len = (obj.cdrom.size >> 11) - 1; } // Number 2048 byte blocks
441 if (obj.debug) console.log('DEV_CDDVD', len);
444 if (obj.debug) console.log("SCSI Internal error 4", dev);
447 //if (dev == 0xA0) { dev = 0x00; } else { dev = 0x10; } // Weird but seems to work.
448 if (obj.debug) console.log("SCSI: READ_CAPACITY2", dev, deviceFlags);
449 obj.SendDataToHost(deviceFlags, true, Buffer.concat([IntToStr(len), Buffer.from([0, 0, ((dev == 0xB0) ? 0x08 : 0x02), 0])]), featureRegister & 1);
451 case 0x28: // READ_10
452 lba = ReadInt(cdb, 2);
453 len = ReadShort(cdb, 7);
454 if (obj.debug) console.log("SCSI: READ_10", dev, lba, len);
455 sendDiskData(dev, lba, len, featureRegister);
457 case 0x2a: // WRITE_10 (Floppy only)
458 case 0x2e: // WRITE_AND_VERIFY (Floppy only)
459 lba = ReadInt(cdb, 2);
460 len = ReadShort(cdb, 7);
461 if (obj.debug) console.log("SCSI: WRITE_10", dev, lba, len);
462 obj.SendGetDataFromHost(dev, 512 * len); // Floppy writes only, accept sectors of 512 bytes
464 case 0x43: // READ_TOC (CD Audio only)
465 var buflen = ReadShort(cdb, 7);
466 var msf = cdb[1] & 0x02;
467 var format = cdb[2] & 0x07;
468 if (format == 0) { format = cdb[9] >> 6; }
469 if (obj.debug) console.log("SCSI: READ_TOC, dev=" + dev + ", buflen=" + buflen + ", msf=" + msf + ", format=" + format);
472 case 0xA0: // DEV_FLOPPY
473 obj.SendCommandEndResponse(1, 0x05, dev, 0x20, 0x00); // Not implemented
475 case 0xB0: // DEV_CDDVD
479 if (obj.debug) console.log("SCSI Internal error 9", dev);
483 if (format == 1) { obj.SendDataToHost(dev, true, Buffer.from([0x00, 0x0a, 0x01, 0x01, 0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]), featureRegister & 1); }
484 else if (format == 0) {
486 obj.SendDataToHost(dev, true, Buffer.from([0x00, 0x12, 0x01, 0x01, 0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x14, 0xaa, 0x00, 0x00, 0x00, 0x34, 0x13]), featureRegister & 1);
488 obj.SendDataToHost(dev, true, Buffer.from([0x00, 0x12, 0x01, 0x01, 0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00]), featureRegister & 1);
492 case 0x46: // GET_CONFIGURATION
493 var sendall = (cdb[1] != 2);
494 var firstcode = ReadShort(cdb, 2);
495 var buflen = ReadShort(cdb, 7);
497 if (obj.debug) console.log("SCSI: GET_CONFIGURATION", dev, sendall, firstcode, buflen);
498 if (buflen == 0) { obj.SendDataToHost(dev, true, Buffer.concat([IntToStr(0x003c), IntToStr(0x0008)]), featureRegister & 1); return -1; } // TODO: Fixed this return, it's not correct.
504 if (firstcode == 0) { r = IDE_CD_ConfigArrayProfileList; }
505 if ((firstcode == 0x1) || (sendall && (firstcode < 0x1))) { r = IDE_CD_ConfigArrayCore; }
506 if ((firstcode == 0x2) || (sendall && (firstcode < 0x2))) { r = IDE_CD_Morphing; }
507 if ((firstcode == 0x3) || (sendall && (firstcode < 0x3))) { r = IDE_CD_ConfigArrayRemovable; }
508 if ((firstcode == 0x10) || (sendall && (firstcode < 0x10))) { r = IDE_CD_ConfigArrayRandom; }
509 if ((firstcode == 0x1E) || (sendall && (firstcode < 0x1E))) { r = IDE_CD_Read; }
510 if ((firstcode == 0x100) || (sendall && (firstcode < 0x100))) { r = IDE_CD_PowerManagement; }
511 if ((firstcode == 0x105) || (sendall && (firstcode < 0x105))) { r = IDE_CD_Timeout; }
514 //console.log('NOT RIGHT', sendall, firstcode, cdb[2], cdb[3]);
516 r = Buffer.concat([IntToStr(0x0008), IntToStr(4)]);
518 r = Buffer.concat([IntToStr(0x0008), IntToStr(r.length + 4), r]);
521 // Cut the length to buflen if needed
522 if (r.length > buflen) { r = r.slice(0, buflen); }
524 obj.SendDataToHost(dev, true, r, featureRegister & 1);
526 case 0x4a: // GET_EV_STATUS - GET_EVENT_STATUS_NOTIFICATION
527 //var buflen = (cdb[7] << 8) + cdb[8];
528 //if (buflen == 0) { obj.SendDataToHost(dev, true, Buffer.concat([IntToStr(0x003c), IntToStr(0x0008)]), featureRegister & 1); return -1; } // TODO: Fixed this return, it's not correct.
529 if (obj.debug) console.log("SCSI: GET_EVENT_STATUS_NOTIFICATION", dev, cdb[1], cdb[4], cdb[9]);
530 if ((cdb[1] != 0x01) && (cdb[4] != 0x10)) {
531 if (obj.debug) console.log('SCSI ERROR');
532 obj.SendCommandEndResponse(1, 0x05, dev, 0x26, 0x01);
536 if ((dev == 0xA0) && (obj.floppy != null)) { present = 0x02; }
537 else if ((dev == 0xB0) && (obj.cdrom != null)) { present = 0x02; }
538 obj.SendDataToHost(dev, true, Buffer.from([0x00, present, 0x80, 0x00]), featureRegister & 1); // This is the original version, 4 bytes long
541 obj.SendCommand(0x51, Buffer.concat([IntToStrX(0), IntToStrX(0), IntToStrX(0), Buffer.from([0x87, 0x50, 0x03, 0x00, 0x00, 0x00, 0xb0, 0x51, 0x05, 0x20, 0x00])]), true);
543 case 0x51: // READ_DISC_INFO
544 if (obj.debug) console.log("SCSI READ_DISC_INFO", dev);
545 obj.SendCommandEndResponse(0, 0x05, dev, 0x20, 0x00); // Correct
547 case 0x55: // MODE_SELECT_10:
548 if (obj.debug) console.log("SCSI ERROR: MODE_SELECT_10", dev);
549 obj.SendCommandEndResponse(1, 0x05, dev, 0x20, 0x00);
551 case 0x5a: // MODE_SENSE_10
552 if (obj.debug) console.log("SCSI: MODE_SENSE_10", dev, cdb[2] & 0x3f);
553 var buflen = ReadShort(cdb, 7);
554 //var pc = cdb[2] & 0xc0;
557 if (buflen == 0) { obj.SendDataToHost(dev, true, Buffer.concat([IntToStr(0x003c), IntToStr(0x0008)]), featureRegister & 1); return -1; } // TODO: Fixed this return, it's not correct.
559 // 1.44 mb floppy or LS120 (sectorCount == 0x3c300)
562 if (obj.floppy != null) { sectorCount = (obj.floppy.size >> 9); }
564 if (obj.cdrom != null) { sectorCount = (obj.cdrom.size >> 11); }
567 switch (cdb[2] & 0x3f) {
568 case 0x01: if (dev == 0xA0) { r = (sectorCount <= 0xb40) ? IDE_ModeSence_FloppyError_Recovery_Array : IDE_ModeSence_Ls120Error_Recovery_Array; } else { r = IDE_ModeSence_CDError_Recovery_Array; } break;
569 case 0x05: if (dev == 0xA0) { r = (sectorCount <= 0xb40) ? IDE_ModeSence_FloppyDisk_Page_Array : IDE_ModeSence_LS120Disk_Page_Array; } break;
570 case 0x3f: if (dev == 0xA0) { r = (sectorCount <= 0xb40) ? IDE_ModeSence_3F_Floppy_Array : IDE_ModeSence_3F_LS120_Array; } else { r = IDE_ModeSence_3F_CD_Array; } break;
571 case 0x1A: if (dev == 0xB0) { r = IDE_ModeSence_CD_1A_Array; } break;
572 case 0x1D: if (dev == 0xB0) { r = IDE_ModeSence_CD_1D_Array; } break;
573 case 0x2A: if (dev == 0xB0) { r = IDE_ModeSence_CD_2A_Array; } break;
577 obj.SendCommandEndResponse(0, 0x05, dev, 0x20, 0x00); // TODO: Send proper error!!!
579 // Set disk to read only (we don't support write).
580 //ms_data[3] = ms_data[3] | 0x80;
581 obj.SendDataToHost(dev, true, r, featureRegister & 1);
584 case 0x51: // READ_DISK_INFORMATION
585 obj.SendDataToHost(dev, true, RD_CD_DiskInfo, featureRegister & 1);
587 case 0xAC: // GET_PERFORMANCE
588 obj.SendDataToHost(dev, true, RD_CD_Performance, featureRegister & 1);
590 default: // UNKNOWN COMMAND
591 if (obj.debug) console.log("IDER: Unknown SCSI command", cdb[0]);
592 obj.SendCommandEndResponse(0, 0x05, dev, 0x20, 0x00);
598 function sendDiskData(dev, lba, len, featureRegister) {
601 if (dev == 0xA0) { media = obj.floppy; if (obj.floppy != null) { mediaBlocks = (obj.floppy.size >> 9); } }
602 if (dev == 0xB0) { media = obj.cdrom; if (obj.cdrom != null) { mediaBlocks = (obj.cdrom.size >> 11); } }
603 if ((len < 0) || (lba + len > mediaBlocks)) { obj.SendCommandEndResponse(1, 0x05, dev, 0x21, 0x00); return 0; }
604 if (len == 0) { obj.SendCommandEndResponse(1, 0x00, dev, 0x00, 0x00); return 0; }
607 if (obj.sectorStats) { obj.sectorStats(1, (dev == 0xA0) ? 0 : 1, mediaBlocks, lba, len); }
608 if (dev == 0xA0) { lba <<= 9; len <<= 9; } else { lba <<= 11; len <<= 11; }
609 if (g_media !== null) {
610 // Queue read operation
611 g_readQueue.push({ media: media, dev: dev, lba: lba, len: len, fr: featureRegister });
613 // obj.iderinfo.readbfr // TODO: MaxRead
618 sendDiskDataEx(featureRegister);
623 var g_readQueue = [], g_dev, g_lba, g_len, g_media = null, g_reset = false;
624 function sendDiskDataEx(featureRegister) {
625 var len = g_len, lba = g_lba;
626 if (g_len > obj.iderinfo.readbfr) { len = obj.iderinfo.readbfr; }
630 var buffer = Buffer.alloc(len);
631 fs.read(g_media.ptr, buffer, 0, len, lba, function (error, bytesRead, buffer) {
632 obj.SendDataToHost(g_dev, (g_len == 0), buffer, featureRegister & 1);
633 if ((g_len > 0) && (g_reset == false)) {
634 sendDiskDataEx(featureRegister);
637 if (g_reset) { obj.SendCommand(0x47); g_readQueue = []; g_reset = false; } // Send ResetOccuredResponse
638 else if (g_readQueue.length > 0) { var op = g_readQueue.shift(); g_media = op.media; g_dev = op.dev; g_lba = op.lba; g_len = op.len; sendDiskDataEx(op.fr); } // Un-queue read operation
646function ShortToStr(v) { return Buffer.from([(v >> 8) & 0xFF, v & 0xFF]); }
647function ShortToStrX(v) { return Buffer.from([v & 0xFF, (v >> 8) & 0xFF]); }
648function IntToStr(v) { return Buffer.from([(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]); }
649function IntToStrX(v) { return Buffer.from([v & 0xFF, (v >> 8) & 0xFF, (v >> 16) & 0xFF, (v >> 24) & 0xFF]); }
650function ReadShort(v, p) { return (v[p] << 8) + v[p + 1]; }
651function ReadShortX(v, p) { return (v[p + 1] << 8) + v[p]; }
652function ReadInt(v, p) { return (v[p] * 0x1000000) + (v[p + 1] << 16) + (v[p + 2] << 8) + v[p + 3]; } // We use "*0x1000000" instead of "<<24" because the shift converts the number to signed int32.
653function ReadSInt(v, p) { return (v[p] << 24) + (v[p + 1] << 16) + (v[p + 2] << 8) + v[p + 3]; }
654function ReadIntX(v, p) { return (v[p + 3] * 0x1000000) + (v[p + 2] << 16) + (v[p + 1] << 8) + v[p]; }