mfm.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. # greaseweazle/codec/ibm/mfm.py
  2. #
  3. # Written & released by Keir Fraser <keir.xen@gmail.com>
  4. #
  5. # This is free and unencumbered software released into the public domain.
  6. # See the file COPYING for more details, or visit <http://unlicense.org>.
  7. import copy, heapq, struct, functools
  8. import itertools as it
  9. from bitarray import bitarray
  10. import crcmod.predefined
  11. from greaseweazle.track import MasterTrack, RawTrack
  12. default_revs = 2
  13. iam_sync_bytes = b'\x52\x24' * 3
  14. iam_sync = bitarray(endian='big')
  15. iam_sync.frombytes(iam_sync_bytes)
  16. sync_bytes = b'\x44\x89' * 3
  17. sync = bitarray(endian='big')
  18. sync.frombytes(sync_bytes)
  19. crc16 = crcmod.predefined.Crc('crc-ccitt-false')
  20. def sec_sz(n):
  21. return 128 << n if n <= 7 else 128 << 8
  22. class TrackArea:
  23. def __init__(self, start, end, crc=None):
  24. self.start = start
  25. self.end = end
  26. self.crc = crc
  27. def delta(self, delta):
  28. self.start -= delta
  29. self.end -= delta
  30. def __eq__(self, x):
  31. return (isinstance(x, type(self))
  32. and self.start == x.start
  33. and self.end == x.end
  34. and self.crc == x.crc)
  35. class IDAM(TrackArea):
  36. def __init__(self, start, end, crc, c, h, r, n):
  37. super().__init__(start, end, crc)
  38. self.c = c
  39. self.h = h
  40. self.r = r
  41. self.n = n
  42. def __str__(self):
  43. return ("IDAM:%6d-%6d c=%02x h=%02x r=%02x n=%02x CRC:%04x"
  44. % (self.start, self.end, self.c, self.h, self.r, self.n,
  45. self.crc))
  46. def __eq__(self, x):
  47. return (super().__eq__(x)
  48. and self.c == x.c and self.h == x.h
  49. and self.r == x.r and self.n == x.n)
  50. def __copy__(self):
  51. return IDAM(self.start, self.end, self.crc,
  52. self.c, self.h, self.r, self.n)
  53. class DAM(TrackArea):
  54. def __init__(self, start, end, crc, mark, data=None):
  55. super().__init__(start, end, crc)
  56. self.mark = mark
  57. self.data = data
  58. def __str__(self):
  59. return "DAM: %6d-%6d mark=%02x" % (self.start, self.end, self.mark)
  60. def __eq__(self, x):
  61. return (super().__eq__(x)
  62. and self.mark == x.mark
  63. and self.data == x.data)
  64. def __copy__(self):
  65. return DAM(self.start, self.end, self.crc, self.mark, self.data)
  66. class Sector(TrackArea):
  67. def __init__(self, idam, dam):
  68. super().__init__(idam.start, dam.end, idam.crc | dam.crc)
  69. self.idam = idam
  70. self.dam = dam
  71. def __str__(self):
  72. s = "Sec: %6d-%6d CRC:%04x\n" % (self.start, self.end, self.crc)
  73. s += " " + str(self.idam) + "\n"
  74. s += " " + str(self.dam)
  75. return s
  76. def delta(self, delta):
  77. super().delta(delta)
  78. self.idam.delta(delta)
  79. self.dam.delta(delta)
  80. def __eq__(self, x):
  81. return (super().__eq__(x)
  82. and self.idam == x.idam
  83. and self.dam == x.dam)
  84. class IAM(TrackArea):
  85. def __str__(self):
  86. return "IAM: %6d-%6d" % (self.start, self.end)
  87. def __copy__(self):
  88. return IAM(self.start, self.end)
  89. class IBM_MFM:
  90. IAM = 0xfc
  91. IDAM = 0xfe
  92. DAM = 0xfb
  93. DDAM = 0xf8
  94. gap_presync = 12
  95. gapbyte = 0x4e
  96. def __init__(self, cyl, head):
  97. self.cyl, self.head = cyl, head
  98. self.sectors = []
  99. self.iams = []
  100. def summary_string(self):
  101. nsec, nbad = len(self.sectors), self.nr_missing()
  102. s = "IBM MFM (%d/%d sectors)" % (nsec - nbad, nsec)
  103. if nbad != 0:
  104. s += " - %d sectors missing" % nbad
  105. return s
  106. def has_sec(self, sec_id):
  107. return self.sectors[sec_id].crc == 0
  108. def nr_missing(self):
  109. return len(list(filter(lambda x: x.crc != 0, self.sectors)))
  110. def flux(self, *args, **kwargs):
  111. return self.raw_track().flux(*args, **kwargs)
  112. def decode_raw(self, track):
  113. track.cue_at_index()
  114. raw = RawTrack(clock = self.clock, data = track)
  115. bits, _ = raw.get_all_data()
  116. areas = []
  117. idam = None
  118. ## 1. Calculate offsets within dump
  119. for offs in bits.itersearch(iam_sync):
  120. mark = decode(bits[offs+3*16:offs+4*16].tobytes())[0]
  121. if mark == IBM_MFM.IAM:
  122. areas.append(IAM(offs, offs+4*16))
  123. self.has_iam = True
  124. for offs in bits.itersearch(sync):
  125. mark = decode(bits[offs+3*16:offs+4*16].tobytes())[0]
  126. if mark == IBM_MFM.IDAM:
  127. s, e = offs, offs+10*16
  128. b = decode(bits[s:e].tobytes())
  129. c,h,r,n = struct.unpack(">4x4B2x", b)
  130. crc = crc16.new(b).crcValue
  131. if idam is not None:
  132. areas.append(idam)
  133. idam = IDAM(s, e, crc, c=c, h=h, r=r, n=n)
  134. elif mark == IBM_MFM.DAM or mark == IBM_MFM.DDAM:
  135. if idam is None or idam.end - offs > 1000:
  136. areas.append(DAM(offs, offs+4*16, 0xffff, mark=mark))
  137. else:
  138. sz = 128 << idam.n
  139. s, e = offs, offs+(4+sz+2)*16
  140. b = decode(bits[s:e].tobytes())
  141. crc = crc16.new(b).crcValue
  142. dam = DAM(s, e, crc, mark=mark, data=b[4:-2])
  143. areas.append(Sector(idam, dam))
  144. idam = None
  145. else:
  146. pass #print("Unknown mark %02x" % mark)
  147. if idam is not None:
  148. areas.append(idam)
  149. # Convert to offsets within track
  150. areas.sort(key=lambda x:x.start)
  151. index = iter(raw.revolutions)
  152. p, n = 0, next(index)
  153. for a in areas:
  154. if a.start >= n:
  155. p = n
  156. try:
  157. n = next(index)
  158. except StopIteration:
  159. n = float('inf')
  160. a.delta(p)
  161. areas.sort(key=lambda x:x.start)
  162. # Add to the deduped lists
  163. for a in areas:
  164. match = False
  165. if isinstance(a, IAM):
  166. list = self.iams
  167. elif isinstance(a, Sector):
  168. list = self.sectors
  169. else:
  170. continue
  171. for s in list:
  172. if abs(s.start - a.start) < 1000:
  173. match = True
  174. break
  175. if match and isinstance(a, Sector) and s.crc != 0 and a.crc == 0:
  176. self.sectors = [x for x in self.sectors if x != a]
  177. match = False
  178. if not match:
  179. list.append(a)
  180. def raw_track(self):
  181. areas = heapq.merge(self.iams, self.sectors, key=lambda x:x.start)
  182. t = bytes()
  183. for a in areas:
  184. start = a.start//16 - self.gap_presync
  185. gap = max(start - len(t)//2, 0)
  186. t += encode(bytes([self.gapbyte] * gap))
  187. t += encode(bytes(self.gap_presync))
  188. if isinstance(a, IAM):
  189. t += iam_sync_bytes
  190. t += encode(bytes([self.IAM]))
  191. elif isinstance(a, Sector):
  192. t += sync_bytes
  193. idam = bytes([0xa1, 0xa1, 0xa1, self.IDAM,
  194. a.idam.c, a.idam.h, a.idam.r, a.idam.n])
  195. idam += struct.pack('>H', crc16.new(idam).crcValue)
  196. t += encode(idam[3:])
  197. start = a.dam.start//16 - self.gap_presync
  198. gap = max(start - len(t)//2, 0)
  199. t += encode(bytes([self.gapbyte] * gap))
  200. t += encode(bytes(self.gap_presync))
  201. t += sync_bytes
  202. dam = bytes([0xa1, 0xa1, 0xa1, a.dam.mark]) + a.dam.data
  203. dam += struct.pack('>H', crc16.new(dam).crcValue)
  204. t += encode(dam[3:])
  205. # Add the pre-index gap.
  206. tlen = int((self.time_per_rev / self.clock) // 16)
  207. gap = max(tlen - len(t)//2, 0)
  208. t += encode(bytes([self.gapbyte] * gap))
  209. track = MasterTrack(
  210. bits = mfm_encode(t),
  211. time_per_rev = self.time_per_rev)
  212. track.verify = self
  213. track.verify_revs = default_revs
  214. return track
  215. class IBM_MFM_Formatted(IBM_MFM):
  216. gap_4a = 80 # Post-Index
  217. gap_1 = 50 # Post-IAM
  218. gap_2 = 22 # Post-IDAM
  219. def __init__(self, cyl, head):
  220. super().__init__(cyl, head)
  221. self.raw_iams, self.raw_sectors = [], []
  222. def decode_raw(self, track):
  223. iams, sectors = self.iams, self.sectors
  224. self.iams, self.sectors = self.raw_iams, self.raw_sectors
  225. super().decode_raw(track)
  226. self.iams, self.sectors = iams, sectors
  227. for r in self.raw_sectors:
  228. if r.idam.crc != 0:
  229. continue
  230. for s in self.sectors:
  231. if (s.idam.c == r.idam.c and
  232. s.idam.h == r.idam.h and
  233. s.idam.r == r.idam.r and
  234. s.idam.n == r.idam.n):
  235. s.idam.crc = 0
  236. if r.dam.crc == 0 and s.dam.crc != 0:
  237. s.dam.crc = s.crc = 0
  238. s.dam.data = r.dam.data
  239. def set_img_track(self, tdat):
  240. pos = 0
  241. self.sectors.sort(key = lambda x: x.idam.r)
  242. totsize = functools.reduce(lambda x, y: x + (128<<y.idam.n),
  243. self.sectors, 0)
  244. if len(tdat) < totsize:
  245. tdat += bytes(totsize - len(tdat))
  246. for s in self.sectors:
  247. s.crc = s.idam.crc = s.dam.crc = 0
  248. size = 128 << s.idam.n
  249. s.dam.data = tdat[pos:pos+size]
  250. pos += size
  251. self.sectors.sort(key = lambda x: x.start)
  252. return totsize
  253. def get_img_track(self):
  254. tdat = bytearray()
  255. sectors = self.sectors.copy()
  256. sectors.sort(key = lambda x: x.idam.r)
  257. for s in sectors:
  258. tdat += s.dam.data
  259. return tdat
  260. def verify_track(self, flux):
  261. readback_track = IBM_MFM_Formatted(self.cyl, self.head)
  262. readback_track.clock = self.clock
  263. readback_track.time_per_rev = self.time_per_rev
  264. for x in self.iams:
  265. readback_track.iams.append(copy.copy(x))
  266. for x in self.sectors:
  267. idam, dam = copy.copy(x.idam), copy.copy(x.dam)
  268. idam.crc, dam.crc = 0xffff, 0xffff
  269. readback_track.sectors.append(Sector(idam, dam))
  270. readback_track.decode_raw(flux)
  271. if readback_track.nr_missing() != 0:
  272. return False
  273. return self.sectors == readback_track.sectors
  274. class IBM_MFM_Predefined(IBM_MFM_Formatted):
  275. cskew = 0
  276. hskew = 0
  277. interleave = 1
  278. def __init__(self, cyl, head):
  279. super().__init__(cyl, head)
  280. # Create logical sector map in rotational order
  281. sec_map = [-1] * self.nsec
  282. pos = (cyl*self.cskew + head*self.hskew) % self.nsec
  283. for i in range(self.nsec):
  284. while sec_map[pos] != -1:
  285. pos = (pos + 1) % self.nsec
  286. sec_map[pos] = i
  287. pos = (pos + self.interleave) % self.nsec
  288. pos = self.gap_4a
  289. if self.gap_1 is not None:
  290. self.iams = [IAM(pos*16,(pos+4)*16)]
  291. pos += 4 + self.gap_1
  292. for i in range(self.nsec):
  293. pos += self.gap_presync
  294. idam = IDAM(pos*16, (pos+10)*16, 0xffff,
  295. c=cyl, h=head, r=self.id0+sec_map[i], n = self.sz)
  296. pos += 10 + self.gap_2 + self.gap_presync
  297. size = 128 << self.sz
  298. dam = DAM(pos*16, (pos+4+size+2)*16, 0xffff,
  299. mark=self.DAM, data=bytes(size))
  300. self.sectors.append(Sector(idam, dam))
  301. pos += 4 + size + 2 + self.gap_3
  302. @classmethod
  303. def decode_track(cls, cyl, head, track):
  304. mfm = cls(cyl, head)
  305. mfm.decode_raw(track)
  306. return mfm
  307. class IBM_MFM_720(IBM_MFM_Predefined):
  308. time_per_rev = 0.2
  309. clock = 2e-6
  310. gap_3 = 84 # Post-DAM
  311. nsec = 9
  312. id0 = 1
  313. sz = 2
  314. class IBM_MFM_800(IBM_MFM_720):
  315. gap_3 = 30
  316. nsec = 10
  317. class IBM_MFM_1200(IBM_MFM_720):
  318. time_per_rev = 60/360
  319. clock = 1e-6
  320. nsec = 15
  321. class IBM_MFM_1440(IBM_MFM_720):
  322. clock = 1e-6
  323. nsec = 18
  324. class AtariST_SS_9SPT(IBM_MFM_720):
  325. gap_1 = None
  326. cskew = 2
  327. class AtariST_DS_9SPT(IBM_MFM_720):
  328. gap_1 = None
  329. cskew = 4
  330. hskew = 2
  331. class AtariST_10SPT(IBM_MFM_720):
  332. gap_1 = None
  333. gap_3 = 30
  334. nsec = 10
  335. class AtariST_11SPT(IBM_MFM_720):
  336. clock = 2e-6 * 0.96 # long track
  337. gap_1 = None
  338. gap_3 = 3
  339. nsec = 11
  340. class Acorn_ADFS_640(IBM_MFM_Predefined):
  341. time_per_rev = 0.2
  342. clock = 2e-6
  343. gap_3 = 57
  344. nsec = 16
  345. id0 = 0
  346. sz = 1
  347. class Acorn_ADFS_800(IBM_MFM_Predefined):
  348. time_per_rev = 0.2
  349. clock = 2e-6
  350. gap_3 = 116
  351. nsec = 5
  352. id0 = 0
  353. sz = 3
  354. class Acorn_ADFS_1600(IBM_MFM_Predefined):
  355. time_per_rev = 0.2
  356. clock = 1e-6
  357. gap_3 = 116
  358. nsec = 10
  359. id0 = 0
  360. sz = 3
  361. def mfm_encode(dat):
  362. y = 0
  363. out = bytearray()
  364. for x in dat:
  365. y = (y<<8) | x
  366. if (x & 0xaa) == 0:
  367. y |= ~((y>>1)|(y<<1)) & 0xaaaa
  368. y &= 255
  369. out.append(y)
  370. return bytes(out)
  371. encode_list = []
  372. for x in range(256):
  373. y = 0
  374. for i in range(8):
  375. y <<= 2
  376. y |= (x >> (7-i)) & 1
  377. encode_list.append(y)
  378. def encode(dat):
  379. out = bytearray()
  380. for x in dat:
  381. out += struct.pack('>H', encode_list[x])
  382. return bytes(out)
  383. decode_list = bytearray()
  384. for x in range(0x5555+1):
  385. y = 0
  386. for i in range(16):
  387. if x&(1<<(i*2)):
  388. y |= 1<<i
  389. decode_list.append(y)
  390. def decode(dat):
  391. out = bytearray()
  392. for x,y in zip(dat[::2], dat[1::2]):
  393. out.append(decode_list[((x<<8)|y)&0x5555])
  394. return bytes(out)
  395. # Local variables:
  396. # python-indent: 4
  397. # End: