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/MassStorageHost.lvt
namespace Livt.Usb.MassStorage
using Livt.IO
using Livt.Usb
using Livt.Usb.Host
/**
* Autonomous direct full-speed SCSI/BOT storage discovery and one retained block.
* Supports LUN zero, READ CAPACITY(10), and 512-byte sectors below 2^32 blocks.
* One external scheduled client accepts, reads and releases jobs. The worker owns
* every controller call; data is never exposed until the command status passes.
*/
component MassStorageHost<B: IRam<byte> & new() = SequentialRam<byte, 512, MemoryStyle.Block>,
ENDPOINT_STORAGE: IRam<byte> & new() = SequentialRam<byte, 32, MemoryStyle.Block>> : IReadOnlyBlockDevice
{
const RETRY_TICKS: uint = (this.context.TicksPer(1Hz) - 1) / 10 + 1
const RETRY_BITS: int = Bits.RequiredFor(RETRY_TICKS)
// Owned discovery/transport; controller is borrowed from the composition root.
controller: IHostController
enumerator: DeviceEnumerator<512, ENDPOINT_STORAGE>
transport: BulkOnlyTransport<B>
inputEndpoint: byte
outputEndpoint: byte
inputPacket: byte
outputPacket: byte
interfaceNumber: byte
blocks: uint
online: bool
// Client-owned request mailbox and worker-owned retained completion.
owned: bool
request: bool
cancelRequest: bool
lba: uint
seen: bool
finished: bool
success: bool
// Read-only diagnostic signals; status 0 absent, 1 enumeration, 2 unsupported,
// 3 initializing, 4 ready, 5 transport/init failure. Sense is key/ASC/ASCQ.
public status: out byte
// Enumeration phases 0..6; initialization: 7 LUN, 8 inquiry, 9 readiness, 10 capacity.
public phase: out byte
public identity: out logic[32]
public sense: out logic[24]
public recoveries: out uint
public commandStatus: out byte
/** Starts discovery on attachment; input/output data storage defaults to BRAM. */
new(controller: IHostController)
{
this.controller = controller
enumerator = new DeviceEnumerator<512, ENDPOINT_STORAGE>(controller)
transport = new BulkOnlyTransport<B>(controller)
}
/** Wires observations and cancellation without crossing scheduled ownership. */
process Signals[]()
{
transport.cancel = cancelRequest
identity = enumerator.identity
recoveries = transport.recoveries
commandStatus = transport.lastStatus
}
/** Returns media readiness independently of the retained read slot. */
override fn IsReady() bool
{
return online
}
/** Returns usable block count only while the media is ready. */
override fn GetBlockCount() uint
{
if (!online) {
return 0
}
return blocks
}
/** This profile explicitly accepts only 512-byte logical sectors. */
override fn GetBlockSize() int
{
if (!online) {
return 0
}
return 512
}
/** Reserves one read and publishes the immutable request to the worker. */
override fn TryReadBlock(lba: uint) bool
{
if (!online || owned || lba >= blocks) {
return false
}
state {
this.lba = lba
cancelRequest = false
owned = true
request = !request
}
return true
}
/** Completion cannot be confused with that of a preceding mailbox generation. */
override fn GetReadState() BlockReadState
{
if (!owned) {
return BlockReadState.Idle
}
if (seen != request || !finished) {
return BlockReadState.Active
}
if (success && online) {
return BlockReadState.Complete
}
return BlockReadState.Failed
}
/** Retains data under consumer backpressure; unplug immediately invalidates it. */
override fn TryReadByte(index: int, value: out byte) bool
{
value = 0
if (!owned || seen != request || !finished || !success || !online) {
return false
}
return transport.TryReadByte(index, value)
}
/** Asks the transport worker to cancel; callers must wait for completion. */
override fn CancelRead() bool
{
if (!owned || (seen == request && finished)) {
return false
}
cancelRequest = true
return true
}
/** Drops a completed block only after its consumer explicitly releases it. */
override fn ReleaseRead() bool
{
if (!owned || seen != request || !finished) {
return false
}
state {
owned = false
cancelRequest = false
}
return true
}
/** Selects exactly one SCSI transparent BOT interface with paired bulk pipes. */
fn Bind() bool
{
state {
inputEndpoint = 0
outputEndpoint = 0
}
if (controller.GetSpeed() != UsbSpeed.Full) {
return false
}
for (var index: byte = 0; index < 3; index++) {
if (enumerator.GetEndpoint(index as int, 1) == 2
&& enumerator.GetEndpoint(index as int, 5) == 8
&& enumerator.GetEndpoint(index as int, 6) == 6
&& enumerator.GetEndpoint(index as int, 7) == 80) {
var candidate = enumerator.GetEndpoint(index as int, 4) as byte
if (inputEndpoint == 0 && outputEndpoint == 0) {
interfaceNumber = candidate
}
if (candidate == interfaceNumber) {
var address = enumerator.GetEndpoint(index as int, 0) as byte
var packet = enumerator.GetEndpoint(index as int, 2) as byte
if (address >= 128) {
if (inputEndpoint != 0) {
return false
}
inputEndpoint = address & 15
inputPacket = packet
} else {
if (outputEndpoint != 0) {
return false
}
outputEndpoint = address & 15
outputPacket = packet
}
}
}
}
return inputEndpoint != 0 && outputEndpoint != 0
}
/** Reads retained successful transport data through a local out parameter. */
fn DataByte(index: int) byte
{
var value: byte
transport.TryReadByte(index, value)
return value
}
/** Parses big-endian SCSI response fields. */
fn DataWord(offset: int) uint
{
var value: uint
value = 0
for (var index: byte = 0; index < 4; index++) {
value = (value << 8) | (DataByte(offset + (index as int)) as uint)
}
return value
}
/** Retains sense key/ASC/ASCQ after a failed SCSI command, never exposing its data. */
fn ReadSense()
{
if (transport.Execute(ReadOnlyScsiCommand.REQUEST_SENSE, 0, 18) == BulkOnlyResult.Passed
&& transport.GetLength() >= 14) {
var format = DataByte(0) & 127
if (format == 112 || format == 113) {
sense = (((DataByte(2) & 15) as logic[24]) << 16)
| ((DataByte(12) as logic[24]) << 8) | (DataByte(13) as logic[24])
}
}
}
/** Initializes LUN zero with bounded ready retries and strict capacity bounds. */
fn Initialize() bool
{
transport.Configure(enumerator.GetPacketSize() as byte, interfaceNumber,
inputEndpoint, inputPacket, outputEndpoint, outputPacket)
phase = 7
if (!transport.InitializeLun()) {
return false
}
phase = 8
if (transport.Execute(ReadOnlyScsiCommand.INQUIRY, 0, 36) != BulkOnlyResult.Passed
|| transport.GetLength() < 36 || DataByte(0) != 0) {
return false
}
phase = 9
var ready: bool
ready = false
for (var attempt: byte = 0; attempt < 8 && !ready; attempt++) {
var result = transport.Execute(ReadOnlyScsiCommand.TEST_UNIT_READY, 0, 0)
if (result == BulkOnlyResult.Passed) {
ready = true
} elif (result != BulkOnlyResult.CommandFailed) {
phase = 11
if (result == BulkOnlyResult.RecoveryFailed) {
phase = 12
}
if (result == BulkOnlyResult.Disconnected) {
phase = 13
}
if (result == BulkOnlyResult.Cancelled) {
phase = 14
}
return false
}
if (!ready) {
ReadSense()
for (var cycle: logic[RETRY_BITS] = 0; (cycle as uint) < RETRY_TICKS; cycle++) {
state {}
}
if (!controller.IsConnected()) {
return false
}
}
}
if (!ready) {
phase = 15
return false
}
phase = 10
if (transport.Execute(ReadOnlyScsiCommand.READ_CAPACITY, 0, 8) != BulkOnlyResult.Passed
|| transport.GetLength() != 8) {
return false
}
var last = DataWord(0)
var size = DataWord(4)
if (last == (0xFFFFFFFF as uint) || size != 512) {
return false
}
blocks = last + 1
return true
}
/** Owns enumeration, SCSI command serialization, disconnect and retained reads. */
process Run()
{
state {
online = false
blocks = 0
status = 0
phase = 0
sense = 0
}
while (!controller.IsConnected()) {
if (owned) {
seen = request;
finished = true;
success = false
}
}
status = 1
var ready = enumerator.Enumerate()
phase = enumerator.GetPhase()
status = 2
if (ready) {
ready = Bind()
}
if (ready) {
status = 3;
ready = Initialize()
}
if (ready) {
phase = 6
status = 4;
online = true
} else {
status = 5
}
while (controller.IsConnected()) {
if (online && seen != request) {
// Invalidate the preceding completion before acknowledging this generation.
state {
finished = false
success = false
seen = request
}
var result = transport.Execute(ReadOnlyScsiCommand.READ_BLOCK, lba, 512)
if (result == BulkOnlyResult.Passed && transport.GetLength() == 512 && !cancelRequest) {
success = true
} elif (result == BulkOnlyResult.CommandFailed) {
ReadSense()
} elif (result == BulkOnlyResult.RecoveryFailed) {
online = false
status = 5
}
finished = true
}
}
online = false
blocks = 0
if (owned) {
seen = request;
finished = true;
success = false
}
}
}