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/hid/BootKeyboardHost.lvt
namespace Livt.Usb.Hid
using Livt.IO
using Livt.Usb
using Livt.Usb.Host
/**
* Direct boot-keyboard lifecycle and interrupt polling, independent of terminal speed.
* Owns the controller's sole scheduled client. Exposes a bounded queue of HID usage
* transitions; application owns layout, text, repeat and LEDs. No hub/report protocol.
*/
component BootKeyboardHost<ENDPOINT_STORAGE: IRam<byte> & new() = SequentialRam<byte, 32, MemoryStyle.Block>> : IKeyboardSource
{
// Borrowed controller and owned discovery, class setup, report parser and queue.
controller: IHostController
enumerator: DeviceEnumerator<512, ENDPOINT_STORAGE>
control: ControlTransfer
parser: BootKeyboardReportParser
events: KeyboardEventQueue
status: byte
dropped: uint
badReports: uint
endpoint: byte
packetSize: byte
interval: byte
interfaceNumber: byte
reports: byte
// Low to high bytes: endpoint, packet size, interval, successful report count.
public diagnostics: out logic[32]
// Valid after binding: VID/PID; configuration, EP0 size, interface and endpoint.
public identity: out logic[32]
public binding: out logic[32]
/**
* Starts discovery automatically after the controller reports a recovered port.
*/
new(controller: IHostController)
{
this.controller = controller
enumerator = new DeviceEnumerator<512, ENDPOINT_STORAGE>(controller)
control = new ControlTransfer(controller)
parser = new BootKeyboardReportParser()
events = new KeyboardEventQueue()
}
/**
* Exposes binding and an eight-bit wrapping report count without scheduled access.
*/
process Diagnostics[]()
{
diagnostics = (endpoint as logic[32]) | ((packetSize as logic[32]) << 8)
| ((interval as logic[32]) << 16) | ((reports as logic[32]) << 24)
identity = enumerator.identity
binding = (enumerator.configurationValue as logic[32]) | ((enumerator.packetBytes as logic[32]) << 8)
| ((interfaceNumber as logic[32]) << 16) | ((endpoint as logic[32]) << 24)
}
/**
* Status: 0 disconnected, 1 enumerating, 2 unsupported/error, 3 class setup,
* 4 polling, 5 stalled/failed. Enumeration phase is available separately.
*/
override fn GetStatus() byte
{
return status
}
/**
* Returns the standard discovery phase, useful for failed-device diagnostics.
*/
override fn GetPhase() byte
{
return enumerator.GetPhase()
}
/**
* Returns the number of dropped events; overflow never blocks USB polling.
*/
override fn GetDropped() uint
{
return dropped
}
/**
* Returns rejected boot report count, including rollover and invalid lengths.
*/
override fn GetBadReports() uint
{
return badReports
}
/**
* Removes one transition. One consumer serializes calls and retains it under
* UART backpressure. Bit 17 marks disconnect; inspect GetDropped for losses.
*/
override fn TryReadEvent(value: out logic[18]) bool
{
return events.TryDequeue(value)
}
/**
* Drains parser transitions into the bounded queue and counts any loss.
*/
fn Publish()
{
var value: logic[18]
while (parser.Next(value)) {
if (!events.TryEnqueue(value)) { dropped++ }
}
}
/**
* Selects the first eligible alternate-zero HID boot-keyboard interrupt IN endpoint.
* Device-specific auxiliary-interface exclusions are isolated in BootKeyboardBinding.
*/
fn Bind() bool
{
for (var index: byte = 0; index < 3; index++) {
if (enumerator.GetEndpoint(index as int, 0) >= 128 && enumerator.GetEndpoint(index as int, 1) == 3
&& enumerator.GetEndpoint(index as int, 5) == 3 && enumerator.GetEndpoint(index as int, 6) == 1
&& enumerator.GetEndpoint(index as int, 7) == 1 && enumerator.GetEndpoint(index as int, 2) >= 8
&& BootKeyboardBinding.AcceptsInterface(identity, enumerator.GetEndpoint(index as int, 4))) {
endpoint = (enumerator.GetEndpoint(index as int, 0) & 15) as byte
packetSize = enumerator.GetEndpoint(index as int, 2) as byte
interval = enumerator.GetEndpoint(index as int, 3) as byte
interfaceNumber = enumerator.GetEndpoint(index as int, 4) as byte
return true
}
}
return false
}
/**
* Runs standard discovery then HID SET_PROTOCOL(boot) and SET_IDLE(0).
* A STALL requires reconnect/reset; no unbounded request or busy polling retries.
*/
process Run()
{
// This report is private to the lifecycle worker; every byte is filled before use.
var report: byte[8]
status = 0
reports = 0
parser.Reset()
while (!controller.IsConnected()) {}
status = 1
var ready = enumerator.Enumerate()
if (ready) {
ready = Bind()
}
status = 2
if (ready) {
status = 3
ready = control.Run(1, enumerator.GetPacketSize(), 33, 11, 0, interfaceNumber as int, 0)
controller.TryRelease()
if (ready) {
ready = control.Run(1, enumerator.GetPacketSize(), 33, 10, 0, interfaceNumber as int, 0)
controller.TryRelease()
}
}
if (ready) { status = 4 } else { status = 2 }
while (ready && controller.IsConnected()) {
controller.TryClearStaging()
var submission = controller.TrySubmit(1, endpoint as int, TransferKind.Interrupt,
TransferDirection.DeviceToHost, packetSize as int, 8, 500, interval as int)
if (submission == SubmissionResult.Accepted) {
while (controller.GetState() == TransferState.Active) {}
var result = controller.GetResult()
if (result == TransferResult.Success) {
reports++
if (controller.GetActualLength() == 8) {
for (var index: byte = 0; index < 8; index++) {
var value: byte
controller.TryReadInput(index as int, value)
report[index as int] = value
}
if (parser.Accept(report)) { Publish() } else { badReports++ }
} else { badReports++ }
} elif (result != TransferResult.Timeout && result != TransferResult.Disconnected) {
ready = false;
status = 5
}
controller.TryRelease()
}
// The controller enforces bInterval across submissions and retries.
}
while (controller.IsConnected()) {}
for (var index: byte = 0; index < 8; index++) {
report[index as int] = 0
}
parser.Accept(report)
Publish()
if (!events.TryEnqueue(131072)) { dropped++ }
}
}