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/mass-storage/BulkOnlyTransport.lvt
namespace Livt.Usb.MassStorage
using Livt.IO
using Livt.Usb
using Livt.Usb.Host
/** Command result after status validation and, when needed, reset recovery. */
enum BulkOnlyResult
{
Passed
CommandFailed
TransportError
RecoveryFailed
Disconnected
Cancelled
}
/**
* Serialized read-only BOT command/data/status transactions for LUN zero.
* Owns retained input RAM because the controller reuses its buffer for the CSW.
* Configure after enumeration. Only the caller of Execute owns controller traffic.
* Full-speed bulk transfers aggregate packets; no writes or arbitrary CDBs exist.
*/
component BulkOnlyTransport<B: IRam<byte> & new() = SequentialRam<byte, 512, MemoryStyle.Block>>
{
// Borrowed controller and owned control helper/data buffer.
controller: IHostController
control: ControlTransfer
data: B
ep0: byte
interfaceNumber: byte
inputEndpoint: byte
outputEndpoint: byte
inputPacket: byte
outputPacket: byte
tag: uint
actual: logic[10]
valid: bool
// Cancellation is supplied by the single command owner while Execute is running.
public cancel: in bool
public recoveries: out uint
public lastStatus: out byte
/** Binds an idle controller and a data store of at least 512 bytes. */
new(controller: IHostController)
{
this.controller = controller
control = new ControlTransfer(controller)
data = new B()
}
/** Stores a validated interface's geometry; endpoint numbers exclude direction bit. */
public fn Configure(ep0: byte, interfaceNumber: byte, inputEndpoint: byte,
inputPacket: byte, outputEndpoint: byte, outputPacket: byte)
{
assert data.IsValidAddress(511)
state {
this.ep0 = ep0
this.interfaceNumber = interfaceNumber
this.inputEndpoint = inputEndpoint
this.inputPacket = inputPacket
this.outputEndpoint = outputEndpoint
this.outputPacket = outputPacket
valid = false
}
}
/**
* Probes the configured BOT interface before using LUN zero. A stalled
* GET_MAX_LUN means a single LUN; a successful response must contain one byte.
* Shares the control helper with reset recovery; call only while idle.
*/
public fn InitializeLun() bool
{
var completed = control.Run(1, ep0 as int, 161, 254, 0, interfaceNumber as int, 1)
var supported = controller.GetResult() == TransferResult.Stall
if (completed && controller.GetActualLength() == 1) {
var maximum: byte
controller.TryReadInput(0, maximum)
supported = maximum <= 15
}
controller.TryRelease()
return supported
}
/** Clears one device halt and the corresponding host toggle, in that order. */
fn ClearHalt(inputDirection: bool) bool
{
var endpoint: int
endpoint = outputEndpoint as int
if (inputDirection) {
endpoint = (inputEndpoint as int) | 128
}
var cleared = control.Run(1, ep0 as int, 2, 1, 0, endpoint, 0)
controller.TryRelease()
if (!cleared) {
return false
}
return controller.ClearToggle(endpoint & 15, inputDirection)
}
/** Performs the mandatory class reset, clear bulk IN, clear bulk OUT sequence. */
public fn Recover() bool
{
valid = false
controller.TryRelease()
if (!controller.IsConnected()) {
return false
}
recoveries++
var resetSucceeded = control.Run(1, ep0 as int, 33, 255, 0, interfaceNumber as int, 0)
controller.TryRelease()
if (!resetSucceeded || !ClearHalt(true)) {
return false
}
return ClearHalt(false)
}
/** Runs one bounded transfer; its completion remains retained for inspection. */
fn Transfer(inputDirection: bool, length: int) TransferResult
{
var endpoint: int
var packet: int
var direction: TransferDirection
state {
endpoint = outputEndpoint as int
packet = outputPacket as int
direction = TransferDirection.HostToDevice
}
if (inputDirection) {
state {
endpoint = inputEndpoint as int
packet = inputPacket as int
direction = TransferDirection.DeviceToHost
}
}
if (controller.TrySubmit(1, endpoint, TransferKind.Bulk, direction,
packet, length, 2000, 0) != SubmissionResult.Accepted) {
return TransferResult.ProtocolError
}
while (controller.GetState() == TransferState.Active) {
if (cancel) {
controller.Cancel()
}
}
return controller.GetResult()
}
/** Reads one retained controller byte using a local out parameter. */
fn InputByte(index: int) byte
{
var value: byte
controller.TryReadInput(index, value)
return value
}
/** Reads a little-endian CSW field without exposing controller storage. */
fn InputWord(offset: int) uint
{
var value: uint
value = 0
for (var index: byte = 0; index < 4; index++) {
value = value | ((InputByte(offset + (index as int)) as uint) << ((index as int) * 8))
}
return value
}
/** Builds CBW bytes, padding the CDB to sixteen bytes with zeros. */
fn CommandByte[](index: byte, opcode: byte, lba: uint, commandLength: byte, length: int) byte
{
if (index < 4) {
if (index == 0) {
return 85
}
if (index == 1) {
return 83
}
if (index == 2) {
return 66
}
return 67
}
if (index < 8) {
return ((tag >> (((index as int) - 4) * 8)) & 255) as byte
}
if (index < 12) {
return (((length as uint) >> (((index as int) - 8) * 8)) & 255) as byte
}
if (index == 12) {
if (length == 0) {
return 0
}
return 128
}
if (index == 13) {
return 0
}
if (index == 14) {
return commandLength
}
return ReadOnlyScsiCommand.GetByte(opcode, lba, ((index as int) - 15) as byte)
}
/** Invalidates data, recovers transport synchronization, and reports failure. */
fn Fail() BulkOnlyResult
{
valid = false
controller.TryRelease()
if (!controller.IsConnected()) {
return BulkOnlyResult.Disconnected
}
if (!Recover()) {
return BulkOnlyResult.RecoveryFailed
}
if (cancel) {
return BulkOnlyResult.Cancelled
}
return BulkOnlyResult.TransportError
}
/**
* Executes a permitted read-only command. Result/data are valid only after return.
* Each phase has a 2s transfer deadline; CSW stall retries once. Transport errors
* perform one reset recovery. CommandFailed requires REQUEST SENSE at class level.
*/
public fn Execute(opcode: byte, lba: uint, length: int) BulkOnlyResult
{
state {
valid = false
actual = 0
lastStatus = 255
}
var commandLength = ReadOnlyScsiCommand.Length(opcode)
if (cancel) {
return BulkOnlyResult.Cancelled
}
if (commandLength == 0 || length != ReadOnlyScsiCommand.DataLength(opcode)) {
return BulkOnlyResult.TransportError
}
tag++
controller.TryClearStaging()
for (var index: byte = 0; index < 31; index++) {
if (!controller.TryStageOutput(CommandByte(index, opcode, lba, commandLength, length))) {
return Fail()
}
}
if (Transfer(false, 31) != TransferResult.Success) {
return Fail()
}
controller.TryRelease()
if (length > 0) {
var dataResult = Transfer(true, length)
var received = controller.GetActualLength()
if (received < 0 || received > length) {
return Fail()
}
actual = received as logic[10]
for (var index: logic[10] = 0; (index as uint) < (actual as uint); index++) {
data.Write((index as uint) as int, InputByte((index as uint) as int))
}
controller.TryRelease()
if (dataResult == TransferResult.Stall) {
if (!ClearHalt(true)) {
return Fail()
}
} elif (dataResult != TransferResult.Success) {
return Fail()
}
}
var statusResult = Transfer(true, 13)
if (statusResult == TransferResult.Stall) {
controller.TryRelease()
if (!ClearHalt(true)) {
return Fail()
}
statusResult = Transfer(true, 13)
}
if (statusResult != TransferResult.Success || controller.GetActualLength() != 13) {
return Fail()
}
if (InputWord(0) != (0x53425355 as uint)) {
return Fail()
}
if (InputWord(4) != tag) {
return Fail()
}
var residue = InputWord(8)
var status = InputByte(12)
lastStatus = status
controller.TryRelease()
if (status > 1
|| residue > (length as uint) || (length as uint) - residue > (actual as uint)) {
return Fail()
}
if (status == 1) {
return BulkOnlyResult.CommandFailed
}
actual = (length - (residue as int)) as logic[10]
valid = true
return BulkOnlyResult.Passed
}
/** Returns relevant bytes only after a successful command status. */
public fn GetLength() int
{
if (!valid) {
return 0
}
return (actual as uint) as int
}
/** Non-consuming read of retained command data; the next Execute invalidates it. */
public fn TryReadByte(index: int, value: out byte) bool
{
value = 0
if (!valid || index < 0 || index >= ((actual as uint) as int)) {
return false
}
value = data.Read(index)
return true
}
}