fm.py 10 KB

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