|  | @@ -0,0 +1,342 @@
 | 
	
		
			
				|  |  | +# greaseweazle/codec/ibm/fm.py
 | 
	
		
			
				|  |  | +#
 | 
	
		
			
				|  |  | +# Written & released by Keir Fraser <keir.xen@gmail.com>
 | 
	
		
			
				|  |  | +#
 | 
	
		
			
				|  |  | +# This is free and unencumbered software released into the public domain.
 | 
	
		
			
				|  |  | +# See the file COPYING for more details, or visit <http://unlicense.org>.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import binascii
 | 
	
		
			
				|  |  | +import copy, heapq, struct, functools
 | 
	
		
			
				|  |  | +import itertools as it
 | 
	
		
			
				|  |  | +from bitarray import bitarray
 | 
	
		
			
				|  |  | +import crcmod.predefined
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +from greaseweazle.codec.ibm import mfm
 | 
	
		
			
				|  |  | +from greaseweazle.track import MasterTrack, RawTrack
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +default_revs = 2
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +def sync(dat, clk=0xc7):
 | 
	
		
			
				|  |  | +    x = 0
 | 
	
		
			
				|  |  | +    for i in range(8):
 | 
	
		
			
				|  |  | +        x <<= 1
 | 
	
		
			
				|  |  | +        x |= (clk >> (7-i)) & 1
 | 
	
		
			
				|  |  | +        x <<= 1
 | 
	
		
			
				|  |  | +        x |= (dat >> (7-i)) & 1
 | 
	
		
			
				|  |  | +    return bytes(struct.pack('>H', x))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +sync_prefix = bitarray(endian='big')
 | 
	
		
			
				|  |  | +sync_prefix.frombytes(b'\xaa\xaa' + sync(0xf8))
 | 
	
		
			
				|  |  | +sync_prefix = sync_prefix[:16+10]
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +iam_sync_bytes = sync(0xfc, 0xd7)
 | 
	
		
			
				|  |  | +iam_sync = bitarray(endian='big')
 | 
	
		
			
				|  |  | +iam_sync.frombytes(b'\xaa\xaa' + iam_sync_bytes)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +crc16 = crcmod.predefined.Crc('crc-ccitt-false')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +sec_sz = mfm.sec_sz
 | 
	
		
			
				|  |  | +IDAM   = mfm.IDAM
 | 
	
		
			
				|  |  | +DAM    = mfm.DAM
 | 
	
		
			
				|  |  | +Sector = mfm.Sector
 | 
	
		
			
				|  |  | +IAM    = mfm.IAM
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +class IBM_FM:
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    IAM  = 0xfc
 | 
	
		
			
				|  |  | +    IDAM = 0xfe
 | 
	
		
			
				|  |  | +    DAM  = 0xfb
 | 
	
		
			
				|  |  | +    DDAM = 0xf8
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    gap_presync = 6
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    gapbyte = 0xff
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __init__(self, cyl, head):
 | 
	
		
			
				|  |  | +        self.cyl, self.head = cyl, head
 | 
	
		
			
				|  |  | +        self.sectors = []
 | 
	
		
			
				|  |  | +        self.iams = []
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def summary_string(self):
 | 
	
		
			
				|  |  | +        nsec, nbad = len(self.sectors), self.nr_missing()
 | 
	
		
			
				|  |  | +        s = "IBM FM (%d/%d sectors)" % (nsec - nbad, nsec)
 | 
	
		
			
				|  |  | +        if nbad != 0:
 | 
	
		
			
				|  |  | +            s += " - %d sectors missing" % nbad
 | 
	
		
			
				|  |  | +        return s
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def has_sec(self, sec_id):
 | 
	
		
			
				|  |  | +        return self.sectors[sec_id].crc == 0
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def nr_missing(self):
 | 
	
		
			
				|  |  | +        return len(list(filter(lambda x: x.crc != 0, self.sectors)))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def flux_for_writeout(self, *args, **kwargs):
 | 
	
		
			
				|  |  | +        return self.raw_track().flux_for_writeout(args, kwargs)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def flux(self, *args, **kwargs):
 | 
	
		
			
				|  |  | +        return self.raw_track().flux(args, kwargs)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def decode_raw(self, track):
 | 
	
		
			
				|  |  | +        track.cue_at_index()
 | 
	
		
			
				|  |  | +        raw = RawTrack(clock = self.clock, data = track)
 | 
	
		
			
				|  |  | +        bits, _ = raw.get_all_data()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        areas = []
 | 
	
		
			
				|  |  | +        idam = None
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        ## 1. Calculate offsets within dump
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        for offs in bits.itersearch(iam_sync):
 | 
	
		
			
				|  |  | +            offs += 16
 | 
	
		
			
				|  |  | +            areas.append(IAM(offs, offs+1*16))
 | 
	
		
			
				|  |  | +            self.has_iam = True
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        for offs in bits.itersearch(sync_prefix):
 | 
	
		
			
				|  |  | +            offs += 16
 | 
	
		
			
				|  |  | +            mark = decode(bits[offs:offs+1*16].tobytes())[0]
 | 
	
		
			
				|  |  | +            clock = decode(bits[offs-1:offs+1*16-1].tobytes())[0]
 | 
	
		
			
				|  |  | +            if clock != 0xc7:
 | 
	
		
			
				|  |  | +                continue
 | 
	
		
			
				|  |  | +            if mark == IBM_FM.IDAM:
 | 
	
		
			
				|  |  | +                s, e = offs, offs+7*16
 | 
	
		
			
				|  |  | +                b = decode(bits[s:e].tobytes())
 | 
	
		
			
				|  |  | +                c,h,r,n = struct.unpack(">x4B2x", b)
 | 
	
		
			
				|  |  | +                crc = crc16.new(b).crcValue
 | 
	
		
			
				|  |  | +                if idam is not None:
 | 
	
		
			
				|  |  | +                    areas.append(idam)
 | 
	
		
			
				|  |  | +                idam = IDAM(s, e, crc, c=c, h=h, r=r, n=n)
 | 
	
		
			
				|  |  | +            elif mark == IBM_FM.DAM or mark == IBM_FM.DDAM:
 | 
	
		
			
				|  |  | +                if idam is None or idam.end - offs > 1000:
 | 
	
		
			
				|  |  | +                    areas.append(DAM(offs, offs+4*16, 0xffff, mark=mark))
 | 
	
		
			
				|  |  | +                else:
 | 
	
		
			
				|  |  | +                    sz = 128 << idam.n
 | 
	
		
			
				|  |  | +                    s, e = offs, offs+(1+sz+2)*16
 | 
	
		
			
				|  |  | +                    b = decode(bits[s:e].tobytes())
 | 
	
		
			
				|  |  | +                    crc = crc16.new(b).crcValue
 | 
	
		
			
				|  |  | +                    dam = DAM(s, e, crc, mark=mark, data=b[1:-2])
 | 
	
		
			
				|  |  | +                    areas.append(Sector(idam, dam))
 | 
	
		
			
				|  |  | +                idam = None
 | 
	
		
			
				|  |  | +            else:
 | 
	
		
			
				|  |  | +                pass #print("Unknown mark %02x" % mark)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if idam is not None:
 | 
	
		
			
				|  |  | +            areas.append(idam)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        # Convert to offsets within track
 | 
	
		
			
				|  |  | +        areas.sort(key=lambda x:x.start)
 | 
	
		
			
				|  |  | +        index = iter(raw.revolutions)
 | 
	
		
			
				|  |  | +        p, n = 0, next(index)
 | 
	
		
			
				|  |  | +        for a in areas:
 | 
	
		
			
				|  |  | +            if a.start >= n:
 | 
	
		
			
				|  |  | +                p = n
 | 
	
		
			
				|  |  | +                try:
 | 
	
		
			
				|  |  | +                    n = next(index)
 | 
	
		
			
				|  |  | +                except StopIteration:
 | 
	
		
			
				|  |  | +                    n = float('inf')
 | 
	
		
			
				|  |  | +            a.delta(p)
 | 
	
		
			
				|  |  | +        areas.sort(key=lambda x:x.start)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        # Add to the deduped lists
 | 
	
		
			
				|  |  | +        for a in areas:
 | 
	
		
			
				|  |  | +            match = False
 | 
	
		
			
				|  |  | +            if isinstance(a, IAM):
 | 
	
		
			
				|  |  | +                list = self.iams
 | 
	
		
			
				|  |  | +            elif isinstance(a, Sector):
 | 
	
		
			
				|  |  | +                list = self.sectors
 | 
	
		
			
				|  |  | +            else:
 | 
	
		
			
				|  |  | +                continue
 | 
	
		
			
				|  |  | +            for s in list:
 | 
	
		
			
				|  |  | +                if abs(s.start - a.start) < 1000:
 | 
	
		
			
				|  |  | +                    match = True
 | 
	
		
			
				|  |  | +                    break
 | 
	
		
			
				|  |  | +            if match and isinstance(a, Sector) and s.crc != 0 and a.crc == 0:
 | 
	
		
			
				|  |  | +                self.sectors = [x for x in self.sectors if x != a]
 | 
	
		
			
				|  |  | +                match = False
 | 
	
		
			
				|  |  | +            if not match:
 | 
	
		
			
				|  |  | +                list.append(a)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def raw_track(self):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        areas = heapq.merge(self.iams, self.sectors, key=lambda x:x.start)
 | 
	
		
			
				|  |  | +        t = bytes()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        for a in areas:
 | 
	
		
			
				|  |  | +            start = a.start//16 - self.gap_presync
 | 
	
		
			
				|  |  | +            gap = max(start - len(t)//2, 0)
 | 
	
		
			
				|  |  | +            t += encode(bytes([self.gapbyte] * gap))
 | 
	
		
			
				|  |  | +            t += encode(bytes(self.gap_presync))
 | 
	
		
			
				|  |  | +            if isinstance(a, IAM):
 | 
	
		
			
				|  |  | +                t += iam_sync_bytes
 | 
	
		
			
				|  |  | +            elif isinstance(a, Sector):
 | 
	
		
			
				|  |  | +                idam = bytes([self.IDAM,
 | 
	
		
			
				|  |  | +                              a.idam.c, a.idam.h, a.idam.r, a.idam.n])
 | 
	
		
			
				|  |  | +                idam += struct.pack('>H', crc16.new(idam).crcValue)
 | 
	
		
			
				|  |  | +                t += sync(idam[0]) + encode(idam[1:])
 | 
	
		
			
				|  |  | +                start = a.dam.start//16 - self.gap_presync
 | 
	
		
			
				|  |  | +                gap = max(start - len(t)//2, 0)
 | 
	
		
			
				|  |  | +                t += encode(bytes([self.gapbyte] * gap))
 | 
	
		
			
				|  |  | +                t += encode(bytes(self.gap_presync))
 | 
	
		
			
				|  |  | +                dam = bytes([a.dam.mark]) + a.dam.data
 | 
	
		
			
				|  |  | +                dam += struct.pack('>H', crc16.new(dam).crcValue)
 | 
	
		
			
				|  |  | +                t += sync(dam[0]) + encode(dam[1:])
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        # Add the pre-index gap.
 | 
	
		
			
				|  |  | +        tlen = int((self.time_per_rev / self.clock) // 16)
 | 
	
		
			
				|  |  | +        gap = max(tlen - len(t)//2, 0)
 | 
	
		
			
				|  |  | +        t += encode(bytes([self.gapbyte] * gap))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        track = MasterTrack(
 | 
	
		
			
				|  |  | +            bits = t,
 | 
	
		
			
				|  |  | +            time_per_rev = self.time_per_rev)
 | 
	
		
			
				|  |  | +        track.verify = self
 | 
	
		
			
				|  |  | +        track.verify_revs = default_revs
 | 
	
		
			
				|  |  | +        return track
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class IBM_FM_Formatted(IBM_FM):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    gap_4a = 40 # Post-Index
 | 
	
		
			
				|  |  | +    gap_1  = 26 # Post-IAM
 | 
	
		
			
				|  |  | +    gap_2  = 11 # Post-IDAM
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __init__(self, cyl, head):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        super().__init__(cyl, head)
 | 
	
		
			
				|  |  | +        self.raw_iams, self.raw_sectors = [], []
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def decode_raw(self, track):
 | 
	
		
			
				|  |  | +        iams, sectors = self.iams, self.sectors
 | 
	
		
			
				|  |  | +        self.iams, self.sectors = self.raw_iams, self.raw_sectors
 | 
	
		
			
				|  |  | +        super().decode_raw(track)
 | 
	
		
			
				|  |  | +        self.iams, self.sectors = iams, sectors
 | 
	
		
			
				|  |  | +        for r in self.raw_sectors:
 | 
	
		
			
				|  |  | +            if r.idam.crc != 0:
 | 
	
		
			
				|  |  | +                continue
 | 
	
		
			
				|  |  | +            for s in self.sectors:
 | 
	
		
			
				|  |  | +                if (s.idam.c == r.idam.c and
 | 
	
		
			
				|  |  | +                    s.idam.h == r.idam.h and
 | 
	
		
			
				|  |  | +                    s.idam.r == r.idam.r and
 | 
	
		
			
				|  |  | +                    s.idam.n == r.idam.n):
 | 
	
		
			
				|  |  | +                    s.idam.crc = 0
 | 
	
		
			
				|  |  | +                    if r.dam.crc == 0 and s.dam.crc != 0:
 | 
	
		
			
				|  |  | +                        s.dam.crc = s.crc = 0
 | 
	
		
			
				|  |  | +                        s.dam.data = r.dam.data
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def set_img_track(self, tdat):
 | 
	
		
			
				|  |  | +        pos = 0
 | 
	
		
			
				|  |  | +        self.sectors.sort(key = lambda x: x.idam.r)
 | 
	
		
			
				|  |  | +        totsize = functools.reduce(lambda x, y: x + (128<<y.idam.n),
 | 
	
		
			
				|  |  | +                                   self.sectors, 0)
 | 
	
		
			
				|  |  | +        if len(tdat) < totsize:
 | 
	
		
			
				|  |  | +            tdat += bytes(totsize - len(tdat))
 | 
	
		
			
				|  |  | +        for s in self.sectors:
 | 
	
		
			
				|  |  | +            s.crc = s.idam.crc = s.dam.crc = 0
 | 
	
		
			
				|  |  | +            size = 128 << s.idam.n
 | 
	
		
			
				|  |  | +            s.dam.data = tdat[pos:pos+size]
 | 
	
		
			
				|  |  | +            pos += size
 | 
	
		
			
				|  |  | +        self.sectors.sort(key = lambda x: x.start)
 | 
	
		
			
				|  |  | +        return totsize
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def get_img_track(self):
 | 
	
		
			
				|  |  | +        tdat = bytearray()
 | 
	
		
			
				|  |  | +        sectors = self.sectors.copy()
 | 
	
		
			
				|  |  | +        sectors.sort(key = lambda x: x.idam.r)
 | 
	
		
			
				|  |  | +        for s in sectors:
 | 
	
		
			
				|  |  | +            tdat += s.dam.data
 | 
	
		
			
				|  |  | +        return tdat
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +    def verify_track(self, flux):
 | 
	
		
			
				|  |  | +        readback_track = IBM_FM_Formatted(self.cyl, self.head)
 | 
	
		
			
				|  |  | +        readback_track.clock = self.clock
 | 
	
		
			
				|  |  | +        readback_track.time_per_rev = self.time_per_rev
 | 
	
		
			
				|  |  | +        for x in self.iams:
 | 
	
		
			
				|  |  | +            readback_track.iams.append(copy.copy(x))
 | 
	
		
			
				|  |  | +        for x in self.sectors:
 | 
	
		
			
				|  |  | +            idam, dam = copy.copy(x.idam), copy.copy(x.dam)
 | 
	
		
			
				|  |  | +            idam.crc, dam.crc = 0xffff, 0xffff
 | 
	
		
			
				|  |  | +            readback_track.sectors.append(Sector(idam, dam))
 | 
	
		
			
				|  |  | +        readback_track.decode_raw(flux)
 | 
	
		
			
				|  |  | +        if readback_track.nr_missing() != 0:
 | 
	
		
			
				|  |  | +            return False
 | 
	
		
			
				|  |  | +        return self.sectors == readback_track.sectors
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class IBM_FM_Predefined(IBM_FM_Formatted):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    cskew = 0
 | 
	
		
			
				|  |  | +    hskew = 0
 | 
	
		
			
				|  |  | +    interleave = 1
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    def __init__(self, cyl, head):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        super().__init__(cyl, head)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        # Create logical sector map in rotational order
 | 
	
		
			
				|  |  | +        sec_map = [-1] * self.nsec
 | 
	
		
			
				|  |  | +        pos = (cyl*self.cskew + head*self.hskew) % self.nsec
 | 
	
		
			
				|  |  | +        for i in range(self.nsec):
 | 
	
		
			
				|  |  | +            while sec_map[pos] != -1:
 | 
	
		
			
				|  |  | +                pos = (pos + 1) % self.nsec
 | 
	
		
			
				|  |  | +            sec_map[pos] = i
 | 
	
		
			
				|  |  | +            pos = (pos + self.interleave) % self.nsec
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        pos = self.gap_4a
 | 
	
		
			
				|  |  | +        if self.gap_1 is not None:
 | 
	
		
			
				|  |  | +            self.iams = [IAM(pos*16,(pos+1)*16)]
 | 
	
		
			
				|  |  | +            pos += 1 + self.gap_1
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        for i in range(self.nsec):
 | 
	
		
			
				|  |  | +            pos += self.gap_presync
 | 
	
		
			
				|  |  | +            idam = IDAM(pos*16, (pos+7)*16, 0xffff,
 | 
	
		
			
				|  |  | +                        c=cyl, h=head, r=self.id0+sec_map[i], n = self.sz)
 | 
	
		
			
				|  |  | +            pos += 7 + self.gap_2 + self.gap_presync
 | 
	
		
			
				|  |  | +            size = 128 << self.sz
 | 
	
		
			
				|  |  | +            dam = DAM(pos*16, (pos+1+size+2)*16, 0xffff,
 | 
	
		
			
				|  |  | +                      mark=self.DAM, data=bytes(size))
 | 
	
		
			
				|  |  | +            self.sectors.append(Sector(idam, dam))
 | 
	
		
			
				|  |  | +            pos += 1 + size + 2 + self.gap_3
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @classmethod
 | 
	
		
			
				|  |  | +    def decode_track(cls, cyl, head, track):
 | 
	
		
			
				|  |  | +        mfm = cls(cyl, head)
 | 
	
		
			
				|  |  | +        mfm.decode_raw(track)
 | 
	
		
			
				|  |  | +        return mfm
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class Acorn_DFS(IBM_FM_Predefined):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    time_per_rev = 0.2
 | 
	
		
			
				|  |  | +    clock = 4e-6
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    gap_1  = 0 # No IAM
 | 
	
		
			
				|  |  | +    gap_3  = 21
 | 
	
		
			
				|  |  | +    nsec   = 10
 | 
	
		
			
				|  |  | +    id0    = 0
 | 
	
		
			
				|  |  | +    sz     = 1
 | 
	
		
			
				|  |  | +    cskew  = 3
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +encode_list = []
 | 
	
		
			
				|  |  | +for x in range(256):
 | 
	
		
			
				|  |  | +    y = 0
 | 
	
		
			
				|  |  | +    for i in range(8):
 | 
	
		
			
				|  |  | +        y <<= 1
 | 
	
		
			
				|  |  | +        y |= 1
 | 
	
		
			
				|  |  | +        y <<= 1
 | 
	
		
			
				|  |  | +        y |= (x >> (7-i)) & 1
 | 
	
		
			
				|  |  | +    encode_list.append(y)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +def encode(dat):
 | 
	
		
			
				|  |  | +    out = bytearray()
 | 
	
		
			
				|  |  | +    for x in dat:
 | 
	
		
			
				|  |  | +        out += struct.pack('>H', encode_list[x])
 | 
	
		
			
				|  |  | +    return bytes(out)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +decode = mfm.decode
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +# Local variables:
 | 
	
		
			
				|  |  | +# python-indent: 4
 | 
	
		
			
				|  |  | +# End:
 |