/* 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 if (xfrlen+pktsize <= nbytes) { // Only copy the data to the buffer if the buffer's large enough. 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; } if (xfrlen <= nbytes) { return xfrlen; } else { // Buffer overflow avoided so treat it as an error rather // than return partial data. return -1; } } }//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 }