MODULE UsbHcdi;
IMPORT SYSTEM, KernelLog, Machine, Plugins, Modules, Kernel, Objects, Usbdi, Debug := UsbDebug;
CONST
PortStatusDevicePresent* = {0};
PortStatusEnabled* = {1};
PortStatusLowSpeed* = {2};
PortStatusFullSpeed* = {3};
PortStatusHighSpeed* = {4};
PortStatusReset* = {5};
PortStatusError* = {6};
PortStatusConnectChange* = {7};
PortStatusSuspended* = {8};
PortStatusOverCurrent* = {9};
PortStatusPowered* = {10};
PortStatusEnabledChange* = {11};
PortStatusSuspendChange* = {12};
PortStatusOverCurrentChange* = {13};
PortStatusResetChange* = {14};
PortStatusWakeOnOvercurrent* = {15};
PortStatusWakeOnDisconnect* = {16};
PortStatusWakeOnConnect* = {17};
PortStatusTestControl* = {18};
PortStatusIndicatorControl* = {19};
PortStatusPortOwner* = {20};
NoErrors* = {};
ShortPacket* = {1};
Stalled* = {2};
InProgress* = {3};
Nak* = {4};
Crc* = {5};
Timeout* = {6};
CrcTimeout* = {7};
BitStuff* = {8};
Databuffer* = {9};
Babble* = {10};
UnexpectedPid* = {13};
PidCheckFailure* = {15};
DataToggleMismatch* = {16};
DeviceNotResponding* = {17};
LinkTDsFailed* = {18};
OutOfTDs* = {19};
Internal* = {11};
TransferTooLarge* = {14};
Disconnected* = {12};
PipeControl* = 0;
PipeIsochronous* = 1;
PipeBulk* = 2;
PipeInterrupt* = 3;
Undefined* = 0;
Initialized* = 1;
Operational* = 2;
Suspended* = 3;
Resuming* = 4;
Halted* = 5;
Shutdown* = 6;
LowSpeed* = 0;
FullSpeed* = 1;
HighSpeed* = 2;
In* = 0;
Out* = 1;
TDsPerPipe = 64000;
TDsDefaultPipe = 20;
NotAvailable* = 0;
Global* = 1;
PerPort* = 2;
Automatic* = 0;
Amber* = 1;
Green* = 2;
Off* = 3;
PortResetTime* = 10 + 20;
PortEnableTimeout* = 20;
PortInsertionTime* = 100 + 50;
AddressRecoveryTime* = 2 + 10;
RootHubResetTime* = 100;
MinSuspendTime* = 8;
StateDisconnected* = -1;
TYPE
AlignedMemSpace* = POINTER TO RECORD
data- : POINTER TO ARRAY OF LONGINT;
base- : SYSTEM.ADDRESS;
END;
HubDescriptor* = POINTER TO ARRAY OF CHAR;
StatusChangeHandler* = PROCEDURE {DELEGATE} (status : Usbdi.Status; actLen : LONGINT);
ControlMessage* = POINTER TO ARRAY 8 OF CHAR;
TYPE
HcdInterface = OBJECT(Plugins.Plugin)
PROCEDURE EnablePortPower*(port : LONGINT);
BEGIN HALT(301); END EnablePortPower;
PROCEDURE DisablePortPower*(port : LONGINT);
BEGIN HALT(301); END DisablePortPower;
PROCEDURE ResetAndEnablePort*(port : LONGINT) : BOOLEAN;
BEGIN HALT(301); RETURN FALSE; END ResetAndEnablePort;
PROCEDURE DisablePort*(port: LONGINT);
BEGIN HALT(301); END DisablePort;
PROCEDURE SuspendPort*(port: LONGINT) : BOOLEAN;
BEGIN
IF Debug.Level >= Debug.Warnings THEN KernelLog.String("UsbHcdi: Port suspending not supported."); KernelLog.Ln; END;
RETURN FALSE;
END SuspendPort;
PROCEDURE ResumePort*(port: LONGINT) : BOOLEAN;
BEGIN
IF Debug.Level >= Debug.Warnings THEN KernelLog.String("UsbHcdi: Port resuming not supported."); KernelLog.Ln; END;
RETURN FALSE;
END ResumePort;
PROCEDURE GetPortStatus*(port : LONGINT; ack : BOOLEAN) : SET;
BEGIN HALT(301); RETURN {}; END GetPortStatus;
PROCEDURE IndicatePort*(port, indicate : LONGINT);
BEGIN
IF Debug.Level >= Debug.Warnings THEN KernelLog.String("UsbHcdi: Port indicator control not supported."); KernelLog.Ln; END;
END IndicatePort;
PROCEDURE RoutePortToCompanion*(port : LONGINT);
BEGIN
IF Debug.Level >= Debug.Warnings THEN KernelLog.String("UsbHcdi: Port routing not supported."); KernelLog.Ln; END;
END RoutePortToCompanion;
PROCEDURE Schedule*(pipe : Pipe; bufferLen, offset: LONGINT; VAR buffer : Usbdi.Buffer);
BEGIN HALT(301); END Schedule;
PROCEDURE ScheduleControl*(pipe : Pipe; direction: LONGINT; msg: ControlMessage; bufferLen : LONGINT; VAR buffer : Usbdi.Buffer);
BEGIN HALT(301); END ScheduleControl;
PROCEDURE InsertQH*(pipe : Pipe): BOOLEAN;
BEGIN HALT(301); RETURN FALSE; END InsertQH;
PROCEDURE RemoveQH*(pipe : Pipe);
BEGIN HALT(301); END RemoveQH;
PROCEDURE UpdatePipeStatus*(pipe : Pipe);
BEGIN HALT(301); END UpdatePipeStatus;
PROCEDURE LinkTDsAllowed*(pipe : Pipe) : BOOLEAN;
BEGIN HALT(301); RETURN FALSE; END LinkTDsAllowed;
PROCEDURE LinkTDs*(pipe : Pipe; firstTD : Machine.Address32);
BEGIN HALT(301); END LinkTDs;
PROCEDURE UnlinkTDs*(pipe: Pipe);
BEGIN HALT(301); END UnlinkTDs;
PROCEDURE ClearHalt*(pipe : Pipe);
BEGIN HALT(301); END ClearHalt;
PROCEDURE ShowQH*(qh, firstTD : SYSTEM.ADDRESS);
BEGIN
IF Debug.Level >= Debug.Warnings THEN KernelLog.String("QH/TD diagnostics not implemented."); KernelLog.Ln; END;
END ShowQH;
PROCEDURE ShowPipe*(pipe : Pipe);
BEGIN
IF Debug.Level >= Debug.Warnings THEN KernelLog.String("Pipe diagnostics not implemented."); KernelLog.Ln; END;
END ShowPipe;
PROCEDURE ShowSchedule*;
BEGIN
IF Debug.Level >= Debug.Warnings THEN KernelLog.String("Schedule diagnostics not implemented."); KernelLog.Ln; END;
END ShowSchedule;
END HcdInterface;
Hcd* = OBJECT(HcdInterface)
VAR
iobase*: Machine.Address32;
irq* : LONGINT;
portCount* : LONGINT;
ports* : POINTER TO ARRAY OF Machine.Address32;
bus-, device-, function- : LONGINT;
DMAchaining* : BOOLEAN;
sgListSize* : LONGINT;
isHighSpeed* : BOOLEAN;
hubDescriptor* : HubDescriptor;
state- : LONGINT;
statusChangeHandler- : StatusChangeHandler;
pipes- : ARRAY 128 OF ARRAY 2 OF ARRAY 16 OF Pipe;
notifyPipes- : Pipe;
adrRange : ARRAY 128 OF BOOLEAN;
buslock : LONGINT;
timer : Kernel.Timer;
NbytesTransfered- : HUGEINT;
NnofTransfers-,
NnofBulkTransfers-, NnofControlTransfers-, NnofInterruptTransfers-, NnofIsochronousTransfers-,
NnofUnknownTransfers-,
NnofInterrupts*, NnofInterruptsHandled* : LONGINT;
PROCEDURE Acquire*;
BEGIN {EXCLUSIVE}
AWAIT(buslock <= 0); buslock := 1;
END Acquire;
PROCEDURE Release*;
BEGIN {EXCLUSIVE}
DEC(buslock);
END Release;
PROCEDURE SetState*(state : LONGINT);
BEGIN {EXCLUSIVE}
SELF.state := state;
END SetState;
PROCEDURE GetHubDescriptor*() : HubDescriptor;
BEGIN
IF Debug.StrongChecks THEN ASSERT(hubDescriptor # NIL); END;
RETURN hubDescriptor;
END GetHubDescriptor;
PROCEDURE SetHubDescriptor*(hd : HubDescriptor);
BEGIN
IF Debug.StrongChecks THEN ASSERT((hd # NIL) & (LEN(hd) >= 8)); END;
hubDescriptor := hd;
END SetHubDescriptor;
PROCEDURE SetStatusChangeHandler*(handler : StatusChangeHandler) : BOOLEAN;
BEGIN
statusChangeHandler := handler; RETURN TRUE;
END SetStatusChangeHandler;
PROCEDURE AddCompletionHandler*(pipe : Pipe);
VAR temp : Pipe;
BEGIN {EXCLUSIVE}
IF Debug.Trace & Debug.tracePipes THEN
KernelLog.String("UsbHcdi: Adding completion handler for pipe (Adr: "); KernelLog.Int(pipe.address, 0);
KernelLog.String(", ep: "); KernelLog.Int(pipe.endpoint, 0); KernelLog.String(")"); KernelLog.Ln;
END;
temp := notifyPipes;
WHILE(temp.next # NIL) & (temp.next # pipe) DO temp := temp.next; END;
IF temp.next = NIL THEN
temp.next := pipe;
ELSIF Debug.Level >= Debug.Warnings THEN KernelLog.String("UsbHcdi: Warning: Procedure was already registered as interrupt handler"); KernelLog.Ln;
END;
END AddCompletionHandler;
PROCEDURE RemoveCompletionHandler*(pipe : Pipe);
VAR temp : Pipe;
BEGIN
IF Debug.Trace & Debug.tracePipes & (SYSTEM.ADR(pipe) > 10H) THEN
KernelLog.String("UsbHcdi: Removing completion handler for pipe (Adr: "); KernelLog.Int(pipe.address, 0);
KernelLog.String(", ep: "); KernelLog.Int(pipe.endpoint, 0); KernelLog.String(")"); KernelLog.Ln;
END;
pipe.irqActive := FALSE;
temp := notifyPipes;
WHILE (temp.next # NIL) & (temp.next # pipe) DO temp := temp.next; END;
IF temp.next # NIL THEN
temp.next := temp.next.next;
ELSIF Debug.Level >= Debug.Warnings THEN
KernelLog.String("UsbHcdi: Warning: Could not remove interrupt handler (not found)"); KernelLog.Ln;
END;
END RemoveCompletionHandler;
PROCEDURE NotifyCompletionHandlers*;
VAR pipe : Pipe;
BEGIN
pipe := notifyPipes.next;
WHILE pipe # NIL DO
IF pipe.irqActive & (pipe.mode = Usbdi.MinCpu) THEN
UpdatePipeStatus(pipe);
IF (pipe.status # Usbdi.InProgress) & (pipe.device.state # StateDisconnected) THEN
pipe.irqActive := FALSE;
IF Debug.Trace & Debug.traceIoc THEN
KernelLog.String("UsbHcdi: Notify pipe adr: "); KernelLog.Int(pipe.address, 0);
KernelLog.String(", ep: "); KernelLog.Int(pipe.endpoint, 0); KernelLog.String(": "); ShowErrors(pipe.errors); KernelLog.Ln;
END;
IF Debug.Trace & (pipe.status # Usbdi.Ok) THEN
IF (pipe.status = Usbdi.ShortPacket) THEN
IF Debug.traceShortPackets THEN pipe.Show(TRUE); KernelLog.Ln; END;
ELSE
IF Debug.traceFailed THEN pipe.Show(TRUE); KernelLog.Ln; END;
END;
END;
IF Debug.PerformanceMonitoring THEN
IF (pipe.status = Usbdi.Ok) OR (pipe.status = Usbdi.ShortPacket) THEN
INC (NbytesTransfered, pipe.actLen);
END;
END;
IF pipe.mode = Usbdi.MinCpu THEN pipe.completion.SetDone; END;
IF pipe.completionHandlerCaller # NIL THEN pipe.completionHandlerCaller.Call(); END;
END;
END;
pipe := pipe.next;
END;
END NotifyCompletionHandlers;
PROCEDURE GetDefaultPipe*(speed, ttPort, ttAddress : LONGINT; device : Usbdi.UsbDevice) : Pipe;
VAR pipe : Pipe;
BEGIN {EXCLUSIVE}
IF Debug.StrongChecks THEN ASSERT(pipes[0, 0, 0] # NIL); END;
pipe := pipes[0, 0, 0];
pipe.speed := speed;
pipe.device := device;
pipe.completion.device := device;
pipe.ttPort := ttPort;
pipe.ttAddress := ttAddress;
IF isHighSpeed THEN
pipe.maxPacketSize := 64;
ELSE
pipe.maxPacketSize := 8;
END;
IF ~InsertQH(pipe) THEN pipe := NIL; END;
RETURN pipe;
END GetDefaultPipe;
PROCEDURE GetPipe*(deviceAddress, endpointNbr : LONGINT; VAR pipe : Pipe);
VAR testaddr : SYSTEM.ADDRESS;
BEGIN {EXCLUSIVE}
IF Debug.StrongChecks THEN
ASSERT((deviceAddress > 0) & (deviceAddress < 128));
ASSERT((endpointNbr MOD 16 >= 0) & (endpointNbr MOD 16 < 16));
ASSERT((pipe # NIL) & (pipe.direction = In) OR (pipe.direction = Out));
END;
IF pipes[deviceAddress, pipe.direction, endpointNbr MOD 16] # NIL THEN
IF Debug.Level >= Debug.Errors THEN KernelLog.String("UsbHcdi: GetPipe: Pipe already in use."); KernelLog.Ln; END;
pipe := NIL; RETURN;
END;
pipe.tdBufferLen := (TDsPerPipe+1) * 16;
NEW(pipe.tdBuffer, pipe.tdBufferLen);
IF Debug.StrongChecks THEN
testaddr := Machine.PhysicalAdr(SYSTEM.ADR(pipe.tdBuffer[0]), pipe.tdBufferLen);
IF testaddr = Machine.NilAdr THEN
KernelLog.String("UsbHcdi: GetPipe: Allocated buffer not physically contiguous"); KernelLog.Ln;
pipe := NIL; RETURN;
END;
END;
pipe.qh := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, SYSTEM.ADR(pipe.tdBuffer[0]) + 31) * {5..31});
pipe.tdBase := pipe.qh + 32;
ASSERT((pipe.qh >= SYSTEM.ADR(pipe.tdBuffer[0])) & (pipe.tdBase <= SYSTEM.ADR(pipe.tdBuffer[pipe.tdBufferLen-1])));
IF ~InsertQH(pipe) THEN
IF Debug.Level >= Debug.Errors THEN KernelLog.String("UsbHcdi: GetPipe: InsertQH failed."); KernelLog.Ln; END;
pipe := NIL; RETURN;
END;
pipes[deviceAddress, pipe.direction, endpointNbr MOD 16] := pipe;
IF Debug.Trace & Debug.tracePipes THEN
KernelLog.String("UsbHcdi: GetPipe: ");
CASE pipe.type OF
|PipeControl: KernelLog.String("Control");
|PipeBulk: KernelLog.String("Bulk");
|PipeInterrupt: KernelLog.String("Interrupt");
|PipeIsochronous: KernelLog.String("Isochronous");
ELSE
KernelLog.String("Unknown");
END;
IF pipe.direction = In THEN KernelLog.String(" IN Pipe, ");
ELSIF pipe.direction = Out THEN KernelLog.String(" OUT Pipe, ");
ELSE KernelLog.String("Unknown direction Pipe, ");
END;
KernelLog.String(" Adr: "); KernelLog.Int(deviceAddress, 0);
KernelLog.String(" Ep: "); KernelLog.Int(endpointNbr, 0);
KernelLog.String(" established"); KernelLog.Ln;
END;
END GetPipe;
PROCEDURE FreePipe*(pipe : Pipe);
BEGIN {EXCLUSIVE}
FreePipeInternal(pipe);
END FreePipe;
PROCEDURE FreePipeInternal(pipe : Pipe);
BEGIN
IF pipe.address = 0 THEN
IF pipe.qh # 0 THEN RemoveQH(pipe); END;
IF Debug.Trace & Debug.tracePipes THEN KernelLog.String("UsbHcdi: Default pipe at adr 0 freed up."); KernelLog.Ln; END;
ELSIF pipes[pipe.address, pipe.direction, pipe.endpoint MOD 16] # NIL THEN
IF pipe.ioc THEN RemoveCompletionHandler(pipe); END;
IF pipe.completionHandlerCaller # NIL THEN pipe.completionHandlerCaller.Terminate; END;
IF pipe.qh # 0 THEN RemoveQH(pipe); END;
pipes[pipe.address, pipe.direction, pipe.endpoint MOD 16] := NIL;
IF Debug.Trace & Debug.tracePipes THEN
KernelLog.String("UsbHcdi: FreePipe:"); KernelLog.String(" Adr: "); KernelLog.Int(pipe.address, 0);
KernelLog.String(" Ep: "); KernelLog.Int(pipe.endpoint, 0); KernelLog.String(" freed up."); KernelLog.Ln;
END;
ELSE
IF Debug.Level >= Debug.Warnings THEN
KernelLog.String("UsbHcdi: FreePipe: Can't free pipe... pipe not known by host controller ADR: ");
IF pipe # NIL THEN
KernelLog.Int(pipe.address, 0); KernelLog.String(", ep: "); KernelLog.Int(pipe.endpoint, 0);
KernelLog.String(", dir: "); KernelLog.Int(pipe.direction, 0);
ELSE
KernelLog.String("NIL");
END;
KernelLog.Ln;
END;
END;
END FreePipeInternal;
PROCEDURE FreeAll*(adr : LONGINT);
VAR i, j : LONGINT;
BEGIN {EXCLUSIVE}
IF Debug.StrongChecks THEN ASSERT((adr >= 0) & (adr < 128)); END;
IF adr = 0 THEN RETURN END;
IF state = Shutdown THEN RETURN; END;
FOR i := 0 TO 15 DO
FOR j := 0 TO 1 DO
IF pipes[adr, j, i] # NIL THEN
FreePipeInternal(pipes[adr, j , i]);
END;
END;
END;
END FreeAll;
PROCEDURE GetFreeAddress*() : LONGINT;
VAR adr : LONGINT;
BEGIN {EXCLUSIVE}
FOR adr := 1 TO 127 DO
IF adrRange[adr] = FALSE THEN adrRange[adr] := TRUE; RETURN adr; END;
END;
RETURN 0;
END GetFreeAddress;
PROCEDURE FreeAddress*(adr : LONGINT);
BEGIN {EXCLUSIVE}
adrRange[adr] := FALSE;
END FreeAddress;
PROCEDURE Wait*(ms : LONGINT);
BEGIN
timer.Sleep(ms);
END Wait;
PROCEDURE Cleanup*;
BEGIN
SetState(Shutdown); timer.Wakeup;
END Cleanup;
PROCEDURE &Default*(bus, device, funtion : LONGINT);
VAR pipe : Pipe; i : LONGINT;
BEGIN
SELF.bus := bus; SELF.device := device; SELF.function := function;
NEW(timer);
FOR i := 0 TO 127 DO adrRange[i] := FALSE; END;
NEW(pipe, 0, 0, SELF);
pipe.type := PipeControl; pipe.direction := 0;
pipe.maxPacketSize := 8;
pipe.maxRetries := 3; pipe.timeout := 5000;
pipe.tdBufferLen := (TDsDefaultPipe + 2) * 16;
NEW(pipe.tdBuffer, pipe.tdBufferLen);
pipe.qh := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, SYSTEM.ADR(pipe.tdBuffer[0]) + 31) * {5..31});
pipe.tdBase := pipe.qh + 32;
IF Debug.StrongChecks THEN
ASSERT(pipe.tdBase >= SYSTEM.ADR(pipe.tdBuffer[0]));
ASSERT(pipe.tdBase <= SYSTEM.ADR(pipe.tdBuffer[pipe.tdBufferLen-1]));
END;
pipes[0, 0, 0] := pipe;
notifyPipes := pipe;
END Default;
PROCEDURE Diag*;
VAR port : LONGINT; dword : SET;
BEGIN
IF Debug.Trace THEN
KernelLog.String("Diagnostics of "); KernelLog.String(name);
KernelLog.String(" ("); KernelLog.String(desc); KernelLog.String(")");
KernelLog.Ln;
KernelLog.String(" PCI bus "); KernelLog.Int(bus, 0); KernelLog.String(", device "); KernelLog.Int(device, 0);
KernelLog.String(", function "); KernelLog.Int(function, 0);
KernelLog.Ln;
KernelLog.String(" I/O base "); KernelLog.Hex(iobase, 8); KernelLog.String("H, Irq: "); KernelLog.Int (irq, 0);
KernelLog.String(", Int Counter: "); KernelLog.Int(NnofInterrupts, 0);
KernelLog.Ln;
KernelLog.String(" Number of ports: "); KernelLog.Int(portCount, 0); KernelLog.String(", Port status: ");
KernelLog.Ln;
FOR port := 0 TO portCount-1 DO
dword := GetPortStatus(port, FALSE); KernelLog.String(" Port "); KernelLog.Int(port+1, 0); KernelLog.String(": ");
ShowPortStatus(dword); KernelLog.Ln;
END;
END;
END Diag;
END Hcd;
TYPE
TransferCompletion = OBJECT
VAR
done : BOOLEAN;
completiontimeout : BOOLEAN;
timer : Objects.Timer;
device* : Usbdi.UsbDevice;
PROCEDURE SetDone;
BEGIN {EXCLUSIVE}
done := TRUE;
END SetDone;
PROCEDURE SetCompletionTimeout;
BEGIN {EXCLUSIVE}
completiontimeout := TRUE;
END SetCompletionTimeout;
PROCEDURE AwaitDone(timeout : LONGINT) : BOOLEAN;
VAR result : BOOLEAN;
BEGIN {EXCLUSIVE}
Objects.SetTimeout(timer, SELF.SetCompletionTimeout, timeout);
done := FALSE; completiontimeout := FALSE;
AWAIT(done OR completiontimeout OR (device.state = StateDisconnected));
Objects.CancelTimeout(timer);
done := FALSE; result := completiontimeout; completiontimeout := FALSE;
RETURN ~result;
END AwaitDone;
PROCEDURE &New*;
BEGIN
NEW(timer);
END New;
END TransferCompletion;
TYPE
CompletionHandlerCaller = OBJECT
VAR
completionHandler : Usbdi.CompletionHandler;
pipe : Pipe;
alive, dead, doCall : BOOLEAN;
PROCEDURE Call;
BEGIN {EXCLUSIVE}
doCall := TRUE;
END Call;
PROCEDURE Terminate;
BEGIN
BEGIN {EXCLUSIVE} alive := FALSE; END;
BEGIN {EXCLUSIVE} AWAIT(dead); END;
END Terminate;
PROCEDURE &New*(pipe : Pipe; c : Usbdi.CompletionHandler);
BEGIN
ASSERT(c # NIL);
SELF.pipe := pipe;
completionHandler := c;
alive := TRUE; dead := FALSE; doCall := FALSE;
END New;
BEGIN {ACTIVE}
WHILE alive DO
BEGIN {EXCLUSIVE}
AWAIT(doCall OR ~alive);
doCall := FALSE;
END;
IF alive THEN completionHandler(pipe.status, pipe.actLen); END;
END;
BEGIN {EXCLUSIVE} dead := TRUE; END;
END CompletionHandlerCaller;
TYPE
Pipe* = OBJECT(Usbdi.Pipe)
VAR
address* : LONGINT;
endpoint* : LONGINT;
direction* : LONGINT;
controller* : Hcd;
device* : Usbdi.UsbDevice;
ttPort*, ttAddress* : LONGINT;
type* : LONGINT;
irqInterval* : LONGINT;
mult* : LONGINT;
speed* : LONGINT;
dataToggle* : BOOLEAN;
status* : Usbdi.Status ;
errors* : SET;
transferLen* : LONGINT;
actLen* : LONGINT;
sgBuffer : Usbdi.BufferPtr;
physBufferAdr- : SYSTEM.ADDRESS;
sgList- : POINTER TO ARRAY OF Machine.Range;
timeout* : LONGINT;
queue* : Machine.Address32;
qh* : Machine.Address32;
firstTD*, lastTD* : Machine.Address32;
tdBuffer* : POINTER TO ARRAY OF CHAR;
tdBufferLen* : LONGINT;
tdBase* : Machine.Address32;
ioc* : BOOLEAN;
completionHandlerCaller* : CompletionHandlerCaller;
irqActive* : BOOLEAN;
completion- : TransferCompletion;
next* : Pipe;
message : ControlMessage;
PROCEDURE &New*(adr, ep : LONGINT; hcd : Hcd);
BEGIN
NEW(completion);
address := adr; endpoint := ep; controller := hcd; status := Usbdi.InProgress;
IF controller.DMAchaining THEN NEW(sgList, controller.sgListSize); END;
END New;
PROCEDURE NeedSWScatterGather(direction, bufferLen, offset : LONGINT; VAR buffer : Usbdi.Buffer; VAR doCopy : BOOLEAN) : BOOLEAN;
VAR adr : SYSTEM.ADDRESS;
BEGIN
doCopy := FALSE;
IF bufferLen = 0 THEN RETURN FALSE; END;
adr := Machine.PhysicalAdr(SYSTEM.ADR(buffer[offset]), bufferLen);
IF adr = -1 THEN
IF sgBuffer = NIL THEN NEW(sgBuffer, 128*1024); END;
physBufferAdr := SYSTEM.ADR(sgBuffer[0]);
IF direction = In THEN
doCopy := TRUE;
ELSE
ASSERT(bufferLen <= 128*1024);
Copy(buffer, sgBuffer^, offset, 0, bufferLen);
END;
RETURN TRUE;
ELSE
physBufferAdr := adr;
RETURN FALSE;
END;
END NeedSWScatterGather;
PROCEDURE ExecuteTransfer(bufferLen, offset : LONGINT; VAR buffer : Usbdi.Buffer; copy : BOOLEAN) : Usbdi.Status;
VAR mtimer : Kernel.MilliTimer;
BEGIN
IF (controller.state # Operational) OR (device.state = StateDisconnected) THEN
status := Usbdi.Disconnected; errors := Disconnected;
ELSIF status = Usbdi.Error THEN
ELSE
IF mode = Usbdi.MinCpu THEN irqActive := TRUE; END;
controller.LinkTDs(SELF, firstTD);
IF timeout # 0 THEN
IF mode # Usbdi.MinCpu THEN
Kernel.SetTimer(mtimer, timeout);
LOOP
IF (status # Usbdi.InProgress) OR Kernel.Expired(mtimer) OR (device.state = StateDisconnected) THEN EXIT; END;
controller.UpdatePipeStatus(SELF);
IF mode = Usbdi.Normal THEN Objects.Yield; END;
END;
ELSE
IF ~completion.AwaitDone(timeout) THEN
END;
END;
IF Debug.PerformanceMonitoring THEN
INC (controller.NbytesTransfered, actLen);
END;
IF Debug.Trace & Debug.traceControlData & (type = PipeControl) THEN
IF bufferLen > 0 THEN ShowData(buffer); KernelLog.Char(" "); ELSE KernelLog.String("[No Data] "); END;
END;
IF Debug.Trace & (Debug.traceTransfers OR Debug.traceControl) THEN ShowStatus(status); KernelLog.Ln; END;
IF Debug.Trace & (status # Usbdi.Ok) THEN
IF (status = Usbdi.ShortPacket) THEN
IF Debug.traceShortPackets THEN Show(TRUE); KernelLog.Ln; END;
ELSE
IF Debug.traceFailed THEN Show(TRUE); KernelLog.Ln; END;
END;
END;
IF device.state = StateDisconnected THEN
errors := Disconnected; status := Usbdi.Disconnected;
ELSIF status = Usbdi.InProgress THEN
controller.UnlinkTDs(SELF);
ELSIF copy THEN
Copy(sgBuffer^, buffer, 0, offset, actLen);
END;
END;
IF Debug.Stats THEN
Machine.AtomicInc(controller.NnofTransfers);
IF (type = PipeControl) THEN Machine.AtomicInc(controller.NnofControlTransfers)
ELSIF (type = PipeBulk) THEN Machine.AtomicInc(controller.NnofBulkTransfers)
ELSIF (type = PipeInterrupt) THEN Machine.AtomicInc(controller.NnofInterruptTransfers)
ELSIF (type = PipeIsochronous) THEN Machine.AtomicInc(controller.NnofIsochronousTransfers);
ELSE
Machine.AtomicInc(controller.NnofUnknownTransfers);
END;
END;
END;
RETURN status;
END ExecuteTransfer;
PROCEDURE Transfer*(bufferLen, offset : LONGINT; VAR buffer : Usbdi.Buffer) : Usbdi.Status;
VAR copy : BOOLEAN;
BEGIN
ASSERT(type # PipeControl);
ASSERT(LEN(buffer) >= bufferLen + offset);
IF Debug.Trace & Debug.traceTransfers THEN ShowTransfer(bufferLen, offset); END;
transferLen := bufferLen; status := Usbdi.InProgress; errors := NoErrors;
IF controller.LinkTDsAllowed(SELF) THEN
IF ~controller.DMAchaining & NeedSWScatterGather(direction, bufferLen, offset, buffer, copy) THEN
IF timeout = 0 THEN
KernelLog.String("UsbHcdi: Non-blocking transfer to physically non-contiguous buffer not allowed."); KernelLog.Ln;
status := Usbdi.Error; errors := Internal; RETURN status;
END;
controller.Schedule(SELF, bufferLen, 0, sgBuffer^);
ELSE
controller.Schedule(SELF, bufferLen, offset, buffer);
END;
END;
IF Debug.StrongChecks THEN CheckBuffer(bufferLen, offset, buffer); END;
status := ExecuteTransfer(bufferLen, offset, buffer, copy);
RETURN status;
END Transfer;
PROCEDURE CheckBuffer(length, ofs : LONGINT; VAR buffer : Usbdi.Buffer);
VAR i : SYSTEM.ADDRESS;
BEGIN
IF Debug.StrongChecks THEN
ASSERT(tdBuffer # NIL);
i := Machine.PhysicalAdr(SYSTEM.ADR(tdBuffer[0]), tdBufferLen);
IF i = Machine.NilAdr THEN HALT(99); END;
ASSERT((firstTD >= SYSTEM.ADR(tdBuffer[0])) & (firstTD <= SYSTEM.ADR(tdBuffer[tdBufferLen-1])));
ASSERT((lastTD >= SYSTEM.ADR(tdBuffer[0])) & (lastTD <= SYSTEM.ADR(tdBuffer[tdBufferLen-1])));
END;
END CheckBuffer;
PROCEDURE Request*(bmRequestType : SET; bRequest, wValue, wIndex, wLength : LONGINT; VAR buffer : Usbdi.Buffer) : Usbdi. Status;
VAR dir : LONGINT; copy : BOOLEAN;
BEGIN {EXCLUSIVE}
ASSERT(type = PipeControl);
ASSERT(LEN(buffer) >= wLength);
IF message = NIL THEN NEW(message); END;
message[0] := CHR(SYSTEM.VAL(LONGINT, bmRequestType));
message[1] := CHR(bRequest);
message[2] := CHR(wValue);
message[3] := CHR(SYSTEM.LSH(wValue, -8));
message[4] := CHR(wIndex);
message[5] := CHR(SYSTEM.LSH(wIndex,-8));
message[6] := CHR(wLength);
message[7] := CHR(SYSTEM.LSH(wLength, -8));
IF bmRequestType * Usbdi.ToHost # {} THEN dir := In; ELSE dir := Out; END;
IF Debug.Trace & Debug.traceControl THEN ShowMessage(wLength, dir, message); END;
transferLen := wLength; status := Usbdi.InProgress; errors := NoErrors;
IF controller.LinkTDsAllowed(SELF) THEN
IF ~controller.DMAchaining & NeedSWScatterGather(dir, wLength, 0, buffer, copy) THEN
IF timeout = 0 THEN
KernelLog.String("UsbHcdi: Non-blocking transfer to physically non-contiguous buffer not allowed."); KernelLog.Ln;
status := Usbdi.Error; errors := Internal; RETURN status;
END;
controller.ScheduleControl(SELF, dir, message, wLength, sgBuffer^);
ELSE
controller.ScheduleControl(SELF, dir, message, wLength, buffer);
END;
IF Debug.StrongChecks THEN CheckBuffer(wLength, 0, buffer); END;
status := ExecuteTransfer(wLength, 0, buffer, copy);
END;
RETURN status;
END Request;
PROCEDURE ClearHalt*(): BOOLEAN;
CONST FsEndpointHalt = 0; SrClearFeature = 1;
VAR res : BOOLEAN;
BEGIN
IF Debug.Trace & Debug.tracePipes THEN
KernelLog.String("UsbHcdi: Clearhalt Pipe"); KernelLog.String(" Adr: ");KernelLog.Int(address, 0);
KernelLog.String(" Ep: "); KernelLog.Int(endpoint, 0); KernelLog.Ln;
END;
controller.ClearHalt(SELF);
res := device.Request(Usbdi.ToDevice + Usbdi.Standard + Usbdi.Endpoint, SrClearFeature, FsEndpointHalt, endpoint, 0, Usbdi.NoData) = Usbdi.Ok;
IF res THEN dataToggle := FALSE; END;
RETURN res;
END ClearHalt;
PROCEDURE IsHalted*() : BOOLEAN;
CONST SrGetStatus = 0; Halted = {0};
VAR buffer : Usbdi.BufferPtr; status : SET;
BEGIN
IF Debug.Trace & Debug.tracePipes THEN
KernelLog.String("UsbHcdi: Get endpoint status for Adr: "); KernelLog.Int(address, 0);
KernelLog.String(", Ep: "); KernelLog.Int(endpoint, 0); KernelLog.Ln;
END;
NEW(buffer, 2);
IF device.Request(Usbdi.ToHost + Usbdi.Standard + Usbdi.Endpoint, SrGetStatus , 0, endpoint MOD 16, 2, buffer^) = Usbdi.Ok THEN
status := SYSTEM.VAL(SET, ORD(buffer[0]) + ORD(buffer[1])*100H);
RETURN status * Halted # {};
END;
RETURN FALSE;
END IsHalted;
PROCEDURE GetActLen*() : LONGINT;
BEGIN
RETURN actLen;
END GetActLen;
PROCEDURE SetTimeout*(timeout : LONGINT);
BEGIN
SELF.timeout := timeout;
END SetTimeout;
PROCEDURE GetStatus*(VAR len : LONGINT) : Usbdi.Status;
BEGIN
controller.UpdatePipeStatus(SELF); len := actLen;
RETURN status;
END GetStatus;
PROCEDURE SetCompletionHandler*(handler: Usbdi.CompletionHandler);
BEGIN
ioc := TRUE;
mode := Usbdi.MinCpu;
IF completionHandlerCaller # NIL THEN
completionHandlerCaller.Terminate;
END;
NEW(completionHandlerCaller, SELF, handler);
controller.AddCompletionHandler(SELF);
END SetCompletionHandler;
PROCEDURE ShowTransfer(bufferLen, offset : LONGINT);
BEGIN
IF Debug.Trace THEN
KernelLog.String("UsbHcdi: ");
CASE type OF
PipeControl : KernelLog.String("Control Transfer???:");
| PipeBulk : KernelLog.String("Bulk Transfer:");
| PipeInterrupt : KernelLog.String("Interrupt Transfer:");
| PipeIsochronous : KernelLog.String("Isochronous Transfer:");
ELSE KernelLog.String("Unknown transfer type");
END;
KernelLog.String(" Adr: "); KernelLog.Int(address, 0);
KernelLog.String(" Endpoint: "); KernelLog.Int(endpoint, 0);
KernelLog.String(" Length: "); KernelLog.Int(bufferLen, 0); KernelLog.String(" Bytes: ");
END;
END ShowTransfer;
PROCEDURE ShowMessage(bufferLen, direction : LONGINT; msg : ControlMessage);
VAR i : LONGINT;
BEGIN
IF Debug.Trace THEN
KernelLog.String("UsbHcdi: Control Transfer: ");
IF direction = In THEN KernelLog.String("IN");
ELSIF direction = Out THEN KernelLog.String("OUT");
ELSE KernelLog.String("ERROR");
END;
KernelLog.String(" Adr: "); KernelLog.Int(address, 0);
KernelLog.String(" Endpoint: "); KernelLog.Int(endpoint, 0);
KernelLog.String(" Length: "); KernelLog.Int(bufferLen, 0); KernelLog.String(" Bytes: ");
KernelLog.String(" CtrlMsg: "); FOR i := 0 TO 7 DO KernelLog.Hex(ORD(msg[i]), -2); KernelLog.String(" "); END;
END;
END ShowMessage;
PROCEDURE ShowData(CONST buffer : Usbdi.Buffer);
VAR i : LONGINT;
BEGIN
IF Debug.Trace THEN
KernelLog.String("[DATA: ");
FOR i := 0 TO LEN(buffer)-1 DO
KernelLog.Hex(ORD(buffer[i]), -2);
IF i < LEN(buffer)-1 THEN KernelLog.Char(" "); END;
END;
KernelLog.Char("]");
END;
END ShowData;
PROCEDURE Show*(detailed : BOOLEAN);
BEGIN
IF Debug.Trace THEN
CASE type OF
| PipeControl: KernelLog.String(" Control ");
| PipeInterrupt : KernelLog.String(" Interrupt ");
| PipeBulk : KernelLog.String(" Bulk ");
| PipeIsochronous : KernelLog.String(" Isochronous ");
ELSE
KernelLog.String("Unknown("); KernelLog.Int(type, 0); KernelLog.String(") ");
END;
CASE direction OF
| Out: KernelLog.String("OUT");
| In : KernelLog.String("IN");
ELSE
KernelLog.String("IN/OUT");
END;
KernelLog.String(" Pipe:"); KernelLog.String(" Adr: "); KernelLog.Int(address, 0); KernelLog.String(" Ep: "); KernelLog.Int(endpoint, 0);
KernelLog.String(" ");
IF ioc THEN KernelLog.String("[IOC]"); END;
IF completionHandlerCaller # NIL THEN KernelLog.String("[Handler]"); END;
IF irqActive THEN KernelLog.String("[IRQ_ACTIVE]"); END;
IF speed = LowSpeed THEN KernelLog.String("[LowSpeed]");
ELSIF speed = FullSpeed THEN KernelLog.String("[FullSpeed]");
ELSIF speed = HighSpeed THEN KernelLog.String("[HighSpeed]");
ELSE KernelLog.String("[ERROR: Not speed specified]");
END;
KernelLog.Ln;
KernelLog.String(" Queue: "); KernelLog.Hex(queue, 8); KernelLog.String("H");
KernelLog.String(", QH: "); KernelLog.Hex(qh, 8); KernelLog.String("H");
KernelLog.String(", firstTD: "); KernelLog.Hex(firstTD, 8); KernelLog.String("H");
KernelLog.String(", lastTD: "); KernelLog.Hex(lastTD, 8); KernelLog.String("H");
KernelLog.Ln;
IF detailed THEN
IF type = PipeInterrupt THEN KernelLog.String(" IRQ Interval: "); KernelLog.Int(irqInterval, 0); KernelLog.String("ms, "); ELSE KernelLog.String(" "); END;
KernelLog.String("Timeout: "); KernelLog.Int(timeout, 0); KernelLog.String("ms ");
KernelLog.String(", MaxPacketSize: "); KernelLog.Int(maxPacketSize, 0); KernelLog.String(" Bytes");
KernelLog.String(", MaxRetries: "); KernelLog.Int(maxRetries, 0);
KernelLog.String(", Mode: ");
CASE mode OF
|Usbdi.Normal: KernelLog.String("Normal");
|Usbdi.MaxPerformance: KernelLog.String("MaxPerformance");
|Usbdi.MinCpu: KernelLog.String("MinCPU");
ELSE
KernelLog.String("Undefined ("); KernelLog.Int(mode, 0); KernelLog.String(")");
END;
KernelLog.String(", Last status: "); ShowStatus(status);
KernelLog.String(", Last errors: "); ShowErrors(errors); KernelLog.Ln;
IF type # PipeIsochronous THEN
KernelLog.String(" TD buffer: "); KernelLog.Int((LEN(tdBuffer) - (tdBase - SYSTEM.ADR(tdBuffer[0]))) DIV 16 , 0); KernelLog.String(" TD's ");
KernelLog.Ln; KernelLog.Ln;
controller.ShowPipe(SELF);
ELSE
KernelLog.String(" ITD chain: "); KernelLog.Ln;
END;
KernelLog.Ln;
END;
END;
END Show;
END Pipe;
TYPE
Registry= OBJECT(Plugins.Registry) END Registry;
VAR
controllerCount : LONGINT;
controllers- : Registry;
PROCEDURE RegisterHostController*(hcd : Hcd; CONST description : Plugins.Description);
VAR name : Plugins.Name; res : LONGINT;
BEGIN {EXCLUSIVE}
name := "USBHC"; name[5] := CHR(controllerCount + 48); name[6] := 0X;
hcd.SetName(name); hcd.desc := description;
controllers.Add(hcd, res);
IF res # Plugins.Ok THEN
KernelLog.Enter; KernelLog.String("UsbHcdi: Error: Couldn't add host controller to registry."); KernelLog.Exit;
ELSE
INC(controllerCount);
END;
END RegisterHostController;
PROCEDURE UnRegisterHostControllers*(CONST description : Plugins.Description);
VAR table : Plugins.Table; hcd : Hcd; i : LONGINT;
BEGIN {EXCLUSIVE}
controllers.GetAll(table);
IF table # NIL THEN
FOR i := 0 TO LEN(table)-1 DO
hcd := table[i] (Hcd);
IF hcd.desc = description THEN
hcd.Cleanup;
controllers.Remove(hcd);
END;
END;
END;
END UnRegisterHostControllers;
PROCEDURE Copy(VAR from, to: ARRAY OF CHAR; fofs, tofs, len: LONGINT);
BEGIN
IF Debug.Trace & Debug.traceCopying THEN KernelLog.String("UsbHcdi: SG: Copying "); KernelLog.Int(len, 0); KernelLog.String(" Bytes."); KernelLog.Ln; END;
IF len > 0 THEN
ASSERT((fofs+len <= LEN(from)) & (tofs+len <= LEN(to)));
SYSTEM.MOVE(SYSTEM.ADR(from[fofs]), SYSTEM.ADR(to[tofs]), len);
END;
END Copy;
PROCEDURE GetAlignedMemSpace*(size, alignment : LONGINT ) : AlignedMemSpace;
VAR memspace : AlignedMemSpace; temp, i : LONGINT; mask : SET;
BEGIN
ASSERT(alignment >= 4);
NEW(memspace);
NEW(memspace.data, 2*size + alignment);
mask := {}; temp := alignment; i := 0;
LOOP
temp := temp DIV 2;
IF temp = 1 THEN INC(i); EXIT ELSE INC(i)END;
IF (temp MOD 2 # 0) OR (temp = 0) THEN RETURN NIL END;
END;
FOR temp := 0 TO i-1 DO INCL(mask, temp); END;
temp := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, SYSTEM.ADR(memspace.data[0]) + alignment - 1) - mask);
memspace.base := (temp - SYSTEM.ADR(memspace.data[0])) DIV 4;
RETURN memspace;
END GetAlignedMemSpace;
PROCEDURE ShowStatus*(status : Usbdi.Status);
BEGIN
IF Debug.Trace THEN
IF status = Usbdi.Ok THEN KernelLog.String("[Ok]"); END;
IF status = Usbdi.ShortPacket THEN KernelLog.String("[ShortPacket]"); END;
IF status = Usbdi.Stalled THEN KernelLog.String("[Stalled]"); END;
IF status = Usbdi.InProgress THEN KernelLog.String("[InProgress]"); END;
IF status = Usbdi.Error THEN KernelLog.String("[Error]"); END;
IF status = Usbdi.Disconnected THEN KernelLog.String("[Disconnected]"); END;
END;
END ShowStatus;
PROCEDURE ShowErrors*(errors : SET);
BEGIN
IF Debug.Trace THEN
IF errors = NoErrors THEN KernelLog.String("[NoErrors]"); END;
IF errors * ShortPacket # {} THEN KernelLog.String("[ShortPacket]"); END;
IF errors * Stalled # {} THEN KernelLog.String("[Stalled]"); END;
IF errors * InProgress # {} THEN KernelLog.String("[InProgress]"); END;
IF errors * Nak # {} THEN KernelLog.String("[Nak]"); END;
IF errors * Crc # {} THEN KernelLog.String("[Crc]"); END;
IF errors * Timeout # {} THEN KernelLog.String("[Timeout]"); END;
IF errors * CrcTimeout # {} THEN KernelLog.String("[CRC/Timeout]"); END;
IF errors * BitStuff # {} THEN KernelLog.String("[Bitstuff]"); END;
IF errors * Databuffer # {} THEN KernelLog.String("[Databuffer]"); END;
IF errors * Babble # {} THEN KernelLog.String("[Babble]"); END;
IF errors * Internal # {} THEN KernelLog.String("[Internal]"); END;
IF errors * Disconnected # {} THEN KernelLog.String("[Disconnected]"); END;
IF errors * UnexpectedPid # {} THEN KernelLog.String("[UnexpectedPid]"); END;
IF errors * TransferTooLarge # {} THEN KernelLog.String("[TransferTooLarge]"); END;
IF errors * PidCheckFailure # {} THEN KernelLog.String("[PidCheckFailure]"); END;
IF errors * DataToggleMismatch # {} THEN KernelLog.String("[DatatoggleMismatch]"); END;
IF errors * DeviceNotResponding # {} THEN KernelLog.String("[DeviceNotResponding]"); END;
IF errors * LinkTDsFailed # {} THEN KernelLog.String("[TDLinkError]"); END;
IF errors * OutOfTDs # {} THEN KernelLog.String("[OutOfTDs]"); END;
END;
END ShowErrors;
PROCEDURE ShowPortStatus*(status : SET);
BEGIN
IF Debug.Trace THEN
IF status * PortStatusEnabled # {} THEN KernelLog.String("[Enabled]"); ELSE KernelLog.String("[Disabled]"); END;
IF status * PortStatusDevicePresent # {} THEN
IF status * PortStatusLowSpeed # {} THEN KernelLog.String("[LowSpeed]");
ELSIF status * PortStatusFullSpeed # {} THEN KernelLog.String("[FullSpeed]");
ELSIF status * PortStatusHighSpeed # {} THEN KernelLog.String("[HighSpeed]");
ELSE
KernelLog.String("[ERROR:Device connected but no speed indication, port enabled?]");
END;
END;
IF status * PortStatusReset # {} THEN KernelLog.String("[Reset]"); END;
IF status * PortStatusDevicePresent # {} THEN KernelLog.String("[DevicePresent]"); END;
IF status * PortStatusError # {} THEN KernelLog.String("[Error]"); END;
IF status * PortStatusConnectChange # {} THEN KernelLog.String("[ConnectChange]"); END;
IF status * PortStatusSuspended # {} THEN KernelLog.String("[Suspended]"); END;
IF status * PortStatusOverCurrent # {} THEN KernelLog.String("[OverCurrent]"); END;
IF status * PortStatusPowered # {} THEN KernelLog.String("[Powered]"); END;
IF status * PortStatusEnabledChange # {} THEN KernelLog.String("[EnabledChange]"); END;
IF status * PortStatusSuspendChange # {} THEN KernelLog.String("[SuspendChange]"); END;
IF status * PortStatusOverCurrentChange # {} THEN KernelLog.String("[OverCurrentChange]"); END;
IF status * PortStatusWakeOnOvercurrent # {} THEN KernelLog.String("[WakeOnOvercurrent]"); END;
IF status * PortStatusWakeOnDisconnect # {} THEN KernelLog.String("[WakeOnDisconnect]"); END;
IF status * PortStatusWakeOnConnect # {} THEN KernelLog.String("[WakeOnConnect]"); END;
IF status * PortStatusTestControl # {} THEN KernelLog.String("[TestControl]"); END;
IF status * PortStatusIndicatorControl # {} THEN KernelLog.String("[IndicatorControl]"); END;
IF status * PortStatusPortOwner # {} THEN KernelLog.String("[PortOwner]"); END;
END;
END ShowPortStatus;
PROCEDURE Cleanup;
BEGIN
Plugins.main.Remove(controllers);
END Cleanup;
BEGIN
Modules.InstallTermHandler(Cleanup);
NEW(controllers, "UsbHcdi","USB host controller drivers");
END UsbHcdi.