mfm.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  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 heapq, struct
  8. import itertools as it
  9. from bitarray import bitarray
  10. import crcmod.predefined
  11. from greaseweazle.track import MasterTrack, RawTrack
  12. default_trackset = 'c=0-79:h=0-1'
  13. default_revs = 2
  14. iam_sync_bytes = b'\x52\x24' * 3
  15. iam_sync = bitarray(endian='big')
  16. iam_sync.frombytes(iam_sync_bytes)
  17. sync_bytes = b'\x44\x89' * 3
  18. sync = bitarray(endian='big')
  19. sync.frombytes(sync_bytes)
  20. crc16 = crcmod.predefined.Crc('crc-ccitt-false')
  21. class TrackArea:
  22. def __init__(self, start, end, crc=None):
  23. self.start = start
  24. self.end = end
  25. self.crc = crc
  26. def delta(self, delta):
  27. self.start -= delta
  28. self.end -= delta
  29. def __eq__(self, x):
  30. return (isinstance(x, type(self))
  31. and self.start == x.start
  32. and self.end == x.end
  33. and self.crc == x.crc)
  34. class IDAM(TrackArea):
  35. def __init__(self, start, end, crc, c, h, r, n):
  36. super().__init__(start, end, crc)
  37. self.c = c
  38. self.h = h
  39. self.r = r
  40. self.n = n
  41. def __str__(self):
  42. return ("IDAM:%6d-%6d c=%02x h=%02x r=%02x n=%02x CRC:%04x"
  43. % (self.start, self.end, self.c, self.h, self.r, self.n,
  44. self.crc))
  45. def __eq__(self, x):
  46. return (super().__eq__(x)
  47. and self.c == x.c and self.h == x.h
  48. and self.r == x.r and self.n == x.n)
  49. class DAM(TrackArea):
  50. def __init__(self, start, end, crc, mark, data=None):
  51. super().__init__(start, end, crc)
  52. self.mark = mark
  53. self.data = data
  54. def __str__(self):
  55. return "DAM: %6d-%6d mark=%02x" % (self.start, self.end, self.mark)
  56. def __eq__(self, x):
  57. return (super().__eq__(x)
  58. and self.mark == x.mark
  59. and self.data == x.data)
  60. class Sector(TrackArea):
  61. def __init__(self, idam, dam):
  62. super().__init__(idam.start, dam.end, idam.crc | dam.crc)
  63. self.idam = idam
  64. self.dam = dam
  65. def __str__(self):
  66. s = "Sec: %6d-%6d CRC:%04x\n" % (self.start, self.end, self.crc)
  67. s += " " + str(self.idam) + "\n"
  68. s += " " + str(self.dam)
  69. return s
  70. def delta(self, delta):
  71. super().delta(delta)
  72. self.idam.delta(delta)
  73. self.dam.delta(delta)
  74. def __eq__(self, x):
  75. return (super().__eq__(x)
  76. and self.idam == x.idam
  77. and self.dam == x.dam)
  78. class IAM(TrackArea):
  79. def __str__(self):
  80. return "IAM: %6d-%6d" % (self.start, self.end)
  81. class IBM_MFM:
  82. IAM = 0xfc
  83. IDAM = 0xfe
  84. DAM = 0xfb
  85. DDAM = 0xf8
  86. gap_presync = 12
  87. filler = 0x4e
  88. def __init__(self, cyl, head):
  89. self.cyl, self.head = cyl, head
  90. self.sectors = []
  91. self.iams = []
  92. def summary_string(self):
  93. return "IBM MFM"
  94. def has_sec(self, sec_id):
  95. return self.sectors[sec_id].crc == 0
  96. def nr_missing(self):
  97. return len(list(filter(lambda x: x.crc != 0, self.sectors)))
  98. def flux_for_writeout(self, *args, **kwargs):
  99. return self.raw_track().flux_for_writeout(args, kwargs)
  100. def flux(self, *args, **kwargs):
  101. return self.raw_track().flux(args, kwargs)
  102. def decode_raw(self, track):
  103. track.cue_at_index()
  104. raw = RawTrack(clock = 1e-6, data = track)
  105. bits, _ = raw.get_all_data()
  106. areas = []
  107. idam = None
  108. ## 1. Calculate offsets within dump
  109. for offs in bits.itersearch(iam_sync):
  110. mark = decode(bits[offs+3*16:offs+4*16].tobytes())[0]
  111. if mark == IBM_MFM.IAM:
  112. areas.append(IAM(offs, offs+4*16))
  113. self.has_iam = True
  114. for offs in bits.itersearch(sync):
  115. mark = decode(bits[offs+3*16:offs+4*16].tobytes())[0]
  116. if mark == IBM_MFM.IDAM:
  117. s, e = offs, offs+10*16
  118. b = decode(bits[s:e].tobytes())
  119. c,h,r,n = struct.unpack(">4x4B2x", b)
  120. crc = crc16.new(b).crcValue
  121. if idam is not None:
  122. areas.append(idam)
  123. idam = IDAM(s, e, crc, c=c, h=h, r=r, n=n)
  124. elif mark == IBM_MFM.DAM or mark == IBM_MFM.DDAM:
  125. if idam is None or idam.end - offs > 1000:
  126. areas.append(DAM(offs, offs+4*16, 0xffff, mark=mark))
  127. else:
  128. sz = 128 << idam.n
  129. s, e = offs, offs+(4+sz+2)*16
  130. b = decode(bits[s:e].tobytes())
  131. crc = crc16.new(b).crcValue
  132. dam = DAM(s, e, crc, mark=mark, data=b[4:-2])
  133. areas.append(Sector(idam, dam))
  134. idam = None
  135. else:
  136. print("Unknown mark %02x" % mark)
  137. if idam is not None:
  138. areas.append(idam)
  139. # Convert to offsets within track
  140. areas.sort(key=lambda x:x.start)
  141. index = iter(raw.revolutions)
  142. p, n = 0, next(index)
  143. for a in areas:
  144. if a.start >= n:
  145. p = n
  146. try:
  147. n = next(index)
  148. except StopIteration:
  149. n = float('inf')
  150. a.delta(p)
  151. areas.sort(key=lambda x:x.start)
  152. # Add to the deduped lists
  153. for a in areas:
  154. match = False
  155. if isinstance(a, IAM):
  156. list = self.iams
  157. elif isinstance(a, Sector):
  158. list = self.sectors
  159. else:
  160. continue
  161. for s in list:
  162. if abs(s.start - a.start) < 1000:
  163. match = True
  164. break
  165. if match and list == self.sectors and s.crc != 0 and a.crc == 0:
  166. self.sectors = [x for x in self.sectors if x != a]
  167. match = False
  168. if not match:
  169. list.append(a)
  170. def raw_track(self):
  171. areas = heapq.merge(self.iams, self.sectors, key=lambda x:x.start)
  172. t = bytes()
  173. for a in areas:
  174. start = a.start//16 - self.gap_presync
  175. gap = max(start - len(t)//2, 0)
  176. t += encode(bytes([self.filler] * gap))
  177. t += encode(bytes(self.gap_presync))
  178. if isinstance(a, IAM):
  179. t += iam_sync_bytes
  180. t += encode(bytes([self.IAM]))
  181. elif isinstance(a, Sector):
  182. t += sync_bytes
  183. idam = bytes([0xa1, 0xa1, 0xa1, self.IDAM,
  184. a.idam.c, a.idam.h, a.idam.r, a.idam.n])
  185. idam += struct.pack('>H', crc16.new(idam).crcValue)
  186. t += encode(idam[3:])
  187. start = a.dam.start//16 - self.gap_presync
  188. gap = max(start - len(t)//2, 0)
  189. t += encode(bytes([self.filler] * gap))
  190. t += encode(bytes(self.gap_presync))
  191. t += sync_bytes
  192. dam = bytes([0xa1, 0xa1, 0xa1, a.dam.mark]) + a.dam.data
  193. dam += struct.pack('>H', crc16.new(dam).crcValue)
  194. t += encode(dam[3:])
  195. # Add the pre-index gap.
  196. tlen = 200000//16
  197. gap = max(tlen - len(t)//2, 0)
  198. t += encode(bytes([self.filler] * gap))
  199. track = MasterTrack(
  200. bits = mfm_encode(t),
  201. time_per_rev = 0.2)
  202. track.verify = self
  203. track.verify_revs = default_revs
  204. return track
  205. def verify_track(self, flux):
  206. readback_track = decode_track(self.cyl, self.head, flux)
  207. if readback_track.nr_missing() != 0:
  208. return False
  209. return self.sectors == readback_track.sectors
  210. class IBM_MFM_Formatted(IBM_MFM):
  211. gap_4a = 80 # Post-Index
  212. gap_1 = 50 # Post-IAM
  213. gap_2 = 22 # Post-IDAM
  214. def __init__(self, cyl, head):
  215. super().__init__(cyl, head)
  216. self.raw_iams, self.raw_sectors = [], []
  217. pos = self.gap_4a
  218. if self.gap_1 is not None:
  219. self.iams = [IAM(pos*16,(pos+4)*16)]
  220. pos += self.gap_1
  221. for i in range(self.nsec):
  222. pos += self.gap_presync
  223. idam = IDAM(pos*16, (pos+10)*16, 0xffff,
  224. c=cyl, h=head, r=self.id0+i, n = self.sz)
  225. pos += 10 + self.gap_2 + self.gap_presync
  226. size = 128 << self.sz
  227. dam = DAM(pos*16, (pos+4+size+2)*16, 0xffff,
  228. mark=self.DAM, data=bytes(size))
  229. self.sectors.append(Sector(idam, dam))
  230. pos += 4 + size + 2 + self.gap_3
  231. def decode_raw(self, track):
  232. iams, sectors = self.iams, self.sectors
  233. self.iams, self.sectors = self.raw_iams, self.raw_sectors
  234. super().decode_raw(track)
  235. self.iams, self.sectors = iams, sectors
  236. for r in self.raw_sectors:
  237. if r.idam.crc != 0:
  238. continue
  239. for s in self.sectors:
  240. if (s.idam.c == r.idam.c and
  241. s.idam.h == r.idam.h and
  242. s.idam.r == r.idam.r and
  243. s.idam.n == r.idam.n):
  244. s.idam.crc = 0
  245. if r.dam.crc == 0 and s.dam.crc != 0:
  246. s.dam.crc = s.crc = 0
  247. s.dam.data = r.dam.data
  248. def set_img_track(self, tdat):
  249. pos = 0
  250. self.sectors.sort(key = lambda x: x.idam.r)
  251. for s in self.sectors:
  252. s.crc = s.idam.crc = s.dam.crc = 0
  253. size = 128 << s.idam.n
  254. s.dam.data = tdat[pos:pos+size]
  255. pos += size
  256. self.sectors.sort(key = lambda x: x.start)
  257. def get_img_track(self):
  258. tdat = bytearray()
  259. sectors = self.sectors.copy()
  260. sectors.sort(key = lambda x: x.idam.r)
  261. for s in sectors:
  262. tdat += s.dam.data
  263. return tdat
  264. class IBM_MFM_1M44(IBM_MFM_Formatted):
  265. gap_3 = 84 # Post-DAM
  266. nsec = 18
  267. id0 = 1
  268. sz = 2
  269. def mfm_encode(dat):
  270. y = 0
  271. out = bytearray()
  272. for x in dat:
  273. y = (y<<8) | x
  274. if (x & 0xaa) == 0:
  275. y |= ~((y>>1)|(y<<1)) & 0xaaaa
  276. y &= 255
  277. out.append(y)
  278. return bytes(out)
  279. encode_list = []
  280. for x in range(256):
  281. y = 0
  282. for i in range(8):
  283. y <<= 2
  284. y |= (x >> (7-i)) & 1
  285. encode_list.append(y)
  286. def encode(dat):
  287. out = bytearray()
  288. for x in dat:
  289. out += struct.pack('>H', encode_list[x])
  290. return bytes(out)
  291. decode_list = bytearray()
  292. for x in range(0x5555+1):
  293. y = 0
  294. for i in range(16):
  295. if x&(1<<(i*2)):
  296. y |= 1<<i
  297. decode_list.append(y)
  298. def decode(dat):
  299. out = bytearray()
  300. for x,y in zip(dat[::2], dat[1::2]):
  301. out.append(decode_list[((x<<8)|y)&0x5555])
  302. return bytes(out)
  303. def decode_track(cyl, head, track):
  304. mfm = IBM_MFM_1M44(cyl, head)
  305. mfm.decode_raw(track)
  306. return mfm
  307. # Local variables:
  308. # python-indent: 4
  309. # End: