Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,103 @@
# SPDX-License-Identifier: MPL-2.0
"""Bounded Linux usbfs transport for the read-only Tundra diagnostic protocol."""
import ctypes
import fcntl
from pathlib import Path
import struct
import time
class Bulk(ctypes.Structure):
_fields_=[('ep',ctypes.c_uint),('length',ctypes.c_uint),('timeout',ctypes.c_uint),('data',ctypes.c_void_p)]
def parse_endpoints(data, configuration):
"""Read the active vendor interface; FunctionFS may renumber endpoints."""
candidates = []
current = None
active = False
offset = 0
while offset < len(data):
if len(data)-offset < 2:
raise ValueError('Truncated USB descriptor')
size, kind = data[offset:offset+2]
if size < 2 or offset+size > len(data):
raise ValueError('Invalid USB descriptor length')
item = data[offset:offset+size]
if kind == 2:
if size < 9:
raise ValueError('Invalid configuration descriptor')
active = item[5] == configuration
current = None
elif kind == 4:
if size < 9:
raise ValueError('Invalid interface descriptor')
current = None
if active and item[3] == 0 and item[5:8] == bytes([255,0,0]):
current = {'interface':item[2], 'count':item[4], 'endpoints':[]}
candidates.append(current)
elif kind == 5 and current is not None:
if size < 7:
raise ValueError('Invalid endpoint descriptor')
current['endpoints'].append((item[2], item[3]&3))
offset += size
if len(candidates) != 1:
raise ValueError('Expected one active vendor diagnostic interface')
candidate = candidates[0]
endpoints = candidate['endpoints']
incoming = [ep for ep,kind in endpoints if kind == 2 and ep&0x80 and ep&0x0f]
outgoing = [ep for ep,kind in endpoints if kind == 2 and not ep&0x80 and ep&0x0f]
if candidate['count'] != 2 or len(endpoints) != 2 or len(incoming) != 1 or len(outgoing) != 1:
raise ValueError('Expected one bulk IN and one bulk OUT endpoint')
return {'interface':candidate['interface'], 'in':incoming[0], 'out':outgoing[0]}
def device_layout(device, sysfs=Path('/sys/bus/usb/devices')):
device = Path(device)
for entry in sysfs.iterdir():
try:
path = Path('/dev/bus/usb')/f'{int((entry/"busnum").read_text()):03d}'/f'{int((entry/"devnum").read_text()):03d}'
except (FileNotFoundError,NotADirectoryError):
continue
if path == device:
identity = [(entry/k).read_text().strip() for k in ('idVendor','idProduct','serial')]
if identity != ['1d6b','0104','TUNDRA-DIAG']:
raise ValueError('Selected USB device is not the Tundra diagnostic')
return parse_endpoints((entry/'descriptors').read_bytes(), int((entry/'bConfigurationValue').read_text()))
raise RuntimeError('USB diagnostic disappeared; select its current device path')
def exchange(device, commands=('status','version','mounts')):
commands = tuple(commands)
for command in commands:
if not command.isascii() or not command.isalpha() or len(command)>60:
raise ValueError('Invalid diagnostic command')
layout = device_layout(device)
libc=ctypes.CDLL(None,use_errno=True)
libc.ioctl.argtypes = [ctypes.c_int,ctypes.c_ulong,ctypes.c_void_p]
libc.ioctl.restype = ctypes.c_int
ioctl_bulk=0xc0005502 | (ctypes.sizeof(Bulk)<<16)
results={}
with Path(device).open('r+b',buffering=0) as stream:
fcntl.ioctl(stream,0x8004550f,struct.pack('I',layout['interface'])) # USBDEVFS_CLAIMINTERFACE
def bulk(ep,payload=None):
buf=ctypes.create_string_buffer(payload,len(payload)) if payload else ctypes.create_string_buffer(4096)
request=Bulk(ep,len(buf),3000,ctypes.addressof(buf))
count=libc.ioctl(stream.fileno(),ioctl_bulk,ctypes.byref(request))
if count<0:raise OSError(ctypes.get_errno(),f'USB transfer endpoint 0x{ep:02x}')
if payload is not None and count != len(payload):
raise RuntimeError('Incomplete USB command write')
return buf.raw[:count]
for command in commands:
bulk(layout['out'],(command+'\n').encode())
response=b''
deadline=time.monotonic()+6
while not response.endswith(b'\nEND\n'):
if time.monotonic() >= deadline:
raise TimeoutError('Diagnostic response did not finish')
chunk=bulk(layout['in'])
if not chunk:
raise RuntimeError('Empty USB response')
response+=chunk
if len(response)>8192:raise RuntimeError('Invalid oversized response')
results[command]=response.decode()
return results