mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
tools: remote: add usb communications protocol
This adds a USB communications protocol to the suite of litex-supported wishbone bridge protocols. Signed-off-by: Sean Cross <sean@xobs.io>
This commit is contained in:
parent
a92e90b215
commit
9dd59d6301
2 changed files with 143 additions and 0 deletions
|
@ -1,4 +1,5 @@
|
|||
from litex.soc.tools.remote.comm_uart import CommUART
|
||||
from litex.soc.tools.remote.comm_udp import CommUDP
|
||||
from litex.soc.tools.remote.comm_pcie import CommPCIe
|
||||
from litex.soc.tools.remote.comm_usb import CommUSB
|
||||
from litex.soc.tools.remote.litex_client import RemoteClient
|
||||
|
|
142
litex/soc/tools/remote/comm_usb.py
Normal file
142
litex/soc/tools/remote/comm_usb.py
Normal file
|
@ -0,0 +1,142 @@
|
|||
import usb.core
|
||||
import time
|
||||
|
||||
# Wishbone USB Protocol Bridge
|
||||
# ============================
|
||||
#
|
||||
# This module implements a bridge to connect LiteX to the target system's
|
||||
# Wishbone bus via USB. It uses `vendor` packets to communicate, which are
|
||||
# normally reserved. Since we're the vendors of this USB protocol, we take
|
||||
# advantage of this packet type to implement the bridge.
|
||||
#
|
||||
# All traffic goes to/from USB EP0, which is guaranteed to exist regardless
|
||||
# of the user's device implementation. The 8th bit of the first SETUP DATA
|
||||
# transaction contains all the information we need to mark this as a
|
||||
# Wishbone packet.
|
||||
#
|
||||
# Packets going to EP0 always start with a SETUP packet, followed by an IN
|
||||
# or an OUT packet, followed by an OUT or an IN packet.
|
||||
#
|
||||
# The SETUP packet looks like this:
|
||||
#
|
||||
# +----+----+----------+----+----+
|
||||
# | C0 | 00 | ADDRESS | 04 | 00 | read packet
|
||||
# +----+----+----------+----+----+
|
||||
# 1 1 4 1 1
|
||||
#
|
||||
# +----+----+----------+----+----+
|
||||
# | 40 | 00 | ADDRESS | 04 | 00 | write packet
|
||||
# +----+----+----------+----+----+
|
||||
# 1 1 4 1 1
|
||||
#
|
||||
# If the transaction is a "read" transaction, the device responds with an OUT
|
||||
# packet with the data. If the transaction is a "write" transaction, the host
|
||||
# responds with an IN packet with the data.
|
||||
#
|
||||
# Much like other Wishbone bridges, there are two types of packets. The first
|
||||
# byte indicates what type of packet it is, and that it is a Wishbone Bridge
|
||||
# packet. This is the value "0x40" (VENDOR type packet destined for DEVICE)
|
||||
# with the "Data Phase Transfer" bit either set or cleared:
|
||||
# - Read: 0xc0
|
||||
# - Write: 0x40
|
||||
#
|
||||
# The next byte is bRequest, which in the current implementation is unused.
|
||||
# Set this value to 0.
|
||||
#
|
||||
# The next four bytes form the wValue and wIndex values of the SETUP token.
|
||||
# We reuse these two 16-bit values as a single 32-bit ADDRESS packet. Note that
|
||||
# USB is big endian.
|
||||
#
|
||||
# Finally, the last two bytes indicate the length of the transaction. Since
|
||||
# we only support 32-bit reads and writes, this is always 4. On big endian
|
||||
# USB, this has the value {04, 00}.
|
||||
|
||||
class CommUSB:
|
||||
def __init__(self, vid=None, pid=None, max_retries=10, debug=False):
|
||||
self.vid = vid
|
||||
self.pid = pid
|
||||
self.debug = debug
|
||||
self.max_retries = max_retries
|
||||
self.MAX_RECURSION_COUNT = 5
|
||||
|
||||
def open(self):
|
||||
if hasattr(self, "dev"):
|
||||
return
|
||||
for t in range(self.max_retries):
|
||||
args = {}
|
||||
if self.vid is not None:
|
||||
args['idVendor'] = self.vid
|
||||
if self.pid is not None:
|
||||
args['idProduct'] = self.pid
|
||||
self.dev = usb.core.find(**args)
|
||||
if self.dev is not None:
|
||||
if self.debug:
|
||||
print("device connected after {} tries".format(t+1))
|
||||
return True
|
||||
del self.dev
|
||||
time.sleep(0.2 * t)
|
||||
print("unable to find usb device after {} tries".format(self.max_retries))
|
||||
return False
|
||||
|
||||
|
||||
def close(self):
|
||||
if not hasattr(self, "dev"):
|
||||
return
|
||||
del self.dev
|
||||
|
||||
def read(self, addr, length=None):
|
||||
data = []
|
||||
length_int = 1 if length is None else length
|
||||
for i in range(length_int):
|
||||
value = self.usb_read(addr)
|
||||
# Note that sometimes, the value ends up as None when the device
|
||||
# disconnects during a transaction. Paper over this fact by
|
||||
# replacing it with a sentinal.
|
||||
if value is None:
|
||||
value = 0xffffffff
|
||||
if self.debug:
|
||||
print("read {:08x} @ {:08x}".format(value, addr))
|
||||
if length is None:
|
||||
return value
|
||||
data.append(value)
|
||||
return data
|
||||
|
||||
def usb_read(self, addr, depth=0):
|
||||
try:
|
||||
value = self.dev.ctrl_transfer(bmRequestType=0xc0,
|
||||
bRequest=0x00,
|
||||
wValue=addr & 0xffff,
|
||||
wIndex=(addr >> 16) & 0xffff,
|
||||
data_or_wLength=4)
|
||||
if value is None:
|
||||
raise TypeError
|
||||
return int.from_bytes(value, byteorder="little")
|
||||
except (usb.core.USBError, TypeError):
|
||||
self.close()
|
||||
self.open()
|
||||
if depth < self.MAX_RECURSION_COUNT:
|
||||
return self.usb_read(addr, depth+1)
|
||||
|
||||
def write(self, addr, data):
|
||||
data = data if isinstance(data, list) else [data]
|
||||
length = len(data)
|
||||
for i, value in enumerate(data):
|
||||
self.usb_write(addr, value)
|
||||
if self.debug:
|
||||
print("write {:08x} @ {:08x}".format(value, addr + 4*i))
|
||||
|
||||
def usb_write(self, addr, value, depth=0):
|
||||
try:
|
||||
self.dev.ctrl_transfer(bmRequestType=0x40, bRequest=0x00,
|
||||
wValue=addr & 0xffff,
|
||||
wIndex=(addr >> 16) & 0xffff,
|
||||
data_or_wLength=bytes([(value >> 0) & 0xff,
|
||||
(value >> 8) & 0xff,
|
||||
(value >> 16) & 0xff,
|
||||
(value >> 24) & 0xff]
|
||||
), timeout=None)
|
||||
except usb.core.USBError:
|
||||
self.close()
|
||||
self.open()
|
||||
if depth < self.MAX_RECURSION_COUNT:
|
||||
return self.usb_write(addr, value, depth+1)
|
Loading…
Reference in a new issue