-
Notifications
You must be signed in to change notification settings - Fork 70
/
Usb.cpp
422 lines (409 loc) · 18.4 KB
/
Usb.cpp
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
/* Copyright 2009-2011 Oleg Mazurov, Circuits At Home, http://www.circuitsathome.com */
/* USB functions */
#include "Usb.h"
static byte usb_error = 0;
static byte usb_task_state;
DEV_RECORD devtable[ USB_NUMDEVICES + 1 ];
EP_RECORD dev0ep; //Endpoint data structure used during enumeration for uninitialized device
/* constructor */
USB::USB () {
usb_task_state = USB_DETACHED_SUBSTATE_INITIALIZE; //set up state machine
init();
}
/* Initialize data structures */
void USB::init()
{
byte i;
for( i = 0; i < ( USB_NUMDEVICES + 1 ); i++ ) {
devtable[ i ].epinfo = NULL; //clear device table
devtable[ i ].devclass = 0;
}
devtable[ 0 ].epinfo = &dev0ep; //set single ep for uninitialized device
// not necessary dev0ep.MaxPktSize = 8; //minimum possible
dev0ep.sndToggle = bmSNDTOG0; //set DATA0/1 toggles to 0
dev0ep.rcvToggle = bmRCVTOG0;
}
byte USB::getUsbTaskState( void )
{
return( usb_task_state );
}
void USB::setUsbTaskState( byte state )
{
usb_task_state = state;
}
EP_RECORD* USB::getDevTableEntry( byte addr, byte ep )
{
EP_RECORD* ptr;
ptr = devtable[ addr ].epinfo;
ptr += ep;
return( ptr );
}
/* set device table entry */
/* each device is different and has different number of endpoints. This function plugs endpoint record structure, defined in application, to devtable */
void USB::setDevTableEntry( byte addr, EP_RECORD* eprecord_ptr )
{
devtable[ addr ].epinfo = eprecord_ptr;
//return();
}
/* Control transfer. Sets address, endpoint, fills control packet with necessary data, dispatches control packet, and initiates bulk IN transfer, */
/* depending on request. Actual requests are defined as inlines */
/* return codes: */
/* 00 = success */
/* 01-0f = non-zero HRSLT */
byte USB::ctrlReq( byte addr, byte ep, byte bmReqType, byte bRequest, byte wValLo, byte wValHi, unsigned int wInd, unsigned int nbytes, char* dataptr, unsigned int nak_limit )
{
boolean direction = false; //request direction, IN or OUT
byte rcode;
SETUP_PKT setup_pkt;
regWr( rPERADDR, addr ); //set peripheral address
if( bmReqType & 0x80 ) {
direction = true; //determine request direction
}
/* fill in setup packet */
setup_pkt.ReqType_u.bmRequestType = bmReqType;
setup_pkt.bRequest = bRequest;
setup_pkt.wVal_u.wValueLo = wValLo;
setup_pkt.wVal_u.wValueHi = wValHi;
setup_pkt.wIndex = wInd;
setup_pkt.wLength = nbytes;
bytesWr( rSUDFIFO, 8, ( char *)&setup_pkt ); //transfer to setup packet FIFO
rcode = dispatchPkt( tokSETUP, ep, nak_limit ); //dispatch packet
//Serial.println("Setup packet"); //DEBUG
if( rcode ) { //return HRSLT if not zero
Serial.print("Setup packet error: ");
Serial.print( rcode, HEX );
return( rcode );
}
//Serial.println( direction, HEX );
if( dataptr != NULL ) { //data stage, if present
rcode = ctrlData( addr, ep, nbytes, dataptr, direction );
}
if( rcode ) { //return error
Serial.print("Data packet error: ");
Serial.print( rcode, HEX );
return( rcode );
}
rcode = ctrlStatus( ep, direction ); //status stage
return( rcode );
}
/* Control transfer with status stage and no data stage */
/* Assumed peripheral address is already set */
byte USB::ctrlStatus( byte ep, boolean direction, unsigned int nak_limit )
{
byte rcode;
if( direction ) { //GET
rcode = dispatchPkt( tokOUTHS, ep, nak_limit );
}
else {
rcode = dispatchPkt( tokINHS, ep, nak_limit );
}
return( rcode );
}
/* Control transfer with data stage. Stages 2 and 3 of control transfer. Assumes preipheral address is set and setup packet has been sent */
byte USB::ctrlData( byte addr, byte ep, unsigned int nbytes, char* dataptr, boolean direction, unsigned int nak_limit )
{
byte rcode;
if( direction ) { //IN transfer
devtable[ addr ].epinfo[ ep ].rcvToggle = bmRCVTOG1;
rcode = inTransfer( addr, ep, nbytes, dataptr, nak_limit );
return( rcode );
}
else { //OUT transfer
devtable[ addr ].epinfo[ ep ].sndToggle = bmSNDTOG1;
rcode = outTransfer( addr, ep, nbytes, dataptr, nak_limit );
return( rcode );
}
}
/* IN transfer to arbitrary endpoint. Assumes PERADDR is set. Handles multiple packets if necessary. Transfers 'nbytes' bytes. */
/* Keep sending INs and writes data to memory area pointed by 'data' */
/* rcode 0 if no errors. rcode 01-0f is relayed from dispatchPkt(). Rcode f0 means RCVDAVIRQ error,
fe USB xfer timeout */
byte USB::inTransfer( byte addr, byte ep, unsigned int nbytes, char* data, unsigned int nak_limit )
{
byte rcode;
byte pktsize;
byte maxpktsize = devtable[ addr ].epinfo[ ep ].MaxPktSize;
unsigned int xfrlen = 0;
regWr( rHCTL, devtable[ addr ].epinfo[ ep ].rcvToggle ); //set toggle value
while( 1 ) { // use a 'return' to exit this loop
rcode = dispatchPkt( tokIN, ep, nak_limit ); //IN packet to EP-'endpoint'. Function takes care of NAKS.
if( rcode ) {
return( rcode ); //should be 0, indicating ACK. Else return error code.
}
/* check for RCVDAVIRQ and generate error if not present */
/* the only case when absense of RCVDAVIRQ makes sense is when toggle error occured. Need to add handling for that */
if(( regRd( rHIRQ ) & bmRCVDAVIRQ ) == 0 ) {
return ( 0xf0 ); //receive error
}
pktsize = regRd( rRCVBC ); //number of received bytes
data = bytesRd( rRCVFIFO, pktsize, data );
regWr( rHIRQ, bmRCVDAVIRQ ); // Clear the IRQ & free the buffer
xfrlen += pktsize; // add this packet's byte count to total transfer length
/* The transfer is complete under two conditions: */
/* 1. The device sent a short packet (L.T. maxPacketSize) */
/* 2. 'nbytes' have been transferred. */
if (( pktsize < maxpktsize ) || (xfrlen >= nbytes )) { // have we transferred 'nbytes' bytes?
if( regRd( rHRSL ) & bmRCVTOGRD ) { //save toggle value
devtable[ addr ].epinfo[ ep ].rcvToggle = bmRCVTOG1;
}
else {
devtable[ addr ].epinfo[ ep ].rcvToggle = bmRCVTOG0;
}
return( 0 );
}
}//while( 1 )
}
/* Google variant of inTransfer. Pasted verbatim from ADK. Returns length instead of error code. Provided for compatibility with Google Open Accessory code */
int USB::newInTransfer( byte addr, byte ep, unsigned int nbytes, char* data, unsigned int nak_limit )
{
byte rcode;
byte pktsize;
byte maxpktsize = devtable[ addr ].epinfo[ ep ].MaxPktSize;
unsigned int xfrlen = 0;
regWr( rHCTL, devtable[ addr ].epinfo[ ep ].rcvToggle ); //set toggle value
while( 1 ) { // use a 'return' to exit this loop
rcode = dispatchPkt( tokIN, ep, nak_limit ); //IN packet to EP-'endpoint'. Function takes care of NAKS.
if( rcode ) {
return -1; //should be 0, indicating ACK. Else return error code.
}
/* check for RCVDAVIRQ and generate error if not present */
/* the only case when absense of RCVDAVIRQ makes sense is when toggle error occured. Need to add handling for that */
if(( regRd( rHIRQ ) & bmRCVDAVIRQ ) == 0 ) {
return -1; //receive error
}
pktsize = regRd( rRCVBC ); //number of received bytes
data = bytesRd( rRCVFIFO, pktsize, data );
regWr( rHIRQ, bmRCVDAVIRQ ); // Clear the IRQ & free the buffer
xfrlen += pktsize; // add this packet's byte count to total transfer length
/* The transfer is complete under two conditions: */
/* 1. The device sent a short packet (L.T. maxPacketSize) */
/* 2. 'nbytes' have been transferred. */
if (( pktsize < maxpktsize ) || (xfrlen >= nbytes )) { // have we transferred 'nbytes' bytes?
if( regRd( rHRSL ) & bmRCVTOGRD ) { //save toggle value
devtable[ addr ].epinfo[ ep ].rcvToggle = bmRCVTOG1;
}
else {
devtable[ addr ].epinfo[ ep ].rcvToggle = bmRCVTOG0;
}
return xfrlen;
}
}//while( 1 )
}
/* OUT transfer to arbitrary endpoint. Assumes PERADDR is set. Handles multiple packets if necessary. Transfers 'nbytes' bytes. */
/* Handles NAK bug per Maxim Application Note 4000 for single buffer transfer */
/* rcode 0 if no errors. rcode 01-0f is relayed from HRSL */
/* major part of this function borrowed from code shared by Richard Ibbotson */
byte USB::outTransfer( byte addr, byte ep, unsigned int nbytes, char* data, unsigned int nak_limit )
{
byte rcode, retry_count;
char* data_p = data; //local copy of the data pointer
unsigned int bytes_tosend, nak_count;
unsigned int bytes_left = nbytes;
byte maxpktsize = devtable[ addr ].epinfo[ ep ].MaxPktSize;
unsigned long timeout = millis() + USB_XFER_TIMEOUT;
if (!maxpktsize) { //todo: move this check close to epinfo init. Make it 1< pktsize <64
return 0xFE;
}
regWr( rHCTL, devtable[ addr ].epinfo[ ep ].sndToggle ); //set toggle value
while( bytes_left ) {
retry_count = 0;
nak_count = 0;
bytes_tosend = ( bytes_left >= maxpktsize ) ? maxpktsize : bytes_left;
bytesWr( rSNDFIFO, bytes_tosend, data_p ); //filling output FIFO
regWr( rSNDBC, bytes_tosend ); //set number of bytes
regWr( rHXFR, ( tokOUT | ep )); //dispatch packet
while(!(regRd( rHIRQ ) & bmHXFRDNIRQ )); //wait for the completion IRQ
regWr( rHIRQ, bmHXFRDNIRQ ); //clear IRQ
rcode = ( regRd( rHRSL ) & 0x0f );
while( rcode && ( timeout > millis())) {
switch( rcode ) {
case hrNAK:
nak_count++;
if( nak_limit && ( nak_count == USB_NAK_LIMIT )) {
return( rcode); //return NAK
}
break;
case hrTIMEOUT:
retry_count++;
if( retry_count == USB_RETRY_LIMIT ) {
return( rcode ); //return TIMEOUT
}
break;
default:
return( rcode );
}//switch( rcode...
/* process NAK according to Host out NAK bug */
regWr( rSNDBC, 0 );
regWr( rSNDFIFO, *data_p );
regWr( rSNDBC, bytes_tosend );
regWr( rHXFR, ( tokOUT | ep )); //dispatch packet
while(!(regRd( rHIRQ ) & bmHXFRDNIRQ )); //wait for the completion IRQ
regWr( rHIRQ, bmHXFRDNIRQ ); //clear IRQ
rcode = ( regRd( rHRSL ) & 0x0f );
}//while( rcode && ....
bytes_left -= bytes_tosend;
data_p += bytes_tosend;
}//while( bytes_left...
devtable[ addr ].epinfo[ ep ].sndToggle = ( regRd( rHRSL ) & bmSNDTOGRD ) ? bmSNDTOG1 : bmSNDTOG0; //update toggle
return( rcode ); //should be 0 in all cases
}
/* dispatch usb packet. Assumes peripheral address is set and relevant buffer is loaded/empty */
/* If NAK, tries to re-send up to nak_limit times */
/* If nak_limit == 0, do not count NAKs, exit after timeout */
/* If bus timeout, re-sends up to USB_RETRY_LIMIT times */
/* return codes 0x00-0x0f are HRSLT( 0x00 being success ), 0xff means timeout */
byte USB::dispatchPkt( byte token, byte ep, unsigned int nak_limit )
{
unsigned long timeout = millis() + USB_XFER_TIMEOUT;
byte tmpdata;
byte rcode;
unsigned int nak_count = 0;
char retry_count = 0;
while( timeout > millis() ) {
regWr( rHXFR, ( token|ep )); //launch the transfer
rcode = 0xff;
while( millis() < timeout ) { //wait for transfer completion
tmpdata = regRd( rHIRQ );
if( tmpdata & bmHXFRDNIRQ ) {
regWr( rHIRQ, bmHXFRDNIRQ ); //clear the interrupt
rcode = 0x00;
break;
}//if( tmpdata & bmHXFRDNIRQ
}//while ( millis() < timeout
if( rcode != 0x00 ) { //exit if timeout
return( rcode );
}
rcode = ( regRd( rHRSL ) & 0x0f ); //analyze transfer result
switch( rcode ) {
case hrNAK:
nak_count ++;
if( nak_limit && ( nak_count == nak_limit )) {
return( rcode );
}
break;
case hrTIMEOUT:
retry_count ++;
if( retry_count == USB_RETRY_LIMIT ) {
return( rcode );
}
break;
default:
return( rcode );
}//switch( rcode
}//while( timeout > millis()
return( rcode );
}
/* USB main task. Performs enumeration/cleanup */
void USB::Task( void ) //USB state machine
{
byte i;
byte rcode;
static byte tmpaddr;
byte tmpdata;
static unsigned long delay = 0;
USB_DEVICE_DESCRIPTOR buf;
/**/
tmpdata = getVbusState();
// Serial.print("vbusState: ");
// Serial.print(tmpdata, HEX);
//
// Serial.print("\n");
/**/
/* modify USB task state if Vbus changed */
switch( tmpdata ) {
case SE1: //illegal state
usb_task_state = USB_DETACHED_SUBSTATE_ILLEGAL;
break;
case SE0: //disconnected
if(( usb_task_state & USB_STATE_MASK ) != USB_STATE_DETACHED ) {
usb_task_state = USB_DETACHED_SUBSTATE_INITIALIZE;
}
break;
case FSHOST: //attached
case LSHOST:
if(( usb_task_state & USB_STATE_MASK ) == USB_STATE_DETACHED ) {
delay = millis() + USB_SETTLE_DELAY;
usb_task_state = USB_ATTACHED_SUBSTATE_SETTLE;
}
break;
}// switch( tmpdata
//Serial.print("USB task state: ");
//Serial.println( usb_task_state, HEX );
switch( usb_task_state ) {
case USB_DETACHED_SUBSTATE_INITIALIZE:
init();
usb_task_state = USB_DETACHED_SUBSTATE_WAIT_FOR_DEVICE;
break;
case USB_DETACHED_SUBSTATE_WAIT_FOR_DEVICE: //just sit here
break;
case USB_DETACHED_SUBSTATE_ILLEGAL: //just sit here
break;
case USB_ATTACHED_SUBSTATE_SETTLE: //setlle time for just attached device
if( delay < millis() ) {
usb_task_state = USB_ATTACHED_SUBSTATE_RESET_DEVICE;
}
break;
case USB_ATTACHED_SUBSTATE_RESET_DEVICE:
regWr( rHCTL, bmBUSRST ); //issue bus reset
usb_task_state = USB_ATTACHED_SUBSTATE_WAIT_RESET_COMPLETE;
break;
case USB_ATTACHED_SUBSTATE_WAIT_RESET_COMPLETE:
if(( regRd( rHCTL ) & bmBUSRST ) == 0 ) {
tmpdata = regRd( rMODE ) | bmSOFKAENAB; //start SOF generation
regWr( rMODE, tmpdata );
// regWr( rMODE, bmSOFKAENAB );
usb_task_state = USB_ATTACHED_SUBSTATE_WAIT_SOF;
delay = millis() + 20; //20ms wait after reset per USB spec
}
break;
case USB_ATTACHED_SUBSTATE_WAIT_SOF: //todo: change check order
if( regRd( rHIRQ ) & bmFRAMEIRQ ) { //when first SOF received we can continue
if( delay < millis() ) { //20ms passed
usb_task_state = USB_ATTACHED_SUBSTATE_GET_DEVICE_DESCRIPTOR_SIZE;
}
}
break;
case USB_ATTACHED_SUBSTATE_GET_DEVICE_DESCRIPTOR_SIZE:
// toggle( BPNT_0 );
devtable[ 0 ].epinfo->MaxPktSize = 8; //set max.packet size to min.allowed
rcode = getDevDescr( 0, 0, 8, ( char* )&buf );
if( rcode == 0 ) {
devtable[ 0 ].epinfo->MaxPktSize = buf.bMaxPacketSize0;
usb_task_state = USB_STATE_ADDRESSING;
}
else {
usb_error = USB_ATTACHED_SUBSTATE_GET_DEVICE_DESCRIPTOR_SIZE;
usb_task_state = USB_STATE_ERROR;
}
break;
case USB_STATE_ADDRESSING:
for( i = 1; i < USB_NUMDEVICES; i++ ) {
if( devtable[ i ].epinfo == NULL ) {
devtable[ i ].epinfo = devtable[ 0 ].epinfo; //set correct MaxPktSize
//temporary record
//until plugged with real device endpoint structure
rcode = setAddr( 0, 0, i );
if( rcode == 0 ) {
tmpaddr = i;
usb_task_state = USB_STATE_CONFIGURING;
}
else {
usb_error = USB_STATE_ADDRESSING; //set address error
usb_task_state = USB_STATE_ERROR;
}
break; //break if address assigned or error occured during address assignment attempt
}
}//for( i = 1; i < USB_NUMDEVICES; i++
if( usb_task_state == USB_STATE_ADDRESSING ) { //no vacant place in devtable
usb_error = 0xfe;
usb_task_state = USB_STATE_ERROR;
}
break;
case USB_STATE_CONFIGURING:
break;
case USB_STATE_RUNNING:
break;
case USB_STATE_ERROR:
break;
}// switch( usb_task_state
}