cli tool: usb connection
This commit is contained in:
parent
1797dc452e
commit
84d04e6d7e
|
@ -0,0 +1,194 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import glob, shutil
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from typing import *
|
||||
|
||||
import dmctl.connection as devconn
|
||||
import dmctl.commands as devcmds
|
||||
|
||||
|
||||
def dmctl_do(args: Any) -> int:
|
||||
def get_device_info(conn, args): return devcmds.get_device_info(conn)
|
||||
def get_mode_info(conn, args): return devcmds.get_mode_info(conn, args.mode)
|
||||
def set_mode(conn, args): return devcmds.set_mode(conn, args.mode)
|
||||
|
||||
def uart_hw_flowctl(conn, args):
|
||||
if args.get: return devcmds.uart_hw_flowctl_get(conn)
|
||||
fcen = args.set
|
||||
if fcen is None:
|
||||
if args.enable: fcen = True
|
||||
elif args.disable: fcen = False
|
||||
if fcen is None:
|
||||
print("Error: none of '--get', '--set', '--enable' or '--disable' specified.")
|
||||
return 1
|
||||
return devcmds.uart_hw_flowctl_set(conn, fcen)
|
||||
def tempsensor(conn, args):
|
||||
if args.get: return devcmds.tempsensor_get(conn)
|
||||
tsen = args.set
|
||||
if tsen is None:
|
||||
if args.disable: tsen = 0
|
||||
if tsen is None:
|
||||
print("Error: none of '--get', '--set' or '--disable' specified.")
|
||||
return 1
|
||||
return devcmds.tempsensor_set(conn, tsen)
|
||||
def jtag_scan(conn, args):
|
||||
return devcmds.jtag_scan(args.start, args.end)
|
||||
def sump_ovclk(conn, args):
|
||||
if args.get: return devcmds.sump_overclock_get(conn)
|
||||
oven = args.set
|
||||
if oven is None:
|
||||
if args.enable: oven = 1
|
||||
elif args.disable: oven = 0
|
||||
if oven is None:
|
||||
print("Error: none of '--get', '--set', '--enable' or '--disable' specified.")
|
||||
return 1
|
||||
return devcmds.sump_overclock_set(conn, oven)
|
||||
|
||||
|
||||
print(repr(args))
|
||||
cmds = {
|
||||
'get-device-info': get_device_info,
|
||||
'get-mode-info': get_mode_info,
|
||||
'set-mode': set_mode,
|
||||
|
||||
'uart-cts-rts': uart_hw_flowctl,
|
||||
'tempsensor': tempsensor,
|
||||
'jtag-scan': jtag_scan,
|
||||
'sump-overclock': sump_ovclk,
|
||||
}
|
||||
|
||||
if args.subcmd is None:
|
||||
print("No subcommand specified?!")
|
||||
return 1
|
||||
|
||||
subfn = cmds.get(args.subcmd, None)
|
||||
if subfn is None:
|
||||
print("Unknown subcommand '%s'" % args.subcmd)
|
||||
return 1
|
||||
|
||||
conn = devconn.connect(args.conn)
|
||||
if isinstance(conn, str):
|
||||
print("Could not connect to a device: %s." % conn)
|
||||
return 1
|
||||
|
||||
with conn as dev:
|
||||
return subfn(dev, args)
|
||||
|
||||
|
||||
def main(argv: List[str]) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
def auto_int(x):
|
||||
return int(x, 0)
|
||||
|
||||
# commands:
|
||||
# * get device info
|
||||
# * get mode info
|
||||
# * set mode
|
||||
#
|
||||
# * mode 1 (general):
|
||||
# * 0x16 0x??: usb hwflowctl on/off, 0x??=0xc3: get current value
|
||||
# * 0x15 0x00: get tempsensor active/address
|
||||
# * 0x15 0x01 0x??: set tempsensor active/address
|
||||
#
|
||||
# * mode 2 (isp/jtag/...): probably nothing
|
||||
#
|
||||
# * mode 3 (jtag pinout scanner):
|
||||
# * 0x30: get status
|
||||
# * 0x31: get result (5 bytes: pin numbers of tck,tms,tdi,tdo,trst)
|
||||
# * 0x32 0xNN 0xMM: start scan (pins 0xNN..0xMM)
|
||||
#
|
||||
# * mode 4 (sump logic analyzer):
|
||||
# * 0x40: get overclock
|
||||
# * 0x41: set overclock
|
||||
#
|
||||
# * mode 5 (ftdi/fx2 emul): probably nothing
|
||||
|
||||
parser.add_argument('--conn', type=str, default=None,
|
||||
help="Connection string. Either a dmj-char device in"+\
|
||||
" /dev, a USB bus.device number, or a USB VID:PID " + \
|
||||
"pair. Defaults to trying /dev/dmj-* (if there is " + \
|
||||
"only one), and cafe:1312 otherwise.")
|
||||
#parser.descripiton = ...
|
||||
|
||||
subcmds = parser.add_subparsers(required=True, metavar="subcommand",
|
||||
dest="subcmd", help="Command to send to "+\
|
||||
"the device",
|
||||
description="For more info on each " + \
|
||||
"subcommand, run the program with " + \
|
||||
"'subcommand --help' as arguments.")
|
||||
|
||||
# general subcommands
|
||||
getdevinfo = subcmds.add_parser("get-device-info", help="Shows device info")
|
||||
|
||||
getmodeinfo = subcmds.add_parser("get-mode-info", help="Shows mode info."+\
|
||||
" A mode can optionally be specified, "+\
|
||||
"default is the current mode.")
|
||||
getmodeinfo.add_argument('mode', default=None, nargs='?',
|
||||
help="Mode to get info of. Defaults to the " + \
|
||||
"current mode, a single asterisk (*) means all.")
|
||||
|
||||
setmode = subcmds.add_parser("set-mode", help="Set the device mode")
|
||||
setmode.add_argument('mode', type=int, help="Mode to switch to, required.")
|
||||
|
||||
# mode 1 commands
|
||||
usbhwfctl = subcmds.add_parser("uart-cts-rts", help="Get, Enable/disable"+\
|
||||
" UART hardware flow control")
|
||||
uartopts = usbhwfctl.add_mutually_exclusive_group()
|
||||
uartopts.add_argument('--get', default=False, action='store_true',
|
||||
help="Get current hardware flow control setting")
|
||||
uartopts.add_argument('--set', default=None, type=bool, nargs=1,
|
||||
help="Set hardware flow control")
|
||||
uartopts.add_argument('--enable', default=False, action='store_true',
|
||||
help="Enable hardware flow control, short for "+\
|
||||
"--set true")
|
||||
uartopts.add_argument('--disable', default=False, action='store_true',
|
||||
help="Disable hardware flow control, short for "+\
|
||||
"--set false")
|
||||
|
||||
tempsense = subcmds.add_parser("tempsensor", help="Get or set the IRC " + \
|
||||
"emulation enable/address of the " + \
|
||||
"temperature sensor.")
|
||||
tsopts = tempsense.add_mutually_exclusive_group()
|
||||
tsopts.add_argument('--get', default=False, action='store_true',
|
||||
help="Get current I2C emul state/address")
|
||||
tsopts.add_argument('--set', default=None, type=auto_int, nargs=1,
|
||||
help="Set emulated I2C address of the temperature "+\
|
||||
"sensor. 0 (or another invalid I2C device address) "+\
|
||||
"to disable the emulated I2C sensor device.")
|
||||
tsopts.add_argument('--disable', default=False, action='store_true',
|
||||
help="Disable emulated I2C temperature sensor, "+\
|
||||
"short for --set true")
|
||||
|
||||
jtagscan = subcmds.add_parser("jtag-scan", help="JTAG pinout scanner")
|
||||
jtagscan.add_argument("start-pin", type=int, help="Number of the start "+\
|
||||
"of the pin range to scan (inclusive)")
|
||||
jtagscan.add_argument("end-pin", type=int, help="Number of the end of "+\
|
||||
"the pin range to scan (inclusive)")
|
||||
|
||||
sumpla = subcmds.add_parser("sump-overclock",
|
||||
help="SUMP logic analyzer overclock")
|
||||
sumpopts = sumpla.add_mutually_exclusive_group()
|
||||
sumpopts.add_argument('--get', default=False, action='store_true',
|
||||
help="Get current overclocking state")
|
||||
sumpopts.add_argument('--set', default=None, type=int, nargs=1,
|
||||
help="Set current overclocking state")
|
||||
sumpopts.add_argument('--enable', default=False, action='store_true',
|
||||
help="Enable overclocking, short for --set 1")
|
||||
sumpopts.add_argument('--disable', default=False, action='store_true',
|
||||
help="Disable overclocking, short for --set 0")
|
||||
|
||||
args = parser.parse_args()#args=argv)
|
||||
return dmctl_do(args)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
sys.exit(main(sys.argv) or 0)
|
||||
except Exception:# as e
|
||||
traceback.print_exc()
|
||||
sys.exit(1);
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import array
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
|
||||
import abc
|
||||
from typing import *
|
||||
|
@ -12,39 +15,163 @@ from typing import *
|
|||
# * implement device commands
|
||||
|
||||
|
||||
class DevConn(abc.ABC):
|
||||
def read(self, nb: int) -> bytes:
|
||||
raise NotImplementedError()
|
||||
|
||||
def write(self, b: bytes):
|
||||
raise NotImplementedError()
|
||||
|
||||
def __enter__(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def __exit__(self, type, value, tb):
|
||||
raise NotImplementedError()
|
||||
|
||||
class DevConn:
|
||||
pass
|
||||
|
||||
class UsbConn(DevConn):
|
||||
_USB_DEFAULT_VID = 0xcafe
|
||||
_USB_DEFAULT_PID = 0x1312
|
||||
_SUBCLASS = 42
|
||||
_PROTOCOL = 69
|
||||
_VER_MIN = 0x0010
|
||||
_VER_MAX = 0x00ff # ???
|
||||
|
||||
def _open_dev(dev) -> Union[UsbConn, str]:
|
||||
import usb, usb.core
|
||||
|
||||
cfg = dev.get_active_configuration()
|
||||
|
||||
if cfg is None: # should be configured already, but eh
|
||||
dev.set_configuration()
|
||||
cfg = dev.get_active_configuration()
|
||||
|
||||
if cfg is None:
|
||||
return "Couldn't get or set device configuration, aaaaa"
|
||||
|
||||
|
||||
itf = [i for i in cfg.interfaces()
|
||||
if i.bInterfaceClass == usb.CLASS_VENDOR_SPEC and
|
||||
i.bInterfaceSubClass == UsbConn._SUBCLASS and
|
||||
i.bInterfaceProtocol == UsbConn._PROTOCOL]
|
||||
|
||||
if len(itf) == 0:
|
||||
return "No vendor control interface found for device"
|
||||
if len(itf) != 1:
|
||||
return "Multiple vendor control interfaces found for device, wtf?"
|
||||
|
||||
itf = itf[0]
|
||||
|
||||
epout = usb.util.find_descriptor(itf, custom_match =
|
||||
lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT)
|
||||
epin = usb.util.find_descriptor(itf, custom_match =
|
||||
lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN)
|
||||
|
||||
try:
|
||||
# try to read the version number. if it throws, it means the usbdev
|
||||
# is in use by something else
|
||||
epout.write(b'\x00')
|
||||
resp = epin.read(4)
|
||||
if resp[0] != 0 or resp[1] != 2:
|
||||
return "Device does not recognise the 'get protocol version' command"
|
||||
|
||||
verno = struct.unpack('<H', resp[2:])[0]
|
||||
if verno < UsbConn._VER_MIN:
|
||||
return "Version of device (%04x) too old, must be at least %04x" \
|
||||
% (hex(verno, USbConn._VER_MIN))
|
||||
if verno > UsbConn._VER_MAX:
|
||||
return "Version of device (%04x) too new, must be max. %04x" \
|
||||
% (hex(verno, USbConn._VER_MAX))
|
||||
except usb.core.USBError:
|
||||
return "Device is busy, already used by something else? (If you use "+\
|
||||
"the kernel module, use a character device from /dev instead.)"
|
||||
|
||||
return UsbConn(dev, cfg, itf, epout, epin)
|
||||
|
||||
def try_find() -> Optional[UsbConn]:
|
||||
return None
|
||||
import usb.core
|
||||
|
||||
dev = list(usb.core.find(find_all=True, idVendor=UsbConn._USB_DEFAULT_VID,
|
||||
idProduct=UsbConn._USB_DEFAULT_PID))
|
||||
|
||||
if dev is None or len(dev) != 1:
|
||||
return None
|
||||
|
||||
return UsbConn._open_dev(dev[0])
|
||||
|
||||
def is_usbdev_path(conn: str) -> bool:
|
||||
return None
|
||||
# eg. cafe:1312
|
||||
match_vidpid = re.match('^[0-9a-fA-F]{4}:[0-9a-fA-F]{4}$', conn)
|
||||
if match_vidpid is not None:
|
||||
return True
|
||||
|
||||
# eg. 1.123 or 1.123.1
|
||||
match_busdev = re.match('^[0-9]{1}\.[0-9]{1,3}(\.[0-9]{1,3})?$', conn)
|
||||
return match_busdev is not None
|
||||
|
||||
def try_open(conn: str) -> Union[UsbConn, str]:
|
||||
return "Could not open USB device '%s'" % conn
|
||||
import usb.core
|
||||
|
||||
conn_busdev = False
|
||||
conntup = None
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
# eg. cafe:1312
|
||||
match_vidpid = re.match('^([0-9a-fA-F]{4}):([0-9a-fA-F]{4})$', conn)
|
||||
if match_vidpid is not None:
|
||||
conntup = tuple(int(x,16) for x in match_vidpid.groups())
|
||||
else:
|
||||
# eg. 1.123
|
||||
match_busdev = re.match('^([0-9]{1,3})\.([0-9]{1,3})(\.([0-9]{1,3}))?$', conn)
|
||||
if match_busdev is not None:
|
||||
conn_busdev = True
|
||||
conntup = match_busdev.groups()
|
||||
if conntup is not None:
|
||||
if conntup[3] is None:
|
||||
conntup = tuple(int(x) for x in conntup[0:2])
|
||||
else:
|
||||
conntup = tuple(int(x) for x in (conntup[0:2] + (conntup[3],)))
|
||||
|
||||
if conntup is None:
|
||||
return "Could not open USB device '%s': not recognised" % conn
|
||||
|
||||
dev = None
|
||||
if conn_busdev:
|
||||
if len(conntup) == 2:
|
||||
dev = list(usb.core.find(find_all=True, bus=conntup[0], address=conntup[1]))
|
||||
elif len(conntup) == 3:
|
||||
dev = list(usb.core.find(find_all=True, bus=conntup[0], address=conntup[1], port=conntup[2]))
|
||||
else:
|
||||
assert False, ("huh? conntup=%s"%repr(conntup))
|
||||
else:
|
||||
dev = list(usb.core.find(find_all=True, idVendor=conntup[0], idProduct=conntup[1]))
|
||||
|
||||
if len(dev) == 0:
|
||||
return "Connect to '%s' (%s): no such device found" % \
|
||||
(conn, "bus.address(.port)" if conn_busdev else "VID:PID")
|
||||
if len(dev) != 1:
|
||||
# TODO: nicer usb device list?
|
||||
return "Connection string '%s' ambiguous, found more than one device: %s" % (conn, str(dev))
|
||||
|
||||
return UsbConn._open_dev(dev[0])
|
||||
|
||||
def read_raw(self, arr) -> int:
|
||||
return self._epin.read(arr)
|
||||
|
||||
def write_raw(self, b: bytes) -> int:
|
||||
return self._epout.write(b)
|
||||
|
||||
def __init__(self, dev, cfg, itf, epin, epout):
|
||||
self._dev = dev
|
||||
self._cfg = cfg
|
||||
self._itf = itf
|
||||
self._epin = epin
|
||||
self._epout = epout
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, tb):
|
||||
pass
|
||||
import usb.util
|
||||
|
||||
usb.util.release_interface(self._dev, self._itf)
|
||||
usb.util.dispose_resources(self._dev)
|
||||
self._epout = None
|
||||
self._epin = None
|
||||
self._itf = None
|
||||
self._cfg = None
|
||||
self._dev = None
|
||||
|
||||
def __str__(self):
|
||||
return str(self._dev)
|
||||
|
||||
|
||||
class ChardevConn(DevConn):
|
||||
|
@ -72,6 +199,30 @@ class ChardevConn(DevConn):
|
|||
class DmjDevice:
|
||||
def __init__(self, conn: DevConn):
|
||||
self._conn = conn
|
||||
self._buf = array.array('B')
|
||||
self._buf.fromlist([0]*64)
|
||||
self._bufpos = 64
|
||||
|
||||
def read(self, nb: int) -> bytes:
|
||||
if len(self._buf) - self._bufpos > nb:
|
||||
rv = bytes(self._buf[self._bufpos:self._bufpos+nb])
|
||||
self._bufpos += nb
|
||||
return rv
|
||||
|
||||
rv = list(self._buf[self._bufpos:])
|
||||
|
||||
while True: # TODO: timeout?
|
||||
nrd = self.conn.read_raw(self._buf)
|
||||
if len(rv) + nrd >= nb: # last read, will have enough now
|
||||
rv = rv + list(self._buf[nb - len(rv):])
|
||||
self._bufpos = nb - len(rv)
|
||||
return bytes(rv)
|
||||
else:
|
||||
rv += list(self._buf)
|
||||
|
||||
# TODO: buffer(/retry) writes as well?
|
||||
def write(self, b: bytes):
|
||||
return self.conn.write_raw(b)
|
||||
|
||||
def __enter__(self):
|
||||
self._conn.__enter__()
|
||||
|
@ -82,6 +233,7 @@ class DmjDevice:
|
|||
|
||||
|
||||
def connect(conn: Optional[str]) -> Union[DmjDevice, str]:
|
||||
# TODO: some kind of connection backend registration? shrug
|
||||
if conn is None:
|
||||
attempt = ChardevConn.try_find()
|
||||
if attempt is not None:
|
||||
|
|
Loading…
Reference in New Issue