Livt packages
Livt.Usb
Version 0.0.5
USB 2.0 low/full-speed PHY, host transfers, device discovery, HID keyboard, and read-only mass storage for Livt.
Published files (57)
src/host/HostController.lvt
namespace Livt.Usb.Host
using Livt.IO
using Livt.Usb
using Livt.Usb.IO
using Livt.Usb.Packets
/**
* Single-device host with retained transfers and autonomous wire/frame service.
* One scheduled client owns staging, submission, reading and release. An independent
* worker owns accepted storage until completion. Each endpoint/direction retains its
* toggle; reset disconnects, cancels active work and clears all toggles.
* PIPELINE_LINK defaults to registered link stages; false selects the direct path.
* Both settings share the inherited clock/reset and preserve storage ownership.
*/
component HostController<CAPACITY: int = 512, STYLE: MemoryStyle = MemoryStyle.Block,
B: IRam<byte> & new() = SequentialRam<byte, CAPACITY, STYLE>,
PORT_DEBOUNCE_US: int = 100000, PORT_RESET_US: int = 12000, PORT_RECOVERY_US: int = 10000,
P: IRam<logic[16]> & new() = SequentialRam<logic[16], 32, STYLE>,
PACKET_STORAGE: ISynchronousRam<byte, uint, RamGeometry<64>> & new() = SynchronousRam<byte, uint, 64, STYLE>,
PIPELINE_LINK: bool = true> : IHostController
{
static assert CAPACITY >= 8 && CAPACITY <= 65535, "Transfer capacity must be 8..65535"
// Counts include CAPACITY; packet byte indices never exceed 64.
const COUNT_BITS: int = Bits.RequiredFor(CAPACITY)
// Owned link, transaction engine, validation and independent staging/input RAM.
phy: Usb2Phy
port: RootPort<PORT_DEBOUNCE_US, PORT_RESET_US, PORT_RECOVERY_US>
engine: TransactionEngine<STYLE, PACKET_STORAGE, PIPELINE_LINK>
profile: HostProfile<CAPACITY>
output: B
input: B
setup: byte[8]
setupCount: byte
outputCount: logic[COUNT_BITS]
public dpOutput: out logic
public dmOutput: out logic
public dpEnable: out logic
public dmEnable: out logic
public debug: out logic[16]
// Result code bits 0..3, last wire length 4..10, endpoint 11..14, toggle 15.
public transferDiagnostics: out logic[16]
// Client-owned request snapshot and toggle mailboxes.
owned: bool
request: bool
cancelRequest: bool
resetRequest: bool
address: byte
endpoint: byte
kind: TransferKind
direction: TransferDirection
packetSize: byte
requestedLength: logic[COUNT_BITS]
deadline: logic[16]
acceptedFrame: logic[16]
interval: byte
// Worker-owned retained completion, endpoint state and command signals.
seen: bool
finished: bool
result: TransferResult
actual: logic[COUNT_BITS]
toggles: logic[32]
pollValid: logic[32]
pollFrames: P
command: bool
commandPid: byte
commandOne: bool
clearPacket: bool
load: bool
loadByte: byte
read: bool
readIndex: byte
wireResult: TransactionResult
wireAcknowledged: bool
packetLength: byte
controlOne: bool
startFrame: logic[16]
lastFrame: logic[16]
toggleIndex: byte
clearToggleRequest: bool
clearToggleSeen: bool
clearToggleMask: logic[32]
/**
* Owns physical signaling in the inherited clock/reset context.
*/
new(dp: logic, dm: logic)
{
phy = new Usb2Phy(dp, dm)
port = new RootPort<PORT_DEBOUNCE_US, PORT_RESET_US, PORT_RECOVERY_US>()
engine = new TransactionEngine<STYLE, PACKET_STORAGE, PIPELINE_LINK>()
profile = new HostProfile<CAPACITY>()
output = new B()
input = new B()
pollFrames = new P()
}
/**
* Wires clocked services without borrowing aggregates across component boundaries.
*/
process Ports[]()
{
port.line = phy.lineState
port.driving = phy.dpEnable == 0b1
port.resetRequest = resetRequest
phy.speed = port.speed
phy.busReset = port.busReset
phy.txValid = engine.txValid
phy.txData = engine.txData
phy.keepAliveRequest = engine.keepAlive
engine.speed = port.speed
engine.connected = port.connected
engine.cancel = port.busReset || port.speed == UsbSpeed.Unknown || cancelRequest
engine.frameTick = port.frameTick
engine.frame = port.frame
engine.frameRemaining = port.frameRemaining
engine.phyReady = phy.txReady
engine.phyBusy = phy.txBusy
engine.keepReady = phy.keepAliveReady
engine.rxActive = phy.rxActive
engine.rxValid = phy.rxValid
engine.rxData = phy.rxData
engine.rxError = phy.rxError
engine.clearRequest = clearPacket
engine.loadRequest = load
engine.loadData = loadByte
engine.request = command
engine.tokenPid = commandPid
engine.token = TokenCodec.Fields(address as logic[7], endpoint as logic[4])
engine.dataOne = commandOne
engine.maxLength = packetSize as uint
engine.readRequest = read
engine.readAddress = readIndex as uint
dpOutput = phy.dpOutput
dmOutput = phy.dmOutput
dpEnable = phy.dpEnable
dmEnable = phy.dmEnable
var observations = engine.phaseCode as logic[16]
if (port.connected) { observations = observations | 16 }
if (command) { observations = observations | 32 }
if (engine.requestAccepted) { observations = observations | 64 }
if (owned) { observations = observations | 128 }
if (request) { observations = observations | 256 }
if (seen) { observations = observations | 512 }
if (finished) { observations = observations | 1024 }
if (phy.rxActive) { observations = observations | 2048 }
if (phy.txBusy) { observations = observations | 4096 }
if (phy.txReady) { observations = observations | 8192 }
if (engine.completed) { observations = observations | 16384 }
if (engine.ready) { observations = observations | 32768 }
debug = observations
var responseCode: logic[16]
match engine.result
{
TransactionResult.Success =>
{
responseCode = 1
}
TransactionResult.Nak =>
{
responseCode = 2
}
TransactionResult.Stall =>
{
responseCode = 3
}
TransactionResult.Timeout =>
{
responseCode = 4
}
TransactionResult.ProtocolError =>
{
responseCode = 5
}
TransactionResult.Duplicate =>
{
responseCode = 6
}
TransactionResult.Disconnected =>
{
responseCode = 7
}
_ =>
{
responseCode = 0
}
}
var packetObservation = responseCode | ((engine.length as logic[16]) << 4) | ((endpoint as logic[16]) << 11)
if (commandOne) { packetObservation = packetObservation | 32768 }
transferDiagnostics = packetObservation
}
/**
* Reports control/interrupt support and full-speed bulk; no isochronous traffic.
*/
override fn Supports(speed: UsbSpeed, kind: TransferKind) bool
{
return profile.Supports(speed, kind)
}
/**
* Supports one directly attached device, without a hub.
*/
override fn GetDeviceCapacity() int
{
return 1
}
/**
* Bounds initial configuration discovery to four interfaces.
*/
override fn GetInterfaceCapacity() int
{
return 4
}
/**
* Advertises the initial descriptor binding capacity including endpoint zero.
*/
override fn GetEndpointCapacity() int
{
return 4
}
/**
* Returns retained data capacity, excluding the eight setup bytes.
*/
override fn GetTransferCapacity() int
{
return CAPACITY
}
/**
* Returns true after attach, reset and recovery have completed.
*/
override fn IsConnected() bool
{
return port.connected
}
/**
* Reports negotiated line speed, or Unknown before attachment.
*/
override fn GetSpeed() UsbSpeed
{
return port.speed
}
/**
* Appends a setup byte only while the transfer slot is idle.
*/
override fn TryStageSetup(value: byte) bool
{
if (owned || setupCount == 8) { return false }
setup[setupCount as int] = value
setupCount++
return true
}
/**
* Appends one output byte without lending the worker external storage.
*/
override fn TryStageOutput(value: byte) bool
{
if (owned || outputCount == (CAPACITY as logic[COUNT_BITS])) { return false }
output.Write((outputCount as uint) as int, value)
outputCount++
return true
}
/**
* Discards only idle staging; active and retained completions are immutable.
*/
override fn TryClearStaging() bool
{
if (owned) { return false }
setupCount = 0
outputCount = 0
return true
}
/**
* Validates the complete request before publishing its toggle to the worker.
*/
override fn TrySubmit(address: int, endpoint: int, kind: TransferKind,
direction: TransferDirection, maxPacketSize: int, length: int,
timeoutFrames: int, intervalFrames: int) SubmissionResult
{
if (owned) { return SubmissionResult.Busy }
if (!port.connected) { return SubmissionResult.Disconnected }
if (!profile.Supports(port.speed, kind)) { return SubmissionResult.Unsupported }
if (!profile.IsValid(port.speed, address, endpoint, kind, maxPacketSize, length, timeoutFrames, intervalFrames)) {
return SubmissionResult.Invalid
}
if (kind == TransferKind.Control) {
if (setupCount != 8 || (setup[6] as int) + (setup[7] as int) * 256 != length) { return SubmissionResult.Invalid }
if (length > 0 && ((setup[0] & 128) != 0) != (direction == TransferDirection.DeviceToHost)) { return SubmissionResult.Invalid }
if (length == 0 && direction != TransferDirection.HostToDevice) { return SubmissionResult.Invalid }
} elif (setupCount != 0) { return SubmissionResult.Invalid }
if (direction == TransferDirection.HostToDevice) {
if ((outputCount as uint) != (length as uint)) { return SubmissionResult.Invalid }
} elif (outputCount != 0) { return SubmissionResult.Invalid }
this.address = address as byte
this.endpoint = endpoint as byte
this.kind = kind
this.direction = direction
packetSize = maxPacketSize as byte
requestedLength = length as logic[COUNT_BITS]
deadline = timeoutFrames as logic[16]
acceptedFrame = port.frame
interval = intervalFrames as byte
cancelRequest = false
owned = true
request = !request
return SubmissionResult.Accepted
}
/**
* Reports retained completion only after the worker releases all storage.
*/
override fn GetState() TransferState
{
if (!owned) { return TransferState.Idle }
if (finished == request) { return TransferState.Complete }
return TransferState.Active
}
/**
* Returns Pending until completion is published.
*/
override fn GetResult() TransferResult
{
if (!owned || finished != request) { return TransferResult.Pending }
return result
}
/**
* Reports the acknowledged prefix after completion, including error prefixes.
*/
override fn GetActualLength() int
{
if (!owned || finished != request) { return 0 }
return (actual as uint) as int
}
/**
* Reads retained IN data only within its initialized prefix.
*/
override fn TryReadInput(index: int, value: out byte) bool
{
value = 0
if (!owned || finished != request || direction != TransferDirection.DeviceToHost || index < 0 || index >= ((actual as uint) as int)) { return false }
value = input.Read(index)
return true
}
/**
* Cancels active wire work and waits until the worker publishes Cancelled.
*/
override fn Cancel() bool
{
if (!owned || finished == request) { return false }
cancelRequest = true
while (finished != request) {}
if (result != TransferResult.Cancelled) {
cancelRequest = false
return false
}
return true
}
/**
* Releases retained completion and prepares empty staging for a new request.
*/
override fn TryRelease() bool
{
if (!owned || finished != request) { return false }
owned = false
cancelRequest = false
setupCount = 0
outputCount = 0
return true
}
/**
* Requests re-enumeration through a full port debounce/reset cycle.
*/
public fn ResetPort()
{
resetRequest = !resetRequest
}
/**
* Resets one data toggle after a successful CLEAR_FEATURE(ENDPOINT_HALT).
* The single owner calls only while idle; configuration/reset clears all entries.
*/
override fn ClearToggle(endpoint: int, inputDirection: bool) bool
{
if (owned || endpoint < 1 || endpoint > 15) { return false }
var index = endpoint * 2
if (inputDirection) { index++ }
clearToggleMask = (1 as logic[32]) << index
clearToggleRequest = !clearToggleRequest
while (clearToggleSeen != clearToggleRequest) {}
return true
}
/**
* Evaluates cancellation and a wrapping millisecond deadline between attempts.
*/
fn CanContinue() bool
{
if (cancelRequest) {
result = TransferResult.Cancelled;
return false
}
if (!port.connected) {
result = TransferResult.Disconnected;
return false
}
if ((((port.frame as uint) - (startFrame as uint)) as logic[16]) >= deadline) {
result = TransferResult.Timeout;
return false
}
return true
}
/**
* Executes an attempt; OUT retries reload immutable staging. The engine ACKs
* IN before this worker reads the retained packet. Interval is enforced at retries.
*/
fn Exchange(pid: byte, one: bool, count: byte, offset: logic[COUNT_BITS]) bool
{
wireAcknowledged = false
while (CanContinue()) {
if (kind == TransferKind.Interrupt && ((pollValid >> (toggleIndex as int)) & 1) != 0) {
var polledFrame = pollFrames.Read(toggleIndex as int)
while ((((port.frame as uint) - (polledFrame as uint)) as logic[16]) < (interval as logic[16]) && CanContinue()) {}
if (!CanContinue()) { return false }
}
clearPacket = !clearPacket
while (engine.clearAccepted != clearPacket && CanContinue()) {}
if (!CanContinue()) { return false }
if (pid != PacketPid.IN) {
for (var index: byte = 0; index < count; index++) {
if (pid == PacketPid.SETUP) { loadByte = setup[index as int] } else { loadByte = output.Read(((offset as uint) + (index as uint)) as int) }
load = !load
while (engine.loadAccepted != load && CanContinue()) {}
if (!CanContinue()) { return false }
}
}
commandPid = pid
commandOne = one
command = !command
while (engine.requestAccepted != command && port.connected && !cancelRequest) {}
if (kind == TransferKind.Interrupt) {
pollFrames.Write(toggleIndex as int, port.frame)
pollValid = pollValid | ((1 as logic[32]) << (toggleIndex as int))
}
while (!engine.completed && port.connected && !cancelRequest) {}
wireAcknowledged = engine.acknowledged
if (!CanContinue()) { return false }
wireResult = engine.result
packetLength = engine.length as byte
if (wireResult == TransactionResult.Success) { return true }
if (wireResult == TransactionResult.Stall) {
result = TransferResult.Stall;
return false
}
lastFrame = port.frame
while ((((port.frame as uint) - (lastFrame as uint)) as logic[16]) < (interval as logic[16]) && CanContinue()) {}
if (interval == 0) {
while (port.frame == lastFrame && CanContinue()) {}
}
}
return false
}
/**
* Copies the retained packet after the wire ACK; overflow never exposes extra bytes.
*/
fn CopyPacket() bool
{
if ((actual as uint) + (packetLength as uint) > (requestedLength as uint)) {
result = TransferResult.Overflow;
return false
}
for (var index: byte = 0; index < packetLength; index++) {
readIndex = index
read = true
while (!engine.readValid && CanContinue()) {}
if (!CanContinue()) { return false }
input.Write(((actual as uint) + (index as uint)) as int, engine.readData)
read = false
}
actual = ((actual as uint) + (packetLength as uint)) as logic[COUNT_BITS]
return true
}
/**
* Serializes control stages or packetized interrupt/bulk data and publishes once.
*/
process Work()
{
if (!port.connected) {
toggles = 0;
pollValid = 0
}
if (clearToggleRequest != clearToggleSeen) {
toggles = toggles & ~clearToggleMask
clearToggleSeen = clearToggleRequest
}
if (request == seen) { continue }
seen = request
startFrame = acceptedFrame
actual = 0
result = TransferResult.Pending
controlOne = false
toggleIndex = ((endpoint as int) * 2) as byte
if (direction == TransferDirection.DeviceToHost) { toggleIndex++ }
var success: bool
success = true
if (kind == TransferKind.Control) {
success = Exchange(PacketPid.SETUP, false, 8, 0)
controlOne = true
} else {
controlOne = ((toggles >> (toggleIndex as int)) & 1) != 0
}
var more = kind != TransferKind.Control || requestedLength != 0
while (success && more) {
var remaining = (requestedLength as uint) - (actual as uint)
var count: byte
count = packetSize
if (remaining < (packetSize as uint)) { count = remaining as byte }
var pid: byte
pid = PacketPid.OUT
if (direction == TransferDirection.DeviceToHost) { pid = PacketPid.IN }
success = Exchange(pid, controlOne, count, actual)
if (wireAcknowledged) {
// Retention failure or cancellation cannot roll back an ACKed packet.
controlOne = !controlOne
if (kind != TransferKind.Control) {
toggles = toggles ^ ((1 as logic[32]) << (toggleIndex as int))
}
if (direction == TransferDirection.HostToDevice) {
actual = ((actual as uint) + (count as uint)) as logic[COUNT_BITS]
}
}
if (success && direction == TransferDirection.DeviceToHost) { success = CopyPacket() }
more = actual < requestedLength
if (direction == TransferDirection.DeviceToHost && packetLength != packetSize) { more = false }
}
if (success && kind == TransferKind.Control) {
if (direction == TransferDirection.DeviceToHost && requestedLength != 0) {
success = Exchange(PacketPid.OUT, true, 0, 0)
} else {
success = Exchange(PacketPid.IN, true, 0, 0)
if (success && packetLength != 0) {
result = TransferResult.ProtocolError;
success = false
}
}
if (success && setup[0] == 0 && setup[1] == 9) {
toggles = 0;
pollValid = 0
}
}
if (cancelRequest) { result = TransferResult.Cancelled } elif (success) { result = TransferResult.Success }
finished = seen
}
}