-
Notifications
You must be signed in to change notification settings - Fork 9
/
usb-fs-host.spin
2795 lines (2210 loc) · 104 KB
/
usb-fs-host.spin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{{
usb-fs-host
------------------------------------------------------------------
This is a software implementation of a simple full-speed
(12 Mb/s) USB 1.1 host controller for the Parallax Propeller.
This module is a self-contained USB stack, including host
controller driver, host controller, and a bit-banging PHY layer.
Software implementations of low-speed (1.5 Mb/s) USB have become
fairly common, but full-speed pushes the limits of even a fairly
powerful microcontroller like the Propeller. So naturally, we
had to cut some corners. See the sizable list of limitations and
caveats below.
The latest version of this file lives at
https://github.com/scanlime/propeller-usb-host
Copyright (c) 2010-2016 M. Elizabeth Scott <[email protected]>
See end of file for terms of use.
Hardware Requirements
---------------------
- 80 MHz Propeller
- USB D- attached to P0 with a 47 ohm series resistor
- USB D+ attached to P1 with a 47 ohm series resistor
- A spare IO pin to leave unconnected
- Pull-down resistors (~47k ohm) from USB D- and D+ to ground
+-------------------+ USB Host (Type A) Socket
| ----------------- |
| [1] [2] [3] [4] | 1: Vbus (+5v) Red
|-------------------| 2: D- White
3: D+ Green
4: GND Black
+5v
| +-----+
2x 47ohm +---| [1] | Vbus
P0 >---/\/--+-----| [2] | D-
P1 >---/\/--|-+---| [3] | D+
| | +-| [4] | GND
| | | +-----+
2x 47k / / |
| | |
- - -
P8 >--- no connection, or watch with oscilloscope :)
Note: For maximum compatibility and at least some semblance of
USB spec compliance, all four of these resistors are
required. And the pull-down resistors are definitely
necessary if you want to detect device connect/disconnect
state. However, if you are permanently attaching a single
device to your Propeller, depending on the device you
may be able to omit the pull-down resistors and connect
D-/D+ directly to P0/P1. I recommend you only try this if
you know what you're doing and/or you're brave :)
Note: You can modify DPLUS and DMINUS below, to change the pins we use
for the USB port. This can only be done at compile-time so far,
since a lot of compile-time literals rely on these values. Also,
not all pins are supported. Both DPLUS and DMINUS must be between
P0 and P7, inclusive.
Limitations and Caveats
-----------------------
- Supports only a single device.
(One host controller, one port, no hubs.)
- Pin numbers are currently hardcoded as P0 and P1.
Clock speed is hardcoded as 80 MHz
- Requires 2 cogs.
(We need a peak of 2 cogs during receive, but one
of them is idle at other times.)
- Maximum transmitted packet size is approx. 430 bytes (NEEDS UPDATE)
Maximum received packet size is approx. 1024 bytes (NEEDS UPDATE)
- Doesn't even pretend to adhere to the USB spec!
May not work with all USB devices.
- Full speed receiver is single-ended and uses a fixed clock rate.
Does not tolerate line noise well, so use short USB
cables if you must use a cable at all.
- Maximum average speed is much less than line rate,
due to time spent pre-encoding and post-decoding.
- SOF packets do not have an incrementing frame number
SOF packets may not be sent on-time due to other traffic
- We don't detect TX buffer overruns. If it hurts,
don't do it. The RX has some protections against overrun,
mostly untested. (Also, do not use this HC with untrusted
devices- a babble condition can overwrite cog memory.)
Theory of Operation
-------------------
With the Propeller overclocked to 96 MHz, we have 8 clock cycles
(2 instructions) for every bit. That isn't nearly enough! So, we
cheat, and we do as much work as possible either before sending
a packet, after receiving a packet, or concurrently on another cog.
One cog is responsible for the bulk of the host controller
implementation. This is the transmit/controller cog. It accepts
commands from Spin code, handles pre-processing transmitted
data and post-processing received data, and oversees the process
of sending and receiving packets.
The grunt work of actually transmitting and receiving data is
offloaded from this cog. Transmit is handled by the TX cog's video
generator hardware. It's programmed in "VGA" mode to send two-byte
"pixels" to the D+/D- pins every 8 clock cycles. We pre-calculate
a buffer of "video" data representing each transmitted packet.
The receiver gets some help from the counters. At 80MHz we have
20 clocks or 5 instructions for every 3 bits. The counter
can store incoming bits by adding them to the PHS register.
The cog must do the shifting. So, 3 of the 5 instructions
are committed to shifting the FRQ register. We can use the other
2 for things like reading and storing the incoming data.
This ratio between clocks and instructions makes it harder to
fill a 32 bit long. We store 30 bits per long to keep things
simple.
A slight complication of using the counter as an input shift
register is that we should add each bit exactly once. The
logic A&B mode works well. A counter in duty mode produces pulses
one clock wide at a configurable frequency and phase, exactly
what we need to sample the input once per bit. We have to use a
pin to route the sampling clock from one counter to another.
We use this pin for some additional functions as well:
* Waking the RX cog with waitpxx to avoid polling hub ram.
* Signals the receiver to start so we don't need to calculate a wakeup time.
* Used as a watchdog timer for the LS receiver.
* Enables faster upload to hub by using timers to increment address.
USBNC pin in FULL speed mode
________----------______||||||||||||||_____|_|_|_|_|_|______
+ Rising edge from TX cog wakes the RX cog
+ RX cog checks hub ram for the current speed
+ Falling edge from TX cog enables receiver code
+ RX cog CTRA generates 12MHz sampling pulses
+ RX cog CTRA generates 5MHz
pulses to advance hub address
USBNC pin in LOW speed mode
________----------_____----------__________|_|_|_|_|_|______
+ Rising edge from TX cog wakes the RX cog
+ RX cog checks hub ram for the current speed
+ Falling edge from TX cog enables receiver code
+ RX cog CTRA is started as watchdog
+ CTRA switched off when EOP received
+ or, watchdog timer expires
+ RX cog CTRA generates 5MHz
pulses to advance hub address
In USB FS receive:
CTRA produces 12 MHz pulses in duty mode.
While receiving LS data:
CTRA is used in NCO mode to act as a watchdog timer for the LS receiver.
While uploading data to hub:
CTRA produces 5 MHz pulses in duty mode.
CTRB adds 4 the hub address for each pulse received.
The low speed receiver was designed with the following goals:
1. Small size
2. Easy to write
3. Wide bitrate tolerance
Low speed devices may use resonators, which are less accurate than crystals.
The USB spec allows 1.5 % tolerance for low speed devices.
Host controllers must be within 0.05 %.
Some code written for USB device receivers may assume this tight tolerence.
To be a reliable host controller, we need to accept the wider 1.5 % range.
This receiver can theoretically tolerate 5.0 % error.
It is possible to receive and un-stuff low speed USB in real time. The codes
that do this are longer and may not have the bitrate tolerance we want.
Also, longer code reduces the size of the full speed receive buffer.
Instead of trying to sample the bits, we measure the time between transitions.
Then we calculate how many bits fit between the transitions.
This post-processing time is roughly equal to the reception time.
The other demanding operation we need to perform is a data IN
transfer. We need to transmit a token, receive a data packet, then
transmit an ACK. All back-to-back, with just a handful of bit
periods between packets. This is handled by some dedicated code
on the TX cog that does nothing but send low-latency ACKs after
the EOP state.
Since it takes much longer to decode and validate a packet than
we have before we must send an ACK, we use an inefficient but
effective "deferred ACK" strategy for IN transfers.
Programming Model
-----------------
This should look a little familiar to anyone who's written USB
drivers for a PC operating system, or used a user-level USB library
like libusb.
Public functions are provided for each supported transfer type.
(BulkRead, BulkWrite, InterruptRead...) These functions take an
endpoint descriptor, hub memory buffer, and buffer size.
All transfers are synchronous. (So, during a transfer itself,
we're really tying up 3 cogs if you count the one you called from.)
All transfers and most other functions can 'abort' with an error code.
See the E_* constants below. You must use the '\' prefix on function
calls if you want to catch these errors.
Since the transfer functions need to know both an endpoint's address
and its maximum packet size, we refer to endpoints by passing around
pointers to the endpoint's descriptor. In fact, this is how we refer
to interfaces too. This object keeps an in-memory copy of the
configuration descriptor we're using, so this data is always handy.
There are high-level functions for iterating over a device's
descriptors.
When a device is attached, call Enumerate to reset and identify it.
After Enumerate, the device will be in the 'addressed' state. It
will not be configured yet, but we'll have a copy of the device
descriptor and the first configuration descriptor in memory. To use
that default configuration, you can immediately call Configure. Now
the device is ready to use.
This host controller is a singleton object which is intended to
be instantiated only once per Propeller. Multiple objects can declare
OBJs for the host controller, but they will all really be sharing the
same instance. This will prevent you from adding multiple USB
controllers to one system, but there are also other reasons that we
don't currently support that. It's convenient, though, because this
means multiple USB device drivers can use separate instances of the
host controller OBJ to speak to the same USB port. Each device driver
can be invoked conditionally based on the device's class(es).
}}
CON
NUM_COGS = 2
' Transmit / Receive Size limits.
'
' Transmit size limit is based on free Cog RAM. It can be increased if we save space
' in the cog by optimizing the code or removing other data. Receive size is limited only
' by available hub ram.
'
' Note that if TX_BUFFER_WORDS is too large the error is detected at compile-time, but
' if RX_BUFFER_WORDS is too large we won't detect the error until Start is running!
TX_BUFFER_WORDS = 185 ' FIXME is this right?
RX_BUFFER_WORDS = 266
' Maximum stored configuration descriptor size, in bytes. If the configuration
' descriptor is longer than this, we'll truncate it. Must be a multiple of 4.
CFGDESC_BUFFER_LEN = 256
' USB data pins.
'
' Important: Both DMINUS and DPLUS must be <= 7, since we
' use pin masks in instruction literals, and we
' assume we're using the first video generator bank.
DMINUS = 1
DPLUS = 0
' This no-connect pin is used for cog to cog and counter to counter signalling.
USBNC = 2
' This module can be very challenging to debug. To make things a little bit easier,
' there are several places where debug pin masks are OR'ed into our DIRA values
' at compile-time. This doesn't use any additional memory. With a logic analyzer,
' you can use this to see exactly when the bus is being driven, and by what code.
'
' To use this, pick some pin(s) to use, put their bit masks here. Attach a pull-up
' resistor and logic analyzer probe to each pin. To disable, set all values to zero.
'
' Since the masks must fit in an instruction's immediate data, you must use P0 through P8.
DEBUG_ACK_MASK = 0
DEBUG_TX_MASK = 0
' Low-level debug flags, settable at runtime
DEBUGFLAG_NO_CRC = $01
' Output bus states
BUS_MASK = (|< DPLUS) | (|< DMINUS)
STATE_J = |< DPLUS
STATE_K = |< DMINUS
STATE_SE0 = 0
STATE_SE1 = BUS_MASK
' Retry options
MAX_TOKEN_RETRIES = 200
MAX_CRC_RETRIES = 200
TIMEOUT_FRAME_DELAY = 10
' Number of CRC error retry attempts
' Offsets in EndpointTable
EPTABLE_SHIFT = 2 ' log2 of entry size
EPTABLE_TOKEN = 0 ' word
EPTABLE_TOGGLE_IN = 2 ' byte
EPTABLE_TOGGLE_OUT = 3 ' byte
' Port connection status codes
PORTC_NO_DEVICE = STATE_SE0 ' No device (pull-down resistors in host)
PORTC_FULL_SPEED = STATE_J ' Full speed: pull-up on D+
PORTC_LOW_SPEED = STATE_K ' Low speed: pull-up on D-
PORTC_INVALID = STATE_SE1 ' Buggy device? Electrical transient?
PORTC_NOT_READY = $FF ' Haven't checked port status yet
' Command opcodes for the controller cog.
OP_NOP = 0 ' Do nothing
OP_RESET = 1 ' Send a USB Reset signal '
OP_TX_BEGIN = 2 ' Start a TX packet. Includes 8-bit PID
OP_TX_END = 3 ' End a TX packet, arg = # of extra idle bits after
OP_TXRX = 4 ' Transmit and/or receive packets
OP_TX_DATA_16 = 5 ' Encode and store a 16-bit word
OP_TX_DATA_PTR = 6 ' Encode data from hub memory.
' Command arg: pointer
' "result" IN: Number of bytes
OP_TX_CRC16 = 7 ' Encode a 16-bit CRC of all data since the PID
OP_RX_PID = 8 ' Decode and return a 16-bit PID word, reset CRC-16
OP_RX_DATA_PTR = 9 ' Decode data to hub memory.
' Command arg: pointer
' "result" IN: Max number of bytes
' result OUT: Actual number of bytes
OP_RX_CRC16 = 10 ' Decode and check CRC. Returns (actual XOR expected)
OP_SOF_WAIT = 11 ' Wait for one SOF to be sent
' OP_TXRX parameters
TXRX_TX_ONLY = %00
TXRX_TX_RX = %01
TXRX_TX_RX_ACK = %11
' USB PID values / commands
PID_OUT = %1110_0001
PID_IN = %0110_1001
PID_SOF = %1010_0101
PID_SETUP = %0010_1101
PID_DATA0 = %1100_0011
PID_DATA1 = %0100_1011
PID_ACK = %1101_0010
PID_NAK = %0101_1010
PID_STALL = %0001_1110
PID_PRE = %0011_1100
' NRZI-decoded representation of a SYNC field, and PIDs which include the SYNC.
' These are the form of values returned by OP_RX_PID.
SYNC_FIELD = %10000000
SYNC_PID_ACK = SYNC_FIELD | (PID_ACK << 8)
SYNC_PID_NAK = SYNC_FIELD | (PID_NAK << 8)
SYNC_PID_STALL = SYNC_FIELD | (PID_STALL << 8)
SYNC_PID_DATA0 = SYNC_FIELD | (PID_DATA0 << 8)
SYNC_PID_DATA1 = SYNC_FIELD | (PID_DATA1 << 8)
' USB Tokens (Device ID + Endpoint) with pre-calculated CRC5 values.
' Since we only support a single USB device, we only need tokens for
' device 0 (the default address) and device 1 (our arbitrary device ID).
' For device 0, we only need endpoint zero. For device 1, we include
' tokens for every possible endpoint.
'
' CRC5 EP# DEV#
TOKEN_DEV0_EP0 = %00010_0000_0000000
TOKEN_DEV1_EP0 = %11101_0000_0000001
TOKEN_DEV1_EP1 = %01011_0001_0000001
TOKEN_DEV1_EP2 = %11000_0010_0000001
TOKEN_DEV1_EP3 = %01110_0011_0000001
TOKEN_DEV1_EP4 = %10111_0100_0000001
TOKEN_DEV1_EP5 = %00001_0101_0000001
TOKEN_DEV1_EP6 = %10010_0110_0000001
TOKEN_DEV1_EP7 = %00100_0111_0000001
TOKEN_DEV1_EP8 = %01001_1000_0000001
TOKEN_DEV1_EP9 = %11111_1001_0000001
TOKEN_DEV1_EP10 = %01100_1010_0000001
TOKEN_DEV1_EP11 = %11010_1011_0000001
TOKEN_DEV1_EP12 = %00011_1100_0000001
TOKEN_DEV1_EP13 = %10101_1101_0000001
TOKEN_DEV1_EP14 = %00110_1110_0000001
TOKEN_DEV1_EP15 = %10000_1111_0000001
' Standard device requests.
'
' This encodes the first two bytes of the SETUP packet into
' one word-sized command. The low byte is bmRequestType,
' the high byte is bRequest.
REQ_CLEAR_DEVICE_FEATURE = $0100
REQ_CLEAR_INTERFACE_FEATURE = $0101
REQ_CLEAR_ENDPOINT_FEATURE = $0102
REQ_GET_CONFIGURATION = $0880
REQ_GET_DESCRIPTOR = $0680
REQ_GET_INTERFACE = $0a81
REQ_GET_DEVICE_STATUS = $0000
REQ_GET_INTERFACE_STATUS = $0001
REQ_GET_ENDPOINT_STATUS = $0002
REQ_SET_ADDRESS = $0500
REQ_SET_CONFIGURATION = $0900
REQ_SET_DESCRIPTOR = $0700
REQ_SET_DEVICE_FEATURE = $0300
REQ_SET_INTERFACE_FEATURE = $0301
REQ_SET_ENDPOINT_FEATURE = $0302
REQ_SET_INTERFACE = $0b01
REQ_SYNCH_FRAME = $0c82
' Standard descriptor types.
'
' These identify a descriptor in REQ_GET_DESCRIPTOR,
' via the high byte of wValue. (wIndex is the language ID.)
'
' The 'DESCHDR' variants are the full descriptor header,
' including type and length. This matches the first two bytes
' of any such static-length descriptor.
DESC_DEVICE = $0100
DESC_CONFIGURATION = $0200
DESC_STRING = $0300
DESC_INTERFACE = $0400
DESC_ENDPOINT = $0500
DESCHDR_DEVICE = $01_12
DESCHDR_CONFIGURATION = $02_09
DESCHDR_INTERFACE = $04_09
DESCHDR_ENDPOINT = $05_07
' Descriptor Formats
DEVDESC_bLength = 0
DEVDESC_bDescriptorType = 1
DEVDESC_bcdUSB = 2
DEVDESC_bDeviceClass = 4
DEVDESC_bDeviceSubClass = 5
DEVDESC_bDeviceProtocol = 6
DEVDESC_bMaxPacketSize0 = 7
DEVDESC_idVendor = 8
DEVDESC_idProduct = 10
DEVDESC_bcdDevice = 12
DEVDESC_iManufacturer = 14
DEVDESC_iProduct = 15
DEVDESC_iSerialNumber = 16
DEVDESC_bNumConfigurations = 17
DEVDESC_LEN = 18
CFGDESC_bLength = 0
CFGDESC_bDescriptorType = 1
CFGDESC_wTotalLength = 2
CFGDESC_bNumInterfaces = 4
CFGDESC_bConfigurationValue = 5
CFGDESC_iConfiguration = 6
CFGDESC_bmAttributes = 7
CFGDESC_MaxPower = 8
IFDESC_bLength = 0
IFDESC_bDescriptorType = 1
IFDESC_bInterfaceNumber = 2
IFDESC_bAlternateSetting = 3
IFDESC_bNumEndpoints = 4
IFDESC_bInterfaceClass = 5
IFDESC_bInterfaceSubClass = 6
IFDESC_bInterfaceProtocol = 7
IFDESC_iInterface = 8
EPDESC_bLength = 0
EPDESC_bDescriptorType = 1
EPDESC_bEndpointAddress = 2
EPDESC_bmAttributes = 3
EPDESC_wMaxPacketSize = 4
EPDESC_bInterval = 6
' SETUP packet format
SETUP_bmRequestType = 0
SETUP_bRequest = 1
SETUP_wValue = 2
SETUP_wIndex = 4
SETUP_wLength = 6
SETUP_LEN = 8
' Endpoint constants
DIR_IN = $80
DIR_OUT = $00
TT_CONTROL = $00
TT_ISOC = $01
TT_BULK = $02
TT_INTERRUPT = $03
' Negative error codes. Most functions in this library can call
' "abort" with one of these codes.
'
' So that multiple levels of the software stack can share
' error codes, we define a few reserved ranges:
'
' -1 to -99 : Application
' -100 to -150 : Device or device class driver
' -150 to -199 : USB host controller
'
' Within the USB host controller range:
'
' -150 to -159 : Device connectivity errors
' -160 to -179 : Low-level transfer errors
' -180 to -199 : High-level errors (parsing, resource exhaustion)
'
' When adding new errors, please keep existing errors constant
' to avoid breaking other modules who may depend on these values.
' (But if you're writing another module that uses these values,
' please use the constants from this object rather than hardcoding
' them!)
E_SUCCESS = 0
E_NO_DEVICE = -150 ' No device is attached
E_LOW_SPEED = -151 ' Low-speed devices are not supported
E_PORT_BOUNCE = -152 ' Port connection state changing during Enumerate
E_TIMEOUT = -160 ' Timed out waiting for a response
E_TRANSFER = -161 ' Generic low-level transfer error
E_CRC = -162 ' CRC-16 mismatch and/or babble condition
E_TOGGLE = -163 ' DATA0/1 toggle error
E_PID = -164 ' Invalid or malformed PID and/or no response
E_STALL = -165 ' USB STALL response (pipe error)
E_DEV_ADDRESS = -170 ' Enumeration error: Device addressing
E_READ_DD_1 = -171 ' Enumeration error: First device descriptor read
E_READ_DD_2 = -172 ' Enumeration error: Second device descriptor read
E_READ_CONFIG = -173 ' Enumeration error: Config descriptor read
E_OUT_OF_COGS = -180 ' Not enough free cogs, can't initialize
E_OUT_OF_MEM = -181 ' Not enough space for the requested buffer sizes
E_DESC_PARSE = -182 ' Can't parse a USB descriptor
DAT
' This is a singleton object, so we use DAT for all variables.
' Note that, unlike VARs, these won't be sorted automatically.
' Keep variables of the same type together.
txc_command long -1 ' Command buffer: [23:16]=arg, [15:0]=code ptr
rx1_time long -1 ' Trigger time for RX1 cog
'rx2_time long -1 ' Trigger time for RX2 cog
rx2_sop long -1 ' Start of packet, calculated by RX2
txc_result long 0
heap_top word 0 ' Top of recycled memory heap
buf_dd word 0 ' Device descriptor buffer pointer
buf_cfg word 0 ' Configuration descriptor buffer pointer
buf_setup word 0 ' SETUP packet buffer pointer
last_pid_err word 0 ' Details from the last E_PID error
isRunning byte 0
portc byte PORTC_NOT_READY ' Port connection status
rxdone byte $FF
debugFlags byte 0
DAT
''
''
''==============================================================================
'' Host Controller Setup
''==============================================================================
PUB Start
'' Starts the software USB host controller, if it isn't already running.
'' Requires 2 free cogs. May abort if there aren't enough free cogs, or
'' if we run out of recycled memory while allocating buffers.
''
'' This function typically doesn't need to be invoked explicitly. It will
'' be called automatically by GetPortConnection and Enumerate.
if isRunning
return
heap_top := @heap_begin
buf_dd := alloc(DEVDESC_LEN)
buf_cfg := alloc(CFGDESC_BUFFER_LEN)
buf_setup := alloc(SETUP_LEN)
' Set up pre-cognew parameters
sof_deadline := cnt
rx1p_portc := txp_portc := @portc
txp_result := @txc_result
txp_rx1_time := @rx1_time
' txp_rx2_time := @rx2_time
' rx2p_sop := @rx2_sop
rx1p_sop := @rx2_sop
' txp_rxdone := rx1p_done := rx2p_done := @rxdone
' txp_rxbuffer := rx1p_buffer := rx2p_buffer := alloc(constant(RX_BUFFER_WORDS * 4))
txp_rxdone := rx1p_done := @rxdone
txp_rxbuffer := rx1p_buffer := alloc(constant(RX_BUFFER_WORDS * 4))
' if cognew(@controller_cog, @txc_command)<0 or cognew(@rx_cog_1, @rx1_time)<0 or cognew(@rx_cog_2, @rx2_time)<0
if cognew(@controller_cog, @txc_command)<0 or cognew(@rx_cog_1, @rx1_time)<0
abort E_OUT_OF_COGS
' Before we start scribbling over the memory we allocated above, wait for all cogs to start.
repeat while txc_result or rx1_time
isRunning~~
PRI alloc(bytes) : ptr
' Since this object can only be instantiated once, we have no need for the
' cog data in hub memory once we've started our cogs. Repurpose this as buffer
' space.
ptr := heap_top := (heap_top + 3) & !3
heap_top += bytes
if heap_top > @heap_end
abort E_OUT_OF_MEM
PUB FrameWait(count)
'' Wait for the controller to send 'count' Start Of Frame tokens.
'' If one SOF has been emitted since the last call to FrameWait, it may
'' count as the first in 'count'.
repeat count
Command(OP_SOF_WAIT, 0)
Sync
PUB SetDebugFlags(flags)
'' Set low-level debug flags.
'' 'flags' should be a combination of DEBUGFLAG_* constants.
debugFlags := flags
DAT
''
''==============================================================================
'' High-level Device Framework
''==============================================================================
PUB GetPortConnection
'' Is a device connected? If so, what speed? Returns a PORTC_* constant.
'' Starts the host controller if it isn't already running.
Start
repeat while portc == PORTC_NOT_READY
return portc
PUB Enumerate | pc
'' Initialize the attached USB device, and get information about it.
''
'' 1. Reset the device
'' 2. Assign it an address
'' 3. Read the device descriptor
'' 4. Read the first configuration descriptor
''
'' Starts the host controller if it isn't already running.
' Port debounce: Make sure the device is in the
' same connection state for a couple frames.
pc := GetPortConnection
FrameWait(3)
if GetPortConnection <> pc
abort E_PORT_BOUNCE
case pc
PORTC_NO_DEVICE, PORTC_INVALID:
abort E_NO_DEVICE
'PORTC_LOW_SPEED:
' abort E_LOW_SPEED
' Device reset, and give it some time to wake up
DeviceReset
FrameWait(10)
DefaultMaxPacketSize0
if 0 > \DeviceAddress
abort E_DEV_ADDRESS
' Read the real max packet length (Must request exactly 8 bytes)
if 0 > \ControlRead(REQ_GET_DESCRIPTOR, DESC_DEVICE, 0, buf_dd, 8)
abort E_READ_DD_1
' Validate device descriptor header
if WORD[buf_dd] <> DESCHDR_DEVICE
abort E_DESC_PARSE
' Read the whole descriptor
if 0 > \ControlRead(REQ_GET_DESCRIPTOR, DESC_DEVICE, 0, buf_dd, DEVDESC_LEN)
abort E_READ_DD_2
ReadConfiguration(0)
PUB DefaultMaxPacketSize0
' Before we can do any transfers longer than 8 bytes, we need to know the maximum
' packet size on EP0. Otherwise we won't be able to determine when a transfer has
' ended. So, we'll use a temporary maximum packet size of 8 in order to address the
' device and to receive the first 8 bytes of the device descriptor. This should
' always be possible using transfers of no more than one packet in length.
BYTE[buf_dd + DEVDESC_bMaxPacketSize0] := 8
PUB Configure
'' Switch device configurations. This (re)configures the device according to
'' the currently loaded configuration descriptor. To use a non-default configuration,
'' call ReadConfiguration() to load a different descriptor first.
ResetEndpointToggle
Control(REQ_SET_CONFIGURATION, BYTE[buf_cfg + CFGDESC_bConfigurationValue], 0)
PUB UnConfigure
'' Place the device back in its un-configured state.
'' In the unconfigured state, only the default control endpoint may be used.
Control(REQ_SET_CONFIGURATION, 0, 0)
PUB ReadConfiguration(index)
'' Read in a configuration descriptor from the device. Most devices have only one
'' configuration, and we load it automatically in Enumerate. So you usually don't
'' need to call this function. But if the device has multiple configurations, you
'' can use this to get information about them all.
''
'' This does not actually switch configurations. If this newly read configuration
'' is indeed the one you want to use, call Configure.
if 0 > \ControlRead(REQ_GET_DESCRIPTOR, DESC_CONFIGURATION | index, 0, buf_cfg, CFGDESC_BUFFER_LEN)
abort E_READ_CONFIG
if WORD[buf_cfg] <> DESCHDR_CONFIGURATION
abort E_DESC_PARSE
PUB DeviceDescriptor : ptr
'' Get a pointer to the enumerated device's Device Descriptor
return buf_dd
PUB ConfigDescriptor : ptr
'' Get a pointer to the last config descriptor read with ReadConfiguration().
'' If the configuration was longer than CFGDESC_BUFFER_LEN, it will be truncated.
return buf_cfg
PUB VendorID : devID
'' Get the enumerated device's 16-bit Vendor ID
return WORD[buf_dd + DEVDESC_idVendor]
PUB ProductID : devID
'' Get the enumerated device's 16-bit Product ID
return WORD[buf_dd + DEVDESC_idProduct]
PUB ClearHalt(epd)
'' Clear a Halt condition on one endpoint, given a pointer to the endpoint descriptor
Control(REQ_CLEAR_ENDPOINT_FEATURE, 0, BYTE[epd + EPDESC_bEndpointAddress])
DAT
''
''==============================================================================
'' Configuration Descriptor Parsing
''==============================================================================
PUB NextDescriptor(ptrIn) : ptrOut | endPtr
'' Advance to the next descriptor within the configuration descriptor.
'' If there is another descriptor, returns a pointer to it. If we're at
'' the end of the descriptor or the buffer, returns 0.
ptrOut := ptrIn + BYTE[ptrIn]
endPtr := buf_cfg + (WORD[buf_cfg + CFGDESC_wTotalLength] <# CFGDESC_BUFFER_LEN)
if ptrOut => endPtr
ptrOut~
PUB NextHeaderMatch(ptrIn, header) : ptrOut
'' Advance to the next descriptor which matches the specified header.
repeat while ptrIn := NextDescriptor(ptrIn)
if UWORD(ptrIn) == header
return ptrIn
return 0
PUB FirstInterface : firstIf
'' Return a pointer to the first interface in the current config
'' descriptor. If there were no valid interfaces, returns 0.
return NextInterface(buf_cfg)
PUB NextInterface(curIf) : nextIf
'' Advance to the next interface after 'curIf' in the current
'' configuration descriptor. If there are no more interfaces, returns 0.
return NextHeaderMatch(curIf, DESCHDR_INTERFACE)
PUB NextEndpoint(curIf) : nextIf
'' Advance to the next endpoint after 'curIf' in the current
'' configuration descriptor. To get the first endpoint in an interface,
'' pass in a pointer to the interface descriptor.
''
'' If there are no more endpoints in this interface, returns 0.
repeat while curIf := NextDescriptor(curIf)
case UWORD(curIf)
DESCHDR_ENDPOINT:
return curIf
DESCHDR_INTERFACE:
return 0
return 0
PUB FindInterface(class) : foundIf
'' Look for the first interface which has the specified class.
'' If no such interface exists on the current configuration, returns 0.
foundIf := FirstInterface
repeat while foundIf
if BYTE[foundIf + IFDESC_bInterfaceClass] == class
return foundIf
foundIf := NextInterface(foundIf)
PUB EndpointDirection(epd)
'' Given an endpoint descriptor pointer, test the endpoint direction.
'' (DIR_IN or DIR_OUT)
return BYTE[epd + EPDESC_bEndpointAddress] & $80
PUB EndpointType(epd)
'' Return an endpoint's transfer type (TT_BULK, TT_ISOC, TT_INTERRUPT)
return BYTE[epd + EPDESC_bmAttributes] & $03
PUB UWORD(addr) : value
'' Like WORD[addr], but works on unaligned addresses too.
'' You must use this rather than WORD[] when reading 16-bit values
'' from descriptors, since descriptors have no alignment guarantees.
return BYTE[addr] | (BYTE[addr + 1] << 8)
DAT
''
''==============================================================================
'' Device Setup
''==============================================================================
PUB DeviceReset
'' Asynchronously send a USB bus reset signal.
Command(OP_RESET, 0)
ResetEndpointToggle
PUB DeviceAddress | buf
'' Send a SET_ADDRESS(1) to device 0.
''
'' This should be sent after DeviceReset to transition the
'' device from the Default state to the Addressed state. All
'' other transfers here assume the device address is 1.
WORD[buf_setup] := REQ_SET_ADDRESS
WORD[buf_setup + SETUP_wValue] := 1
LONG[buf_setup + SETUP_wIndex]~
ControlRaw(TOKEN_DEV0_EP0, @buf, 4)
DAT
''
''==============================================================================
'' Control Transfers
''==============================================================================
PUB Control(req, value, index) | buf
'' Issue a no-data control transfer to an addressed device.
WORD[buf_setup] := req
WORD[buf_setup + SETUP_wValue] := value
WORD[buf_setup + SETUP_wIndex] := index
WORD[buf_setup + SETUP_wLength]~
return ControlRaw(TOKEN_DEV1_EP0, @buf, 4)
PUB ControlRead(req, value, index, bufferPtr, length) | toggle
'' Issue a control IN transfer to an addressed device.
''
'' Returns the number of bytes read.
'' Aborts on error.
WORD[buf_setup] := req
WORD[buf_setup + SETUP_wValue] := value
WORD[buf_setup + SETUP_wIndex] := index
WORD[buf_setup + SETUP_wLength] := length
' Issues SETUP and IN transactions
result := ControlRaw(TOKEN_DEV1_EP0, bufferPtr, length)
' Status phase (OUT + DATA1)
toggle := PID_DATA1
WriteData(PID_OUT, TOKEN_DEV1_EP0, 0, 0, @toggle, MAX_TOKEN_RETRIES)
PUB ControlWrite(req, value, index, bufferPtr, length) | toggle, pktSize0, packetSize
'' Issue a control OUT transfer to an addressed device.
WORD[buf_setup] := req
WORD[buf_setup + SETUP_wValue] := value
WORD[buf_setup + SETUP_wIndex] := index
WORD[buf_setup + SETUP_wLength] := length
toggle := PID_DATA0
WriteData(PID_SETUP, TOKEN_DEV1_EP0, buf_setup, 8, @toggle, MAX_TOKEN_RETRIES)
' Break OUT data into multiple packets if necessary
pktSize0 := BYTE[buf_dd + DEVDESC_bMaxPacketSize0]
repeat
packetSize := length <# pktSize0
WriteData(PID_OUT, TOKEN_DEV1_EP0, bufferPtr, packetSize, @toggle, MAX_TOKEN_RETRIES)
bufferPtr += packetSize
if (length -= packetSize) =< 0
' Status stage (always DATA1)
toggle := PID_DATA1
return DataIN(TOKEN_DEV1_EP0, @packetSize, 4, pktSize0, @toggle, TXRX_TX_RX_ACK, MAX_TOKEN_RETRIES, 1)
PUB ControlRaw(token, buffer, length) | toggle
' Common low-level implementation of no-data and read control transfers.
toggle := PID_DATA0
WriteData(PID_SETUP, token, buf_setup, 8, @toggle, MAX_TOKEN_RETRIES)
return DataIN(token, buffer, length, BYTE[buf_dd + DEVDESC_bMaxPacketSize0], @toggle, TXRX_TX_RX_ACK, MAX_TOKEN_RETRIES, 1)
PUB SetupBuffer
return buf_setup
DAT
''
''==============================================================================
'' Interrupt Transfers
''==============================================================================
PUB InterruptRead(epd, buffer, length) : actual | epTable
'' Try to read one packet, up to 'length' bytes, from an Interrupt IN endpoint.
'' Returns the actual amount of data read.
'' If no data is available, raises E_TIMEOUT without waiting.
''
'' 'epd' is a pointer to this endpoint's Endpoint Descriptor.
' This is different from Bulk in two main ways:
'
' - We give DataIN an artificially large maxPacketSize, since we
' never want it to receive more than one packet at a time here.
' - We give it a retry of 0, since we don't want to retry on NAK.
epTable := EndpointTableAddr(epd)
return DataIN(WORD[epTable], buffer, length, $1000, epTable + EPTABLE_TOGGLE_IN, TXRX_TX_RX, 0, MAX_CRC_RETRIES)
DAT
''
''==============================================================================
'' Bulk Transfers
''==============================================================================
PUB BulkWrite(epd, buffer, length) | packetSize, epTable, maxPacketSize
'' Write 'length' bytes of data to a Bulk OUT endpoint.
''
'' Always writes at least one packet. If 'length' is zero,
'' we send a zero-length packet. If 'length' is any other
'' even multiple of maxPacketLen, we send only maximally-long
'' packets and no zero-length packet.
''
'' 'epd' is a pointer to this endpoint's Endpoint Descriptor.