floppy.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. "use strict";
  2. // https://www.isdaman.com/alsos/hardware/fdc/floppy.htm
  3. // https://wiki.osdev.org/Floppy_Disk_Controller
  4. const DIR_DOOR = 0x80;
  5. const ST1_NID = 1 << 0;
  6. const ST1_NDAT = 1 << 2;
  7. /**
  8. * @constructor
  9. *
  10. * @param {CPU} cpu
  11. */
  12. function FloppyController(cpu, fda_image, fdb_image)
  13. {
  14. /** @const @type {IO|undefined} */
  15. this.io = cpu.io;
  16. /** @const @type {CPU} */
  17. this.cpu = cpu;
  18. /** @const @type {DMA} */
  19. this.dma = cpu.devices.dma;
  20. this.bytes_expecting = 0;
  21. this.receiving_command = new Uint8Array(10);
  22. this.receiving_index = 0;
  23. this.next_command = null;
  24. this.response_data = new Uint8Array(10);
  25. this.response_index = 0;
  26. this.response_length = 0;
  27. this.status_reg0 = 0;
  28. this.status_reg1 = 0;
  29. this.status_reg2 = 0;
  30. this.drive = 0;
  31. this.last_cylinder = 0;
  32. this.last_head = 0;
  33. this.last_sector = 1;
  34. // this should actually be write-only ... but people read it anyway
  35. this.dor = 0;
  36. this.dir = 0;
  37. this.fda_image = null;
  38. this.fdb_image = null;
  39. if(!fda_image)
  40. {
  41. this.eject_fda();
  42. this.cpu.devices.rtc.cmos_write(CMOS_FLOPPY_DRIVE_TYPE, 4 << 4);
  43. }
  44. else
  45. {
  46. this.set_fda(fda_image);
  47. }
  48. dbg_assert(!fdb_image, "FDB not supported");
  49. this.io.register_read(0x3F0, this, this.port3F0_read);
  50. this.io.register_read(0x3F2, this, this.port3F2_read);
  51. this.io.register_read(0x3F4, this, this.port3F4_read);
  52. this.io.register_read(0x3F5, this, this.port3F5_read);
  53. this.io.register_read(0x3F7, this, this.port3F7_read);
  54. this.io.register_write(0x3F2, this, this.port3F2_write);
  55. this.io.register_write(0x3F4, this, this.port3F4_write);
  56. this.io.register_write(0x3F5, this, this.port3F5_write);
  57. }
  58. FloppyController.prototype.eject_fda = function()
  59. {
  60. this.fda_image = null;
  61. this.sectors_per_track = 0;
  62. this.number_of_heads = 0;
  63. this.number_of_cylinders = 0;
  64. this.dir = DIR_DOOR;
  65. };
  66. FloppyController.prototype.set_fda = function(fda_image)
  67. {
  68. var floppy_types = {
  69. [160 * 1024]: { type: 1, tracks: 40, sectors: 8, heads: 1 },
  70. [180 * 1024]: { type: 1, tracks: 40, sectors: 9, heads: 1 },
  71. [200 * 1024]: { type: 1, tracks: 40, sectors: 10, heads: 1 },
  72. [320 * 1024]: { type: 1, tracks: 40, sectors: 8, heads: 2 },
  73. [360 * 1024]: { type: 1, tracks: 40, sectors: 9, heads: 2 },
  74. [400 * 1024]: { type: 1, tracks: 40, sectors: 10, heads: 2 },
  75. [720 * 1024]: { type: 3, tracks: 80, sectors: 9, heads: 2 },
  76. [1200 * 1024]: { type: 2, tracks: 80, sectors: 15, heads: 2 },
  77. [1440 * 1024]: { type: 4, tracks: 80, sectors: 18, heads: 2 },
  78. [1722 * 1024]: { type: 5, tracks: 82, sectors: 21, heads: 2 },
  79. [2880 * 1024]: { type: 5, tracks: 80, sectors: 36, heads: 2 },
  80. // not a real floppy type, used to support sectorlisp and friends
  81. 512: { type: 1, tracks: 1, sectors: 1, heads: 1 },
  82. };
  83. let floppy_size = fda_image.byteLength;
  84. let floppy_type = floppy_types[floppy_size];
  85. if (!floppy_type)
  86. {
  87. floppy_size = fda_image.byteLength > 1440 * 1024 ? 2880 * 1024 : 1440 * 1024;
  88. floppy_type = floppy_types[floppy_size];
  89. // Note: this may prevent the "Get floppy image" functionality from working
  90. dbg_assert(fda_image.buffer && fda_image.buffer instanceof ArrayBuffer);
  91. const new_image = new Uint8Array(floppy_size);
  92. new_image.set(new Uint8Array(fda_image.buffer));
  93. fda_image = new v86util.SyncBuffer(new_image.buffer);
  94. dbg_log("Warning: Unkown floppy size: " + fda_image.byteLength + ", assuming " + floppy_size);
  95. }
  96. this.sectors_per_track = floppy_type.sectors;
  97. this.number_of_heads = floppy_type.heads;
  98. this.number_of_cylinders = floppy_type.tracks;
  99. this.fda_image = fda_image;
  100. this.dir = DIR_DOOR;
  101. // this is probably not supposed to change at runtime
  102. this.cpu.devices.rtc.cmos_write(CMOS_FLOPPY_DRIVE_TYPE, floppy_type.type << 4);
  103. };
  104. FloppyController.prototype.get_state = function()
  105. {
  106. var state = [];
  107. state[0] = this.bytes_expecting;
  108. state[1] = this.receiving_command;
  109. state[2] = this.receiving_index;
  110. //state[3] = this.next_command;
  111. state[4] = this.response_data;
  112. state[5] = this.response_index;
  113. state[6] = this.response_length;
  114. state[8] = this.status_reg0;
  115. state[9] = this.status_reg1;
  116. state[10] = this.status_reg2;
  117. state[11] = this.drive;
  118. state[12] = this.last_cylinder;
  119. state[13] = this.last_head;
  120. state[14] = this.last_sector;
  121. state[15] = this.dor;
  122. state[16] = this.sectors_per_track;
  123. state[17] = this.number_of_heads;
  124. state[18] = this.number_of_cylinders;
  125. return state;
  126. };
  127. FloppyController.prototype.set_state = function(state)
  128. {
  129. this.bytes_expecting = state[0];
  130. this.receiving_command = state[1];
  131. this.receiving_index = state[2];
  132. this.next_command = state[3];
  133. this.response_data = state[4];
  134. this.response_index = state[5];
  135. this.response_length = state[6];
  136. this.status_reg0 = state[8];
  137. this.status_reg1 = state[9];
  138. this.status_reg2 = state[10];
  139. this.drive = state[11];
  140. this.last_cylinder = state[12];
  141. this.last_head = state[13];
  142. this.last_sector = state[14];
  143. this.dor = state[15];
  144. this.sectors_per_track = state[16];
  145. this.number_of_heads = state[17];
  146. this.number_of_cylinders = state[18];
  147. };
  148. FloppyController.prototype.port3F0_read = function()
  149. {
  150. dbg_log("3F0 read", LOG_FLOPPY);
  151. return 0;
  152. };
  153. FloppyController.prototype.port3F4_read = function()
  154. {
  155. dbg_log("3F4 read", LOG_FLOPPY);
  156. var return_byte = 0x80;
  157. if(this.response_index < this.response_length)
  158. {
  159. return_byte |= 0x40 | 0x10;
  160. }
  161. if((this.dor & 8) === 0)
  162. {
  163. return_byte |= 0x20;
  164. }
  165. return return_byte;
  166. };
  167. FloppyController.prototype.port3F7_read = function()
  168. {
  169. dbg_log("3F7 read", LOG_FLOPPY);
  170. return this.dir;
  171. };
  172. FloppyController.prototype.port3F5_read = function()
  173. {
  174. if(this.response_index < this.response_length)
  175. {
  176. dbg_log("3F5 read: " + this.response_data[this.response_index], LOG_FLOPPY);
  177. this.cpu.device_lower_irq(6);
  178. return this.response_data[this.response_index++];
  179. }
  180. else
  181. {
  182. dbg_log("3F5 read, empty", LOG_FLOPPY);
  183. return 0xFF;
  184. }
  185. };
  186. FloppyController.prototype.port3F4_write = function(byte)
  187. {
  188. dbg_log("3F4/data rate write: " + h(byte), LOG_FLOPPY);
  189. if(byte & 0x80)
  190. {
  191. dbg_log("dsr reset", LOG_FLOPPY);
  192. this.status_reg0 = 0xC0;
  193. this.cpu.device_raise_irq(6);
  194. }
  195. };
  196. FloppyController.prototype.port3F5_write = function(reg_byte)
  197. {
  198. dbg_log("3F5 write " + h(reg_byte), LOG_FLOPPY);
  199. if(this.bytes_expecting > 0)
  200. {
  201. this.receiving_command[this.receiving_index++] = reg_byte;
  202. this.bytes_expecting--;
  203. if(this.bytes_expecting === 0)
  204. {
  205. if(DEBUG)
  206. {
  207. var log = "3F5 command received: ";
  208. for(var i = 0; i < this.receiving_index; i++)
  209. log += h(this.receiving_command[i]) + " ";
  210. dbg_log(log, LOG_FLOPPY);
  211. }
  212. this.next_command.call(this, this.receiving_command);
  213. }
  214. }
  215. else
  216. {
  217. switch(reg_byte)
  218. {
  219. // TODO
  220. //case 2:
  221. //this.next_command = read_complete_track;
  222. //this.bytes_expecting = 8;
  223. //break;
  224. case 0x03:
  225. this.next_command = this.fix_drive_data;
  226. this.bytes_expecting = 2;
  227. break;
  228. case 0x13:
  229. this.next_command = this.configure;
  230. this.bytes_expecting = 3;
  231. break;
  232. case 0x04:
  233. this.next_command = this.check_drive_status;
  234. this.bytes_expecting = 1;
  235. break;
  236. // writes
  237. case 0x05:
  238. case 0x45:
  239. case 0xC5:
  240. this.next_command = function(args) { this.do_sector(true, args); };
  241. this.bytes_expecting = 8;
  242. break;
  243. case 0x06:
  244. case 0x46:
  245. case 0xE6:
  246. this.next_command = function(args) { this.do_sector(false, args); };
  247. this.bytes_expecting = 8;
  248. break;
  249. case 0x07:
  250. this.next_command = this.calibrate;
  251. this.bytes_expecting = 1;
  252. break;
  253. case 0x08:
  254. this.check_interrupt_status();
  255. break;
  256. case 0x4A:
  257. this.next_command = this.read_sector_id;
  258. this.bytes_expecting = 1;
  259. break;
  260. case 0x0F:
  261. this.bytes_expecting = 2;
  262. this.next_command = this.seek;
  263. break;
  264. case 0x0E: // dump registers (not implemented)
  265. case 0x10: // determine controller version (winxp, not implemented)
  266. dbg_log(reg_byte === 0x0E ? "dump registers" : "determine controller version", LOG_FLOPPY);
  267. this.status_reg0 = 0x80;
  268. this.response_data[0] = this.status_reg0;
  269. this.response_index = 0;
  270. this.response_length = 1;
  271. this.bytes_expecting = 0;
  272. break;
  273. default:
  274. dbg_assert(false, "Unimplemented floppy command call " + h(reg_byte));
  275. }
  276. this.receiving_index = 0;
  277. }
  278. };
  279. FloppyController.prototype.port3F2_read = function()
  280. {
  281. dbg_log("read 3F2: DOR", LOG_FLOPPY);
  282. return this.dor;
  283. };
  284. FloppyController.prototype.port3F2_write = function(value)
  285. {
  286. if((value & 4) === 4 && (this.dor & 4) === 0)
  287. {
  288. // clear reset mode
  289. this.status_reg0 = 0xC0;
  290. this.cpu.device_raise_irq(6);
  291. }
  292. dbg_log("start motors: " + h(value >> 4), LOG_FLOPPY);
  293. dbg_log("enable dma/irq: " + !!(value & 8), LOG_FLOPPY);
  294. dbg_log("reset fdc: " + !!(value & 4), LOG_FLOPPY);
  295. dbg_log("drive select: " + (value & 3), LOG_FLOPPY);
  296. if((value & 3) !== 0)
  297. {
  298. dbg_log("guest: fdb not implemented", LOG_FLOPPY);
  299. }
  300. dbg_log("DOR = " + h(value), LOG_FLOPPY);
  301. this.dor = value;
  302. };
  303. FloppyController.prototype.check_drive_status = function(args)
  304. {
  305. dbg_log("check drive status", LOG_FLOPPY);
  306. // do nothing if no fda
  307. if (this.fda_image)
  308. {
  309. this.status_reg1 = 0;
  310. }
  311. else
  312. {
  313. // TODO: is this right?
  314. this.status_reg1 = ST1_NDAT | ST1_NID;
  315. }
  316. this.response_index = 0;
  317. this.response_length = 1;
  318. this.response_data[0] = 0;
  319. };
  320. FloppyController.prototype.seek = function(args)
  321. {
  322. dbg_log("seek", LOG_FLOPPY);
  323. if((args[0] & 3) !== 0)
  324. {
  325. dbg_log("seek on fdb", LOG_FLOPPY);
  326. this.raise_irq();
  327. return;
  328. }
  329. let new_cylinder = args[1];
  330. let new_head = args[0] >> 2 & 1;
  331. // clear eject flag if seek takes us to a new cylinder
  332. if(new_cylinder !== this.last_cylinder)
  333. {
  334. this.dir = 0x0;
  335. }
  336. // do nothing if no fda
  337. if (this.fda_image)
  338. {
  339. this.status_reg1 = 0;
  340. }
  341. else
  342. {
  343. // TODO: is this right?
  344. this.status_reg1 = ST1_NDAT | ST1_NID;
  345. }
  346. this.status_reg0 = 0x20;
  347. this.last_cylinder = new_cylinder;
  348. this.last_head = new_head;
  349. this.raise_irq();
  350. };
  351. FloppyController.prototype.calibrate = function(args)
  352. {
  353. // TODO fdb support: args[0] indicates which drive
  354. dbg_log("floppy calibrate", LOG_FLOPPY);
  355. // This is implemented using seek to make sure last_cylinder, dir, etc are updated properly.
  356. this.seek([args[0], 0]);
  357. };
  358. FloppyController.prototype.check_interrupt_status = function()
  359. {
  360. dbg_log("floppy check interrupt status", LOG_FLOPPY);
  361. this.response_index = 0;
  362. this.response_length = 2;
  363. this.response_data[0] = this.status_reg0;
  364. this.response_data[1] = this.last_cylinder;
  365. };
  366. FloppyController.prototype.do_sector = function(is_write, args)
  367. {
  368. var head = args[2],
  369. cylinder = args[1],
  370. sector = args[3],
  371. sector_size = 128 << args[4],
  372. read_count = args[5] - args[3] + 1,
  373. read_offset = ((head + this.number_of_heads * cylinder) * this.sectors_per_track + sector - 1) * sector_size;
  374. dbg_log("Floppy " + (is_write ? "Write" : "Read"), LOG_FLOPPY);
  375. dbg_log("from " + h(read_offset) + " length " + h(read_count * sector_size), LOG_FLOPPY);
  376. dbg_log(cylinder + " / " + head + " / " + sector, LOG_FLOPPY);
  377. if(!args[4])
  378. {
  379. dbg_log("FDC: sector count is zero, use data length instead", LOG_FLOPPY);
  380. }
  381. if(!this.fda_image)
  382. {
  383. this.status_reg1 = ST1_NDAT | ST1_NID;
  384. return;
  385. }
  386. this.status_reg1 = 0;
  387. if(is_write)
  388. {
  389. this.dma.do_write(this.fda_image, read_offset, read_count * sector_size, 2, this.done.bind(this, args, cylinder, head, sector));
  390. }
  391. else
  392. {
  393. this.dma.do_read(this.fda_image, read_offset, read_count * sector_size, 2, this.done.bind(this, args, cylinder, head, sector));
  394. }
  395. };
  396. FloppyController.prototype.done = function(args, cylinder, head, sector, error)
  397. {
  398. if(error)
  399. {
  400. // TODO: Set appropriate bits
  401. dbg_log("XXX: Unhandled floppy error", LOG_FLOPPY);
  402. return;
  403. }
  404. sector++;
  405. if(sector > this.sectors_per_track)
  406. {
  407. sector = 1;
  408. head++;
  409. if(head >= this.number_of_heads)
  410. {
  411. head = 0;
  412. cylinder++;
  413. }
  414. }
  415. // clear eject flag if seek or write has taken us to a new cylinder
  416. if(cylinder !== this.last_cylinder)
  417. {
  418. this.dir = 0x0;
  419. }
  420. this.status_reg0 = 0x20;
  421. this.last_cylinder = cylinder;
  422. this.last_head = head;
  423. this.last_sector = sector;
  424. this.response_index = 0;
  425. this.response_length = 7;
  426. this.response_data[0] = head << 2 | 0x20;
  427. this.response_data[1] = 0;
  428. this.response_data[2] = 0;
  429. this.response_data[3] = cylinder;
  430. this.response_data[4] = head;
  431. this.response_data[5] = sector;
  432. this.response_data[6] = args[4];
  433. this.raise_irq();
  434. };
  435. FloppyController.prototype.fix_drive_data = function(args)
  436. {
  437. dbg_log("floppy fix drive data " + args.slice(0, this.bytes_expecting), LOG_FLOPPY);
  438. };
  439. FloppyController.prototype.configure = function(args)
  440. {
  441. dbg_log("floppy configure " + args.slice(0, this.bytes_expecting), LOG_FLOPPY);
  442. };
  443. FloppyController.prototype.read_sector_id = function(args)
  444. {
  445. dbg_log("floppy read sector id " + args, LOG_FLOPPY);
  446. this.response_index = 0;
  447. this.response_length = 7;
  448. this.response_data[0] = 0;
  449. this.response_data[1] = 0;
  450. this.response_data[2] = 0;
  451. this.response_data[3] = 0;
  452. this.response_data[4] = 0;
  453. this.response_data[5] = 0;
  454. this.response_data[6] = 0;
  455. this.raise_irq();
  456. };
  457. FloppyController.prototype.raise_irq = function()
  458. {
  459. if(this.dor & 8)
  460. {
  461. this.cpu.device_raise_irq(6);
  462. }
  463. };