floppy.js 14 KB

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