/* Copyright (c) 2010, Peter Barrett ** ** Permission to use, copy, modify, and/or distribute this software for ** any purpose with or without fee is hereby granted, provided that the ** above copyright notice and this permission notice appear in all copies. ** ** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL ** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED ** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR ** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES ** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, ** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS ** SOFTWARE. */ /* Copyright (c) 2014 NicoHood See the readme for credit to other people. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "USBAPI.h" #if defined(USBCON) #define EP_TYPE_CONTROL 0x00 #define EP_TYPE_BULK_IN 0x81 #define EP_TYPE_BULK_OUT 0x80 #define EP_TYPE_INTERRUPT_IN 0xC1 #define EP_TYPE_INTERRUPT_OUT 0xC0 #define EP_TYPE_ISOCHRONOUS_IN 0x41 #define EP_TYPE_ISOCHRONOUS_OUT 0x40 /** Pulse generation counters to keep track of the number of milliseconds remaining for each pulse type */ #define TX_RX_LED_PULSE_MS 100 volatile u8 TxLEDPulse; /**< Milliseconds remaining for data Tx LED pulse */ volatile u8 RxLEDPulse; /**< Milliseconds remaining for data Rx LED pulse */ //================================================================== //================================================================== extern const u16 STRING_LANGUAGE[] PROGMEM; extern const u8 STRING_PRODUCT[] PROGMEM; extern const u8 STRING_MANUFACTURER[] PROGMEM; extern const DeviceDescriptor USB_DeviceDescriptor PROGMEM; extern const DeviceDescriptor USB_DeviceDescriptorA PROGMEM; const u16 STRING_LANGUAGE[2] = { (3 << 8) | (2 + 2), 0x0409 // English }; #ifndef USB_PRODUCT // If no product is provided, use USB IO Board #define USB_PRODUCT "USB IO Board" #endif const u8 STRING_PRODUCT[] PROGMEM = USB_PRODUCT; #if USB_VID == 0x2341 # if defined(USB_MANUFACTURER) # undef USB_MANUFACTURER # endif # define USB_MANUFACTURER "Arduino LLC" #elif USB_VID == 0x1b4f # if defined(USB_MANUFACTURER) # undef USB_MANUFACTURER # endif # define USB_MANUFACTURER "SparkFun" #elif !defined(USB_MANUFACTURER) // Fall through to unknown if no manufacturer name was provided in a macro # define USB_MANUFACTURER "Unknown" #endif const u8 STRING_MANUFACTURER[] PROGMEM = USB_MANUFACTURER; #ifdef CDC_ENABLED #define DEVICE_CLASS 0x02 #else #define DEVICE_CLASS 0x00 #endif //TODO warning from USBlyzer with a 16u2 // DEVICE DESCRIPTOR const DeviceDescriptor USB_DeviceDescriptor = D_DEVICE(0x00, 0x00, 0x00, 64, USB_VID, USB_PID, 0x100, IMANUFACTURER, IPRODUCT, 0, 1); const DeviceDescriptor USB_DeviceDescriptorA = D_DEVICE(DEVICE_CLASS, 0x00, 0x00, 64, USB_VID, USB_PID, 0x100, IMANUFACTURER, IPRODUCT, 0, 1); //================================================================== //================================================================== volatile u8 _usbConfiguration = 0; static inline void WaitIN(void) { while (!(UEINTX & (1 << TXINI))) ; } static inline void ClearIN(void) { UEINTX = ~(1 << TXINI); } static inline void WaitOUT(void) { while (!(UEINTX & (1 << RXOUTI))) ; } static inline u8 WaitForINOrOUT() { while (!(UEINTX & ((1 << TXINI) | (1 << RXOUTI)))) ; return (UEINTX & (1 << RXOUTI)) == 0; } static inline void ClearOUT(void) { UEINTX = ~(1 << RXOUTI); } void Recv(volatile u8* data, u8 count) { while (count--) *data++ = UEDATX; RXLED1; // light the RX LED RxLEDPulse = TX_RX_LED_PULSE_MS; } static inline u8 Recv8() { RXLED1; // light the RX LED RxLEDPulse = TX_RX_LED_PULSE_MS; return UEDATX; } static inline void Send8(u8 d) { UEDATX = d; } static inline void SetEP(u8 ep) { UENUM = ep; } static inline u8 FifoByteCount() { return UEBCLX; } static inline u8 ReceivedSetupInt() { return UEINTX & (1 << RXSTPI); } static inline void ClearSetupInt() { UEINTX = ~((1 << RXSTPI) | (1 << RXOUTI) | (1 << TXINI)); } static inline void Stall() { UECONX = (1 << STALLRQ) | (1 << EPEN); } static inline u8 ReadWriteAllowed() { return UEINTX & (1 << RWAL); } static inline u8 Stalled() { return UEINTX & (1 << STALLEDI); } static inline u8 FifoFree() { return UEINTX & (1 << FIFOCON); } static inline void ReleaseRX() { UEINTX = 0x6B; // FIFOCON=0 NAKINI=1 RWAL=1 NAKOUTI=0 RXSTPI=1 RXOUTI=0 STALLEDI=1 TXINI=1 } static inline void ReleaseTX() { UEINTX = 0x3A; // FIFOCON=0 NAKINI=0 RWAL=1 NAKOUTI=1 RXSTPI=1 RXOUTI=0 STALLEDI=1 TXINI=0 } static inline u8 FrameNumber() { return UDFNUML; } //================================================================== //================================================================== u8 USBGetConfiguration(void) { return _usbConfiguration; } #define USB_RECV_TIMEOUT class LockEP { u8 _sreg; public: LockEP(u8 ep) : _sreg(SREG) { cli(); SetEP(ep & 7); } ~LockEP() { SREG = _sreg; } }; // Number of bytes, assumes a rx endpoint u8 USB_Available(u8 ep) { LockEP lock(ep); return FifoByteCount(); } // Non Blocking receive // Return number of bytes read int USB_Recv(u8 ep, void* d, int len) { if (!_usbConfiguration || len < 0) return -1; LockEP lock(ep); u8 n = FifoByteCount(); len = min(n, len); n = len; u8* dst = (u8*)d; while (n--) *dst++ = Recv8(); if (len && !FifoByteCount()) // release empty buffer ReleaseRX(); return len; } // Recv 1 byte if ready int USB_Recv(u8 ep) { u8 c; if (USB_Recv(ep, &c, 1) != 1) return -1; return c; } // Space in send EP u8 USB_SendSpace(u8 ep) { LockEP lock(ep); if (!ReadWriteAllowed()) return 0; // edit by NicoHood return USB_EP_SIZE - FifoByteCount(); } // Blocking Send of data to an endpoint int USB_Send(u8 ep, const void* d, int len) { if (!_usbConfiguration) return -1; int r = len; const u8* data = (const u8*)d; u8 timeout = 250; // 250ms timeout on send? TODO while (len) { u8 n = USB_SendSpace(ep); if (n == 0) { if (!(--timeout)) return -1; delay(1); continue; } if (n > len) n = len; { LockEP lock(ep); // Frame may have been released by the SOF interrupt handler if (!ReadWriteAllowed()) continue; len -= n; if (ep & TRANSFER_ZERO) { while (n--) Send8(0); } else if (ep & TRANSFER_PGM) { while (n--) Send8(pgm_read_byte(data++)); } else { while (n--) Send8(*data++); } if (!ReadWriteAllowed() || ((len == 0) && (ep & TRANSFER_RELEASE))) // Release full buffer ReleaseTX(); } } TXLED1; // light the TX LED TxLEDPulse = TX_RX_LED_PULSE_MS; return r; } extern const u8 _initEndpoints[] PROGMEM; const u8 _initEndpoints[] = { 0, #ifdef CDC_ENABLED EP_TYPE_INTERRUPT_IN, // CDC_ENDPOINT_ACM EP_TYPE_BULK_OUT, // CDC_ENDPOINT_OUT EP_TYPE_BULK_IN, // CDC_ENDPOINT_IN #endif #ifdef HID_ENABLED EP_TYPE_INTERRUPT_IN // HID_ENDPOINT_INT #endif }; #define EP_SINGLE_64 0x32 // EP0 #define EP_DOUBLE_64 0x36 // Other endpoints // edit by NicoHood #define EP_SINGLE_16 0x12 static void InitEP(u8 index, u8 type, u8 size) { UENUM = index; UECONX = 1; UECFG0X = type; UECFG1X = size; } static void InitEndpoints() { for (u8 i = 1; i < sizeof(_initEndpoints); i++) { UENUM = i; UECONX = 1; UECFG0X = pgm_read_byte(_initEndpoints + i); // edit by NicoHood #if USB_EP_SIZE == 16 UECFG1X = EP_SINGLE_16; #elif USB_EP_SIZE == 64 UECFG1X = EP_DOUBLE_64; #else #error Unsupported value for USB_EP_SIZE #endif } UERST = 0x7E; // And reset them UERST = 0; } // Handle CLASS_INTERFACE requests static bool ClassInterfaceRequest(Setup& setup) { u8 i = setup.wIndex; #ifdef CDC_ENABLED if (CDC_ACM_INTERFACE == i) return CDC_Setup(setup); #endif #ifdef HID_ENABLED if (HID_INTERFACE == i) return HID_Setup(setup); #endif return false; } int _cmark; int _cend; void InitControl(int end) { SetEP(0); _cmark = 0; _cend = end; } static bool SendControl(u8 d) { if (_cmark < _cend) { if (!WaitForINOrOUT()) return false; Send8(d); if (!((_cmark + 1) & 0x3F)) ClearIN(); // Fifo is full, release this packet } _cmark++; return true; }; // Clipped by _cmark/_cend int USB_SendControl(u8 flags, const void* d, int len) { int sent = len; const u8* data = (const u8*)d; bool pgm = flags & TRANSFER_PGM; while (len--) { u8 c = pgm ? pgm_read_byte(data++) : *data++; if (!SendControl(c)) return -1; } return sent; } // Send a USB descriptor string. The string is stored in PROGMEM as a // plain ASCII string but is sent out as UTF-16 with the correct 2-byte // prefix static bool USB_SendStringDescriptor(const u8*string_P, u8 string_len) { SendControl(2 + string_len * 2); SendControl(3); for (u8 i = 0; i < string_len; i++) { bool r = SendControl(pgm_read_byte(&string_P[i])); r &= SendControl(0); // high byte if (!r) { return false; } } return true; } // Does not timeout or cross fifo boundaries // Will only work for transfers <= 64 bytes // TODO int USB_RecvControl(void* d, int len) { WaitOUT(); Recv((u8*)d, len); ClearOUT(); return len; } int SendInterfaces() { int total = 0; u8 interfaces = 0; #ifdef CDC_ENABLED total = CDC_GetInterface(&interfaces); #endif #ifdef HID_ENABLED total += HID_GetInterface(&interfaces); #endif return interfaces; } // Construct a dynamic configuration descriptor // This really needs dynamic endpoint allocation etc // TODO static bool SendConfiguration(int maxlen) { // Count and measure interfaces InitControl(0); int interfaces = SendInterfaces(); ConfigDescriptor config = D_CONFIG(_cmark + sizeof(ConfigDescriptor), interfaces); // Now send them InitControl(maxlen); USB_SendControl(0, &config, sizeof(ConfigDescriptor)); SendInterfaces(); return true; } u8 _cdcComposite = 0; static bool SendDescriptor(Setup& setup) { u8 t = setup.wValueH; if (USB_CONFIGURATION_DESCRIPTOR_TYPE == t) return SendConfiguration(setup.wLength); InitControl(setup.wLength); #ifdef HID_ENABLED if (HID_REPORT_DESCRIPTOR_TYPE == t) return HID_GetDescriptor(t); #endif const u8* desc_addr = 0; if (USB_DEVICE_DESCRIPTOR_TYPE == t) { if (setup.wLength == 8) _cdcComposite = 1; desc_addr = _cdcComposite ? (const u8*)&USB_DeviceDescriptorA : (const u8*)&USB_DeviceDescriptor; } else if (USB_STRING_DESCRIPTOR_TYPE == t) { if (setup.wValueL == 0) { desc_addr = (const u8*)&STRING_LANGUAGE; } else if (setup.wValueL == IPRODUCT) { return USB_SendStringDescriptor(STRING_PRODUCT, strlen(USB_PRODUCT)); } else if (setup.wValueL == IMANUFACTURER) { return USB_SendStringDescriptor(STRING_MANUFACTURER, strlen(USB_MANUFACTURER)); } else return false; } if (desc_addr == 0) return false; u8 desc_length = pgm_read_byte(desc_addr); USB_SendControl(TRANSFER_PGM, desc_addr, desc_length); return true; } // Endpoint 0 interrupt ISR(USB_COM_vect) { SetEP(0); if (!ReceivedSetupInt()) return; Setup setup; Recv((u8*)&setup, 8); ClearSetupInt(); u8 requestType = setup.bmRequestType; if (requestType & REQUEST_DEVICETOHOST) WaitIN(); else ClearIN(); bool ok = true; if (REQUEST_STANDARD == (requestType & REQUEST_TYPE)) { // Standard Requests u8 r = setup.bRequest; if (GET_STATUS == r) { Send8(0); // TODO Send8(0); } else if (CLEAR_FEATURE == r) { } else if (SET_FEATURE == r) { } else if (SET_ADDRESS == r) { WaitIN(); UDADDR = setup.wValueL | (1 << ADDEN); } else if (GET_DESCRIPTOR == r) { ok = SendDescriptor(setup); } else if (SET_DESCRIPTOR == r) { ok = false; } else if (GET_CONFIGURATION == r) { Send8(1); } else if (SET_CONFIGURATION == r) { if (REQUEST_DEVICE == (requestType & REQUEST_RECIPIENT)) { InitEndpoints(); _usbConfiguration = setup.wValueL; } else ok = false; } else if (GET_INTERFACE == r) { } else if (SET_INTERFACE == r) { } } else { InitControl(setup.wLength); // Max length of transfer ok = ClassInterfaceRequest(setup); } if (ok) ClearIN(); else { Stall(); } } void USB_Flush(u8 ep) { SetEP(ep); if (FifoByteCount()) ReleaseTX(); } // General interrupt ISR(USB_GEN_vect) { u8 udint = UDINT; UDINT = 0; // End of Reset if (udint & (1 << EORSTI)) { InitEP(0, EP_TYPE_CONTROL, EP_SINGLE_64); // init ep0 _usbConfiguration = 0; // not configured yet UEIENX = 1 << RXSTPE; // Enable interrupts for ep0 } // Start of Frame - happens every millisecond so we use it for TX and RX LED one-shot timing, too if (udint & (1 << SOFI)) { #ifdef CDC_ENABLED USB_Flush(CDC_TX); // Send a tx frame if found #endif // check whether the one-shot period has elapsed. if so, turn off the LED if (TxLEDPulse && !(--TxLEDPulse)) TXLED0; if (RxLEDPulse && !(--RxLEDPulse)) RXLED0; } } // VBUS or counting frames // Any frame counting? u8 USBConnected() { u8 f = UDFNUML; delay(3); return f != UDFNUML; } //======================================================================= //======================================================================= USBDevice_ USBDevice; USBDevice_::USBDevice_() { } // edit by NicoHood // added from teensy definition by paul stoffregen #if defined(__AVR_AT90USB82__) || defined(__AVR_AT90USB162__) || defined(__AVR_ATmega32U2__) || defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega8U2__) #define HW_CONFIG() #define PLL_CONFIG() (PLLCSR = ((1<