fm.py 10 KB

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