def from_file(cls, dat): (sig, f_rev, nr_cyls, nr_sides, t_enc, bitrate, _, _, _, tlut_base) = struct.unpack("<8s4B2H2BH", dat[:20]) error.check(sig != b"HXCHFEV3", "HFEv3 is not supported") error.check(sig == b"HXCPICFE" and f_rev <= 1, "Not a valid HFE file") error.check(0 < nr_cyls, "HFE: Invalid #cyls") error.check(0 < nr_sides < 3, "HFE: Invalid #sides") error.check(bitrate != 0, "HFE: Invalid bitrate") hfe = cls(0, nr_sides) hfe.bitrate = bitrate tlut = dat[tlut_base * 512:tlut_base * 512 + nr_cyls * 4] for cyl in range(nr_cyls): for side in range(nr_sides): offset, length = struct.unpack("<2H", tlut[cyl * 4:(cyl + 1) * 4]) todo = length // 2 tdat = bytes() while todo: d_off = offset * 512 + side * 256 d_nr = 256 if todo > 256 else todo tdat += dat[d_off:d_off + d_nr] todo -= d_nr offset += 1 hfe.track_list.append((len(tdat) * 8, tdat)) return hfe
def from_file(cls, name): with open(name, "rb") as f: dat = f.read() (sig, f_rev, n_cyl, n_side, t_enc, bitrate, _, _, _, tlut_base) = struct.unpack("<8s4B2H2BH", dat[:20]) error.check(sig != b"HXCHFEV3", "HFEv3 is not supported") error.check(sig == b"HXCPICFE" and f_rev <= 1, "Not a valid HFE file") error.check(0 < n_cyl, "HFE: Invalid #cyls") error.check(0 < n_side < 3, "HFE: Invalid #sides") error.check(bitrate != 0, "HFE: Invalid bitrate") hfe = cls() hfe.bitrate = bitrate tlut = dat[tlut_base * 512:tlut_base * 512 + n_cyl * 4] for cyl in range(n_cyl): for side in range(n_side): offset, length = struct.unpack("<2H", tlut[cyl * 4:(cyl + 1) * 4]) todo = length // 2 tdat = bytes() while todo: d_off = offset * 512 + side * 256 d_nr = 256 if todo > 256 else todo tdat += dat[d_off:d_off + d_nr] todo -= d_nr offset += 1 hfe.to_track[cyl, side] = (len(tdat) * 8, tdat) return hfe
def _send_cmd(self, cmd): self.ser.write(cmd) (c,r) = struct.unpack("2B", self.ser.read(2)) error.check(c == cmd[0], "Command returned garbage (%02x != %02x)" % (c, cmd[0])) if r != 0: raise CmdError(c, r)
def flux_for_writeout(self): error.check(self.splice == 0 or len(self.index_list) > 1, "Cannot write single-revolution unaligned raw flux") splice_at_index = (self.splice == 0) # Copy the required amount of flux to a fresh list. flux_list = [] to_index = self.index_list[0] remain = to_index + self.splice for f in self.list: if f > remain: break flux_list.append(f) remain -= f if splice_at_index: # Extend with "safe" 4us sample values, to avoid unformatted area # at end of track if drive motor is a little slow. four_us = max(self.sample_freq * 4e-6, 1) if remain > four_us: flux_list.append(remain) for i in range(round(to_index / (10 * four_us))): flux_list.append(four_us) elif remain > 0: # End the write exactly where specified. flux_list.append(remain) return WriteoutFlux(to_index, flux_list, self.sample_freq, index_cued=True, terminate_at_index=(self.splice == 0))
def get_track(self, cyl, head, writeout=False): pi = self.pi if head < pi.minhead or head > pi.maxhead: return None if cyl < pi.mincylinder or cyl > pi.maxcylinder: return None ti = CapsTrackInfoT2(2) res = self.lib.CAPSLockTrack(ct.byref(ti), self.iid, cyl, head, DI_LOCK.def_flags) error.check(res == 0, "Could not lock IPF track %d.%d" % (cyl, head)) if not ti.trackbuf: return None # unformatted/empty carray_type = ct.c_ubyte * ((ti.tracklen + 7) // 8) carray = carray_type.from_address(ct.addressof(ti.trackbuf.contents)) trackbuf = bitarray(endian='big') trackbuf.frombytes(bytes(carray)) trackbuf = trackbuf[:ti.tracklen] #for i in range(ti.sectorcnt): # si = CapsSectorInfo() # res = self.lib.CAPSGetInfo(ct.byref(si), self.iid, # cyl, head, 1, i) # error.check(res == 0, "Couldn't get sector info") # range.append((si.datastart, si.datasize)) weak = [] for i in range(ti.weakcnt): wi = CapsDataInfo() res = self.lib.CAPSGetInfo(ct.byref(wi), self.iid, cyl, head, 2, i) error.check(res == 0, "Couldn't get weak data info") weak.append((wi.start, wi.size)) timebuf = None if ti.timebuf: carray_type = ct.c_uint * ti.timelen carray = carray_type.from_address(ct.addressof( ti.timebuf.contents)) # Unpack the per-byte timing info into per-bitcell timebuf = [] for i in carray: for j in range(8): timebuf.append(i) # Pad the timing info with normal cell lengths as necessary for j in range(len(carray) * 8, ti.tracklen): timebuf.append(1000) # Clip the timing info, if necessary. timebuf = timebuf[:ti.tracklen] # We don't really have access to the bitrate. It depends on RPM. # So we assume a rotation rate of 300 RPM (5 rev/sec). rpm = 300 track = MasterTrack(bits=trackbuf, time_per_rev=60 / rpm, bit_ticks=timebuf, splice=ti.overlap, weak=weak) return track
def __init__(self, name): m = re.search("\d{2}.[01].raw$", name, flags=re.IGNORECASE) error.check( m is not None, '''\ Bad Kryoflux image name pattern '%s' Name pattern must be path/to/nameNN.N.raw (N is a digit)''' % name) self.basename = name[:m.start()]
def from_file(cls, name, fmt): if fmt is not None and fmt.img_compatible: # Acorn ADF return IMG.from_file(name, fmt) with open(name, "rb") as f: dat = f.read() adf = cls(name, fmt) while True: nsec = fmt.fmt.nsec error.check((len(dat) % (2 * nsec * 512)) == 0, "Bad ADF image length") ncyl = len(dat) // (2 * nsec * 512) if ncyl < 90: break error.check(nsec == 11, "Bad ADF image length") fmt = adf.fmt = formats.Format_Amiga_AmigaDOS_HD() pos = 0 for t in fmt.max_tracks: tnr = t.cyl * 2 + t.head ados = fmt.fmt(t.cyl, t.head) pos += ados.set_adf_track(dat[pos:]) adf.to_track[tnr] = ados return adf
def open_image(args): image_class = util.get_image_class(args.file) error.check(hasattr(image_class, 'to_file'), "%s: Cannot create %s image files" % (args.file, image_class.__name__)) image = image_class.to_file(args.scyl, args.nr_sides) return image
def get_image(self): # Work out the single-sided byte code s = self.side_count() if s[0] and s[1]: single_sided = 0 elif s[0]: single_sided = 1 else: single_sided = 2 to_track = self.to_track if single_sided and self.opts.legacy_ss: print('SCP: Generated legacy single-sided image') to_track = dict() for tnr in self.to_track: to_track[tnr//2] = self.to_track[tnr] ntracks = max(to_track, default=0) + 1 # Generate the TLUT and concatenate all the tracks together. trk_offs = bytearray() trk_dat = bytearray() for tnr in range(ntracks): if tnr in to_track: track = to_track[tnr] trk_offs += struct.pack("<I", 0x2b0 + len(trk_dat)) trk_dat += struct.pack("<3sB", b"TRK", tnr) trk_dat += track.tdh + track.dat else: trk_offs += struct.pack("<I", 0) error.check(len(trk_offs) <= 0x2a0, "SCP: Too many tracks") trk_offs += bytes(0x2a0 - len(trk_offs)) # Calculate checksum over all data (except 16-byte image header). csum = 0 for x in trk_offs: csum += x for x in trk_dat: csum += x # Generate the image header. flags = 2 # 96TPI if self.index_cued: flags |= 1 # Index-Cued nr_revs = self.nr_revs if self.nr_revs is not None else 0 header = struct.pack("<3s9BI", b"SCP", # Signature 0, # Version self.opts.disktype, nr_revs, 0, ntracks-1, flags, 0, # 16-bit cell width single_sided, 0, # 25ns capture csum & 0xffffffff) # Concatenate it all together and send it back. return header + trk_offs + trk_dat
def to_file(cls, name, fmt, noclobber): error.check(not cls.read_only, "%s: Cannot create %s image files" % (name, cls.__name__)) obj = cls() obj.filename = name obj.fmt = fmt obj.noclobber = noclobber return obj
def open_output_image(args, image_class): image = image_class.to_file(args.out_file, args.fmt_cls, args.no_clobber) for opt, val in args.out_file_opts.items(): error.check( hasattr(image, 'opts') and hasattr(image.opts, opt), "%s: Invalid file option: %s" % (args.out_file, opt)) setattr(image.opts, opt, val) return image
def append(self, flux): error.check(self.sample_freq == flux.sample_freq, "Cannot append flux with different sample frequency") # Any trailing flux is incorporated into the first revolution of # the appended flux. rev0 = flux.index_list[0] + sum(self.list) - sum(self.index_list) self.index_list += [rev0] + flux.index_list[1:] self.list += flux.list
def usb_open(devicename, is_update=False, mode_check=True): def print_update_instructions(usb): print("To perform an Update:") if not usb.jumperless_update: print(" - Disconnect from USB") print(" - Install the Update Jumper at pins %s" % ("RXI-TXO" if usb.hw_model != 1 else "DCLK-GND")) print(" - Reconnect to USB") print(" - Run \"gw update\" to install firmware v%u.%u" % (version.major, version.minor)) if devicename is None: devicename = find_port() usb = USB.Unit(serial.Serial(devicename)) usb.port_info = port_info(devicename) is_win7 = (platform.system() == 'Windows' and platform.release() == '7') usb.jumperless_update = ((usb.hw_model, usb.hw_submodel) != (1, 0) and not is_win7) if not mode_check: return usb if usb.update_mode and not is_update: if usb.jumperless_update and not usb.update_jumpered: usb = usb_reopen(usb, is_update) if not usb.update_mode: return usb print("ERROR: Greaseweazle is in Firmware Update Mode") print(" - The only available action is \"gw update\"") if usb.update_jumpered: print(" - For normal operation disconnect from USB and remove " "the Update Jumper at pins %s" % ("RXI-TXO" if usb.hw_model != 1 else "DCLK-GND")) else: print(" - Main firmware is erased: You *must* perform an update!") sys.exit(1) if is_update and not usb.update_mode: if usb.jumperless_update: usb = usb_reopen(usb, is_update) error.check( usb.update_mode, """\ Greaseweazle F7 did not change to Firmware Update Mode as requested. If the problem persists, install the Update Jumper at pins RXI-TXO.""") return usb print("ERROR: Greaseweazle is not in Firmware Update Mode") print_update_instructions(usb) sys.exit(1) if not usb.update_mode and usb.update_needed: print("ERROR: Greaseweazle firmware v%u.%u is unsupported" % (usb.major, usb.minor)) print_update_instructions(usb) sys.exit(1) return usb
def __init__(self, name, fmt): self.to_track = dict() error.check( fmt is not None and fmt.img_compatible, """\ Sector image requires compatible format conversion Compatible formats:\n%s""" % formats.print_formats(lambda k, v: v.img_compatible)) self.filename = name self.fmt = fmt
def open_image(args, image_class): image = image_class.to_file(args.file) if args.rate is not None: image.bitrate = args.rate for opt, val in args.file_opts.items(): error.check( hasattr(image, 'opts') and hasattr(image.opts, opt), "%s: Invalid file option: %s" % (args.file, opt)) setattr(image.opts, opt, val) return image
def main(argv): parser = util.ArgumentParser(allow_abbrev=False, usage='%(prog)s [options] [file]') parser.add_argument("file", nargs="?", help="update filename") parser.add_argument("--device", help="device name (COM/serial port)") parser.add_argument("--force", action="store_true", help="force update even if firmware is older") parser.add_argument("--bootloader", action="store_true", help="update the bootloader (use with caution!)") parser.description = description parser.prog += ' ' + argv[1] args = parser.parse_args(argv[2:]) if args.file is None: args.file, dat = download_latest() else: with open(args.file, "rb") as f: dat = f.read() try: usb = util.usb_open(args.device, mode_check=False) dat_version, dat = extract_update(usb, dat, args) print("Updating %s to v%u.%u..." % ("Bootloader" if args.bootloader else "Main Firmware", *dat_version)) if not args.force and (usb.can_mode_switch or args.bootloader == usb.update_mode): if args.bootloader != usb.update_mode: usb = util.usb_reopen(usb, is_update=args.bootloader) error.check(args.bootloader == usb.update_mode, 'Device did not mode switch as requested') if usb.version >= dat_version: if usb.update_mode and usb.can_mode_switch: usb = util.usb_reopen(usb, is_update=False) raise error.Fatal('Device is running v%d.%d (>= v%d.%d). ' 'Use --force to update anyway.' % (usb.version + dat_version)) usb = util.usb_mode_check(usb, is_update=not args.bootloader) update_firmware(usb, dat, args) if usb.update_mode and usb.can_mode_switch: util.usb_reopen(usb, is_update=False) except USB.CmdError as err: if err.code == USB.Ack.OutOfSRAM and args.bootloader: # Special warning for Low-Density F1 devices. The new bootloader # cannot be fully buffered in the limited RAM available. print("ERROR: Bootloader update unsupported on this device " "(insufficient SRAM)") elif err.code == USB.Ack.OutOfFlash and not args.bootloader: print("ERROR: New firmware is too large for this device " "(insufficient Flash memory)") else: print("Command Failed: %s" % err)
def get_image_class(name): _, ext = os.path.splitext(name) error.check( ext.lower() in image_types, """\ %s: Unrecognised file suffix '%s' Known suffixes: %s""" % (name, ext, ', '.join(image_types))) typespec = image_types[ext.lower()] if isinstance(typespec, tuple): typename, classname = typespec else: typename, classname = typespec, typespec.lower() mod = importlib.import_module('greaseweazle.image.' + classname) return mod.__dict__[typename]
def open_image(args, image_class): error.check( hasattr(image_class, 'to_file'), "%s: Cannot create %s image files" % (args.file, image_class.__name__)) image = image_class.to_file(args.scyl, args.nr_sides) if args.rate is not None: image.bitrate = args.rate for opt, val in args.file_opts.items(): error.check( hasattr(image, 'opts') and hasattr(image.opts, opt), "%s: Invalid file option: %s" % (args.file, opt)) setattr(image.opts, opt, val) return image
def usb_open(devicename, is_update=False, mode_check=True): if devicename is None: devicename = find_port() usb = USB.Unit(serial.Serial(devicename)) usb.port_info = port_info(devicename) if not mode_check: return usb print("** %s v%u.%u [F%u], Host Tools v%u.%u" % (("Greaseweazle", "Bootloader")[usb.update_mode], usb.major, usb.minor, usb.hw_model, version.major, version.minor)) if usb.update_mode and not is_update: if usb.hw_model == 7 and not usb.update_jumpered: usb = usb_reopen(usb, is_update) if not usb.update_mode: return usb print("Greaseweazle is in Firmware Update Mode:") print(" The only available action is \"update\" of main firmware") if usb.update_jumpered: print(" Remove the Update Jumper for normal operation") else: print(" Main firmware is erased: You *must* perform an update!") sys.exit(1) if is_update and not usb.update_mode: if usb.hw_model == 7: usb = usb_reopen(usb, is_update) error.check( usb.update_mode, """\ Greaseweazle F7 did not change to Firmware Update Mode as requested. If the problem persists, install the Update Jumper (across RX/TX).""") return usb print("Greaseweazle is in Normal Mode:") print(" To \"update\" you must install the Update Jumper") sys.exit(1) if not usb.update_mode and usb.update_needed: print("Firmware is out of date: Require v%u.%u" % (version.major, version.minor)) if usb.hw_model == 7: print("Run \"update <update_file>\"") else: print("Install the Update Jumper and \"update <update_file>\"") sys.exit(1) return usb
def emit_track(self, cyl, side, track): if self.opts.bitrate is None: t = track.raw_track() if hasattr(track, 'raw_track') else track b = getattr(t, 'bitrate', None) error.check( hasattr(t, 'bitrate'), 'HFE: Requires bitrate to be specified' ' (eg. filename.hfe::bitrate=500)') self.opts.bitrate = round(t.bitrate / 2e3) print('HFE: Data bitrate detected: %d kbit/s' % self.opts.bitrate) flux = track.flux() flux.cue_at_index() raw = RawTrack(clock=5e-4 / self.opts.bitrate, data=flux) bits, _ = raw.get_revolution(0) bits.bytereverse() self.to_track[cyl, side] = (len(bits), bits.tobytes())
def get_image_class(name): image_types = { '.adf': 'ADF', '.scp': 'SCP', '.hfe': 'HFE', '.ipf': 'IPF', '.raw': 'KryoFlux' } if os.path.isdir(name): typename = 'KryoFlux' else: _, ext = os.path.splitext(name) error.check(ext.lower() in image_types, "%s: Unrecognised file suffix '%s'" % (name, ext)) typename = image_types[ext.lower()] mod = importlib.import_module('greaseweazle.image.' + typename.lower()) return mod.__dict__[typename]
def flux_for_writeout(self, cue_at_index=True): error.check(self.index_cued, "Cannot write non-index-cued raw flux") error.check(self.splice == 0 or len(self.index_list) > 1, "Cannot write single-revolution unaligned raw flux") splice_at_index = (self.splice == 0) # Copy the required amount of flux to a fresh list. flux_list = [] to_index = self.index_list[0] remain = to_index + self.splice for f in self.list: if f > remain: break flux_list.append(f) remain -= f if not cue_at_index: # We will write more than one revolutionm and terminate the # second revolution at the splice. Extend the start of the write # with "safe" 4us sample values, in case the drive motor is a # little fast. if remain > 0: flux_list.append(remain) prepend = max(round(to_index/10 - self.splice), 0) if prepend != 0: four_us = max(self.sample_freq * 4e-6, 1) flux_list = [four_us]*round(prepend/four_us) + flux_list splice_at_index = False elif splice_at_index: # Extend with "safe" 4us sample values, to avoid unformatted area # at end of track if drive motor is a little slow. four_us = max(self.sample_freq * 4e-6, 1) if remain > four_us: flux_list.append(remain) for i in range(round(to_index/(10*four_us))): flux_list.append(four_us) elif remain > 0: # End the write exactly where specified. flux_list.append(remain) return WriteoutFlux(to_index, flux_list, self.sample_freq, index_cued = cue_at_index, terminate_at_index = splice_at_index)
def open_libcaps(): # Get the OS-dependent list of valid CAPS library names. _names = [] if platform.system() == "Linux": _names = [ "libcapsimage.so.5", "libcapsimage.so.5.1", "libcapsimage.so.4", "libcapsimage.so.4.2", "libcapsimage.so" ] elif platform.system() == "Darwin": _names = ["CAPSImage.framework/CAPSImage"] elif platform.system() == "Windows": _names = ["CAPSImg_x64.dll", "CAPSImg.dll"] # Get the absolute path to the root Greaseweazle folder. path = os.path.dirname(os.path.abspath(__file__)) for _ in range(3): path = os.path.join(path, os.pardir) path = os.path.normpath(path) # Create a search list of both relative and absolute library names. names = [] for name in _names: names.append(name) names.append(os.path.join(path, name)) # Walk the search list, trying to open the CAPS library. for name in names: try: lib = ct.cdll.LoadLibrary(name) break except: pass error.check( "lib" in locals(), """\ Could not find SPS/CAPS IPF decode library For installation instructions please read the wiki: <https://github.com/keirf/Greaseweazle/wiki/IPF-Images>""") # We have opened the library. Now initialise it. res = lib.CAPSInit() error.check(res == 0, "Failure initialising IPF library '%s'" % name) return lib
def from_file(cls, dat): adf = cls(0, 2) nsec = adf.sec_per_track error.check((len(dat) % (2*nsec*512)) == 0, "Bad ADF image") ncyl = len(dat) // (2*nsec*512) if ncyl > 90: ncyl //= 2 nsec *= 2 adf.bitrate *= 2 adf.sec_per_track = nsec for i in range(ncyl*2): ados = amigados.AmigaDOS(tracknr=i, nsec=nsec) ados.set_adf_track(dat[i*nsec*512:(i+1)*nsec*512]) adf.track_list.append(ados) return adf
def extract_update(usb, dat, args): req_type = b'BL' if args.bootloader else b'GW' filename = args.file # Verify the update catalogue. error.check( struct.unpack('4s', dat[:4])[0] == b'GWUP', '%s: Not a valid UPD file' % (filename)) crc32 = crcmod.predefined.Crc('crc-32-mpeg') crc32.update(dat) error.check(crc32.crcValue == 0, '%s: UPD file is corrupt' % (filename)) dat = dat[4:-4] # Search the catalogue for a match on our Weazle's hardware type. while dat: upd_len, hw_model = struct.unpack("<2H", dat[:4]) upd_type, major, minor = struct.unpack("2s2B", dat[upd_len - 4:upd_len]) if ((hw_model, upd_type) == (usb.hw_model, req_type)): # Match: Pull out the embedded update file. dat = dat[4:upd_len + 4] break # Skip to the next catalogue entry. dat = dat[upd_len + 4:] error.check( dat, '%s: F%u %s update not found' % (filename, usb.hw_model, 'bootloader' if args.bootloader else 'firmware')) # Check the matching update file's footer. sig, major, minor, hw_model = struct.unpack("<2s2BH", dat[-8:-2]) error.check( len(dat) & 3 == 0 and sig == req_type and hw_model == usb.hw_model, '%s: Bad update file' % (filename)) crc16 = crcmod.predefined.Crc('crc-ccitt-false') crc16.update(dat) error.check(crc16.crcValue == 0, '%s: Bad CRC' % (filename)) return (major, minor), dat
def flux_for_writeout(self): error.check(self.splice == 0, "Cannot write non-index-aligned raw flux") # Copy the first revolution only to a fresh flux list. flux_list = [] to_index = remain = self.index_list[0] for f in self.list: if f > remain: break flux_list.append(f) remain -= f # Extend with "safe" 4us sample values, to avoid unformatted area # at end of track if drive motor is a little slow. four_us = max(self.sample_freq * 4e-6, 1) if remain > four_us: flux_list.append(remain) for i in range(round(to_index/(10*four_us))): flux_list.append(four_us) return WriteoutFlux(to_index, flux_list, self.sample_freq, terminate_at_index = True)
def from_file(cls, name): with open(name, "rb") as f: dat = f.read() adf = cls() nsec = adf.sec_per_track error.check((len(dat) % (2 * nsec * 512)) == 0, "Bad ADF image") ncyl = len(dat) // (2 * nsec * 512) if ncyl > 90: ncyl //= 2 nsec *= 2 adf.sec_per_track = nsec for tnr in range(ncyl * 2): ados = amigados.AmigaDOS(tracknr=tnr, nsec=nsec) ados.set_adf_track(dat[tnr * nsec * 512:(tnr + 1) * nsec * 512]) adf.to_track[tnr] = ados return adf
def from_file(cls, dat): header = struct.unpack("<3s9BI", dat[0:16]) (sig, _, _, nr_revs, s_trk, e_trk, flags, _, ss, _, _) = header error.check(sig == b"SCP", "SCP: Bad signature") nr_sides = 1 if ss else 2 trk_offs = struct.unpack("<168I", dat[16:0x2b0]) scp = cls(s_trk // nr_sides, nr_sides) scp.nr_revs = nr_revs for trknr in range(s_trk, e_trk + 1): trk_off = trk_offs[trknr] if trk_off == 0: scp.track_list.append((None, None)) # Parse the SCP track header and extract the flux data. thdr = dat[trk_off:trk_off + 4 + 12 * nr_revs] sig, tnr, _, _, s_off = struct.unpack("<3sB3I", thdr[:16]) error.check(sig == b"TRK", "SCP: Missing track signature") error.check(tnr == trknr, "SCP: Wrong track number in header") _, e_nr, e_off = struct.unpack("<3I", thdr[-12:]) tdat = dat[trk_off + s_off:trk_off + e_off + e_nr * 2] scp.track_list.append((thdr, tdat)) return scp
def _decode_flux(self, dat): flux = [] dat_i = iter(dat) try: while True: i = next(dat_i) if i < 250: flux.append(i) elif i == 255: val = (next(dat_i) & 254) >> 1 val += (next(dat_i) & 254) << 6 val += (next(dat_i) & 254) << 13 val += (next(dat_i) & 254) << 20 flux.append(val) else: val = (i - 249) * 250 val += next(dat_i) - 1 flux.append(val) except StopIteration: pass error.check(flux[-1] == 0, "Missing terminator on flux read stream") return flux[:-1]
def seek(self, cyl, head): self._send_cmd(struct.pack("2Bb", Cmd.Seek, 3, cyl)) trk0 = not self.get_pin(26) if cyl == 0 and not trk0: # This can happen with Kryoflux flippy-modded Panasonic drives # which may not assert the /TRK0 signal when stepping *inward* # from cylinder -1. We can check this by attempting a fake outward # step, which is exactly NoClickStep's purpose. try: info = self.get_current_drive_info() if info.is_flippy: self._send_cmd(struct.pack("2B", Cmd.NoClickStep, 2)) except CmdError: # GetInfo.CurrentDrive is unsupported by older firmwares. # NoClickStep is "best effort". We're on a likely error # path anyway, so let them fail silently. pass trk0 = not self.get_pin(26) # now re-sample /TRK0 error.check( cyl < 0 or (cyl == 0) == trk0, "Track0 signal %s after seek to cylinder %d" % (('absent', 'asserted')[trk0], cyl)) self._send_cmd(struct.pack("3B", Cmd.Head, 3, head))