Repository: xobs/grainuum Branch: master Commit: 16d0fa671786 Files: 6 Total size: 74.4 KB Directory structure: gitextract_9qw8xfqk/ ├── LICENSE ├── README.md ├── grainuum-phy-ll.s ├── grainuum-phy.c ├── grainuum-state.c └── grainuum.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ Copyright (c) Sean Cross 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. ================================================ FILE: README.md ================================================ Grainuum USB ============ A software implementation of a USB stack for small CPUs. Grainuum is designed to run on Cortex-M0+ CPUs running at 48 MHz with single-cycle IO. In practice, this means it runs well on a wide variety of Kinetis chips. Usage ======= To start with, create a GrainuumUSB object that defines your device's pin layout. Specify the offsets for setting and clearing pins, sampling pins, changing the pin direction, and the offsets of the two pins in the various banks. The structure is defined as such: static struct USBPHY { // USB D- line descriptor uint32_t dpIAddr; // GPIO "sample-whole-bank" address uint32_t dpSAddr; // GPIO "set-pin-level" address uint32_t dpCAddr; // GPIO "clear-pin-level" address uint32_t dpDAddr; // GPIO "pin-direction" address, where 1 = output uint32_t dpShift; // Shift of GPIO pin in S/C/D/I addresses // USB D+ line descriptor, as above uint32_t dnIAddr; uint32_t dnSAddr; uint32_t dnCAddr; uint32_t dnDAddr; uint32_t dnShift; // USB masks uint32_t dpMask; // Mask of GPIO pin in S/C/D/I addresses uint32_t dnMask; ... }; For example, if D+ was on pin PTA4 and D- was on PTB0, you might specify the following layout: static struct GrainuumUSB myUSB = { /* PTB0 */ .usbdnIAddr = 0xf8000050, /* FGPIOB_PDIR */ .usbdnSAddr = 0xf8000044, /* FGPIOB_PSOR */ .usbdnCAddr = 0xf8000048, /* FGPIOB_PCOR */ .usbdnDAddr = 0xf8000054, /* FGPIOB_PDDR */ .usbdnMask = (1 << 0), .usbdnShift = 0, /* PTA4 */ .usbdpIAddr = 0xf8000010, /* FGPIOA_PDIR */ .usbdpSAddr = 0xf8000004, /* FGPIOA_PSOR */ .usbdpCAddr = 0xf8000008, /* FGPIOA_PCOR */ .usbdpDAddr = 0xf8000014, /* FGPIOA_PDDR */ .usbdpMask = (1 << 4), .usbdpShift = 4, }; You should also set up a GrainuumConfig device to handle USB communication: struct GrainuumConfig { /* Called by GrainuumUSB to send descriptors to the host */ get_usb_descriptor_t getDescriptor; /* Called by GrainuumUSB when the host sets the configuration number */ usb_set_config_num_t setConfigNum; /* Called by GrainuumUSB to get space to store incoming data */ usb_get_buffer_t getReceiveBuffer; /* Called by GrainuumUSB when data has been received from the host */ usb_data_in_t receiveData; /* Called by GrainuumUSB after sendData() has queued data, but before it is sent */ usb_data_out_start_t sendDataStarted; /* Called by GrainuumUSB after sendData() has sent the data to the host */ usb_data_out_finish_t sendDataFinished; } __attribute__((packed, aligned(4))); The most important function to fill in is getDescriptor(), which will allow the USB system to respond to requests from the host. Most other entries are optional. Register these two objects with GrainuumUSB: void grainuumInit(struct GrainuumUSB *usb, struct GrainuumConfig *cfg); This will initialize the PHY and put it in "Disconnected" mode. To connect, call grainuumConnect(); void grainuumConnect(struct GrainuumUSB *usb); Now you can hook your interrupt handler. When an ISR hits, call grainuumCaptureI() with a buffer big enough to hold one USB packet: void grainuumCaptureI(struct GrainuumUSB *usb, uint8_t packet[12]); Then, sometime later once the interrupt is finished, pass the same buffer to grainuumProcess(): void grainuumProcess(struct GrainuumUSB *usb, const uint8_t packet[12]); *The packet that is passed to grainuumProcess() and grainuumCaptureI() MUST be aligned such that packet[1] is word-aligned. One way to do this might be to define packet[16] as being aligned, and pass &packet[3] to these functions. Or you can use Granuum Buffers, which are described below.* To send data to the host, use grainuumSendData(): int grainuumSendData(struct GrainuumUSB *usb, int epnum, const void *data, int size); Grainuum Buffers ---------------- The USB PHY uses a ring buffer to log all incoming data as it enters the device. This data has special alignment requirements. You can use Grainuum Buffers to manage this data. Grainuum Buffers are a set of macros that wrap all of the alignment magic. Declare a Grainuum Buffer using the GRAINUUM_BUFFER macro, specifying the number of complete packets to buffer. To declare a buffer named *usb_buffer* with four elements, write: GRAINUUM_BUFFER(usb_buffer, 4); In your program code, you must initialize the buffer before you use it: GRAINUUM_BUFFER_INIT(usb_buffer); To check if the buffer is empty, use is_empty: if (!GRAINUUM_BUFFER_IS_EMPTY(usb_buffer)) { ... work on the buffer ... } You'll generally want to get a pointer to the top of the buffer, and advance it only if the data is filled. To get a pointer to the top of the buffer (and pass it to grainuumCaptureI()), type: grainuumCaptureI(usb, GRAINUUM_BUFFER_ENTRY(usb_buffer)); If the buffer is filled, advance the buffer with advance(): GRAINUUM_BUFFER_ADVANCE(usb_buffer); To get the oldest item in the queue, use top(): uint8_t *usb_pkt = GRAINUUM_BUFFER_TOP(usb_buffer); When you're done with the packet and want to advance tne end of the buffer (i.e. remove the oldest item), use remove(): GRAINUUM_BUFFER_REMOVE(usb_buffer); Callbacks and Hooks ------------------- Most of the normal configuration is done through the GrainuumConfig structure. ================================================ FILE: grainuum-phy-ll.s ================================================ /**************************************************************************** * Grainuum Software USB Stack * * * * MIT License: * * Copyright (c) 2016 Sean Cross * * * * 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, distribute with modifications, 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 ABOVE 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. * * * * Except as contained in this notice, the name(s) of the above copyright * * holders shall not be used in advertising or otherwise to promote the * * sale, use or other dealings in this Software without prior written * * authorization. * ****************************************************************************/ #ifndef GRAINUUM_SECTION #define GRAINUUM_SECTION .ramtext #endif .section GRAINUUM_SECTION /* Can also run out of .section .text */ .cpu cortex-m0plus .fpu softvfp #if 0 /*************************************************************************** * USB PHY low-level code * * Exports the following functions: * * int usbPhyReadI(struct GrainuumUSB *usb, uint8_t buffer[11]) * void usbPhyWriteI(struct GrainuumUSB *usb, const uint8_t buffer[11], uint32_t count) * * Interrupts are disabled during this code, since it is time-critical. * Note that as a Kinetis "feature", jumps of more than 48 bytes can * cause random amounts of jitter. Make sure you don't do that. * * Both functions take the following struct as their first parameter: * * static struct USBPHY { * // USB D- line descriptor * uint32_t dpIAddr; // GPIO "sample-whole-bank" address * uint32_t dpSAddr; // GPIO "set-pin-level" address * uint32_t dpCAddr; // GPIO "clear-pin-level" address * uint32_t dpDAddr; // GPIO "pin-direction" address, where 1 = output * uint32_t dpShift; // Shift of GPIO pin in S/C/D/I addresses * * // USB D+ line descriptor, as above * uint32_t dnIAddr; * uint32_t dnSAddr; * uint32_t dnCAddr; * uint32_t dnDAddr; * uint32_t dnShift; * * // USB masks * uint32_t dpMask; // Mask of GPIO pin in S/C/D/I addresses * uint32_t dnMask; * * // Optional extra data follows * }; */ #endif /* [r|w]usbphy offsets */ .equ dpIAddr,0x08 .equ dpSAddr,0x0c .equ dpCAddr,0x10 .equ dpDAddr,0x14 .equ dpShift,0x18 .equ dnIAddr,0x1c .equ dnSAddr,0x20 .equ dnCAddr,0x24 .equ dnDAddr,0x28 .equ dnShift,0x2c .equ dpMask,0x30 .equ dnMask,0x34 /* * * Each USB bit takes about 666 nS, which at a 48 MHz clock rate gives us * 32 cycles to read each bit. This code runs on a Cortex M0+, where each * instruction is one cycle, except taken-branches are three cycles. * * A USB frame begins with the pattern KJKJ...KK. A USB frame ends with * a double-SE0 state. USB low-speed packets have an 8-bit sync period, and * a maximum of 11 bytes. Thus, a USB low-speed packet looks like this: * * KJKJKJKK | data | 00 * * Our usbReadData() code will start by positioning ourselves in the * middle of a pulse. We do that by samping the line, then waiting for * it to change, then waiting some number of cycles. * * Once we're positioned, we then start looking for the KK end-of-sync * indicator. An interrupt takes 16 clock cycles, and it probably took * at least 32 clock cycles to get here, meaning we've already lost the * first KJ. This is fine, as we just need to look for "KK" to indicate * the end of the sync period. * * Since a USB low-speed packet is at most 11 bytes, we can store this in * three 32-bit registers. We chain three registers together in a shift- * chain by self-adding-with-carry on each of the three registers in * sequence to move the top bit from one into the bottom bit of the next. * * Add a 1 to the low register if the state is the same as the previous * state, and add a 0 to the low register if the state has changed. * * As a special case, when we get six consecutive bits in a row (i.e. six * ones or six zeroes), the host will "stuff" one bit and flip the state, * meaning we should ignore that 1-bit. If this is the case, the shift * should not be processed. * * Continue reading bits in until we get a double-SE0. Actually, any * SE0 should be considered end-of-frame. * */ rretval .req r0 /* Return value (at the end) */ rusbphy .req r0 /* Pointer to the USBPHY struct (stored on stack) */ rscratch .req r0 /* General-purpose scratch register (after premable) */ routptr .req r1 /* Outgoing sample buffer */ rone .req r2 /* The value 1 */ rreg .req r3 /* Register to sample pin */ rval .req r4 /* Currently-sampled value */ rmash .req r5 /* Mask/shift to isolate required pin */ rsample .req r6 /* Most recent byte */ rcounter .req r7 /* Number of bytes sampled */ rlastval .req r8 /* What value was the last pin? */ runstuff .req r9 /* Log of the last six bits, for unstuffing */ rdpshift .req r10 /* Shift amount for D+ */ rdpiaddr .req r11 /* Pointer to D+ sample address */ rdniaddr .req r12 /* Pointer to D- sample address */ /* Read Stack: * 0: dnMask * 4: rusbphy (currently unused) */ .equ rsDnMask,0x00 .equ rsUsbPhy,0x04 .func usbPhyReadI .global usbPhyReadI usb_phy_read__se0: add sp, #12 pop {r2-r6} mov r8, r2 mov r9, r3 mov r10, r4 mov r11, r5 mov r12, r6 mov rretval, #0 sub rretval, #4 // Return -4 pop {r2-r7,pc} /*int */usbPhyReadI/*(struct GrainuumUSB *usb, uint8_t samples[11])*/: push {r2-r7,lr} mov r2, r8 mov r3, r9 mov r4, r10 mov r5, r11 mov r6, r12 push {r2-r6} // Save high registers sub sp, #12 ldr rreg, [rusbphy, #dnIAddr] // Grab the address for the data input reg. ldr rmash, [rusbphy, #dnMask] // Grab the mask for the bit. str rusbphy, [sp, #rsUsbPhy] // Save the USBPHY on the stack. str rmash, [sp, #rsDnMask] // Save it on the stack for later. ldr rsample, [rusbphy, #dpIAddr] // Also grab D+ address ldr rcounter, [rusbphy, #dpMask] // And D+ mask /* Wait for the line to flip */ ldr rval, [rreg] // Sample D-, to watch for it flipping ldr rone, [rsample] // Sample D+ at the same time and rval, rmash // Mask off the interesting bit mov rlastval, rval // Save the bit for use in looking for sync /* Check to see if it's SE0, in which case this is a keepalive pkt */ and rone, rcounter // Mask off the interesting bit add rone, rval // Combine D+ and D-. beq usb_phy_read__se0 // Exit if SE0 condition (both are 0). /* Clear out the register shift-chain */ mov rsample, #0 // Reset the sample byte value. mov rcounter, #0 // Reset the "bits" counter. mov rone, #1 // Actually load the value '1' into the reg. mov rval, #0b11 mov runstuff, rval // Load 0b11 into unstuff reg, as the header // ends with the pattern KK, which starts // a run of two. // The loop is 4 cycles on a failure. One // pulse is 32 cycles. Therefore, loop up // to 8 times before giving up. .rept 8 ldr rval, [rreg] // Sample USBDP and rval, rmash // Mask off the interesting bit cmp rval, rlastval // Wait for it to change bne usb_phy_read__sync_wait // When it changes, go wait for sync pulse .endr b usb_phy_read__timeout // It never changed, so return "timeout". usb_phy_read__sync_wait: // Move us away from the start of the pulse, to avoid transition errors. bl usb_phy__wait_5_cycles // Wait for the end-of-header sync pulse, which is when the value // repeats itself. This is the "KK" in the KJKJKJKK training sequence. .rept 6 ldr rval, [rreg] // Sample USBDP and rval, rmash // Mask off the interesting bit cmp rlastval, rval beq usb_phy_read__start_reading_usb mov rlastval, rval bl usb_phy__wait_27_cycles .endr b usb_phy_read__sync_timeout /* We're synced to the middle of a pulse, and the clock sync / start-of- * -frame has been found. Real packet data follows. */ usb_phy_read__start_reading_usb: // ? /* Adjust rlastval so that it's in the correct position -- we skip doing this above since we're only interested in the value changing, not in what the value is. However, we're now interested in what the value is, so we now deal with shifts instead of masks, and always mask by #1. */ mov rval, rlastval ldr rmash, [rusbphy, #dpShift] ror rval, rmash and rval, rone mov rlastval, rval // 6 /* We have plenty of extra cycles here, because the first bit is a K, * and we simply need to wait for it to finish. */ ldr rreg, [rusbphy, #dpIAddr] // Cache the address of the D+ input bank mov rdpiaddr, rreg // to save one cycle. ldr rreg, [rusbphy, #dnIAddr] // Cache the address of the D- input bank mov rdniaddr, rreg // to save another cycle. ldr rreg, [rusbphy, #dpShift] // Cache the D+ shift, too. mov rdpshift, rreg // 9 nop nop nop nop nop nop nop usb_phy_read__get_usb_bit: mov rval, rdpiaddr // Get the address of the D+ input bank. mov rreg, rdniaddr // Get the address of the D- input bank. ldr rval, [rval] // Actually sample D+ ldr rreg, [rreg] // Also sample D- mov rmash, rdpshift // Get the shift of the D+ bit ror rval, rmash // Rotate the value down to bit 1. and rval, rone // Mask off everything else. // 7 /* xor this bit with the last bit and invert it, in order to get * the logical value */ mov rmash, rlastval // Load up the previous bit mov rlastval, rval // Save the current bit for the next loop. eor rmash, rval // Check to see if the state has changed. mvn rmash, rmash // Invert, as 1 ^ 1 or 0 ^ 0 should be 1. and rmash, rone // Mask off everything but the last bit. orr rsample, rmash // Append the value to the sample. ror rsample, rone // Move it to the top of the buffer. add runstuff, runstuff // Shift the "unstuff" value up by 1. add runstuff, rmash // "or" in the one-bit rval to the bottom. // 9 // [16 total] /* If we've completed a byte, the rcounter will mask to 0. * If the byte is done, advance. * If the byte continues, check for SE0. */ add rcounter, rone // Increment the total-bit counter. mov rscratch, #7 // Prepare to mask by 0x7 tst rcounter, rscratch // Perform the mask beq usb_phy_read__advance_byte // If the result is 0, advance the byte. // 4 (or 5, if branch taken) // The result is NOT 0, so see if it's an SE0. usb_phy_read__check_se0: // Check for SE0 ldr rscratch, [sp, #rsDnMask] and rreg, rscratch add rreg, rval // An end-of-frame is indicated by two // frames of SE0. If this is the case, // then the result of adding these // together will result in 0. bne usb_phy_read__check_unstuff b usb_phy_read__exit // Exit if so. // 6 (if not SE0) usb_phy_read__advance_byte: lsr rsample, #24 // Rotate the sample down to the byte list strb rsample, [routptr] // Save the value to the out buffer. add routptr, rone // Advance the out pointer. mov rsample, #0 // Reset sample accumulator. // 5 // Figure out if we need to unstuff, or if we can just continue to the // next bit. // Six consecutive USB states will be followed by a dummy // state flip. Ignore this. usb_phy_read__check_unstuff: mov rreg, runstuff mov rval, #0b111111 // Unstuff mask and rreg, rval cmp rreg, rval /* Loop again */ bne usb_phy_read__get_usb_bit // 2 (if branch taken, 1 if unstuffing needs to happen) usb_phy_read__unstuff: nop // We get here when the current bit has // one more clock cycle left. Add a nop // just to make the cycle-counting easier. /* --- New "loop" starts here --- */ // NOTE that we don't increment "rcounter" here. mov runstuff, rone // We're skipping over one bit, which // results in a new run of one. add runstuff, rone // 2 /* Invert the last value, since a false 0 was added to the stream */ mov rreg, rlastval // Read the current last val into a lo reg. mvn rreg, rreg // Negate the value. and rreg, rreg, rone // Mask it with 0b1 mov rlastval, rreg // Save it back into a lo reg. // 4 cmp rcounter, #88 // Sanity check: see if we've read more // than 88 bytes (11 bits), because we've // got cycles to spare here. bgt usb_phy_read__exit // Exit if so // 2 bl usb_phy__wait_22_cycles b usb_phy_read__get_usb_bit // 2 usb_phy_read__exit: // a minimum of 10 cycles have elapsed since we got here /* Count the number of bytes read (rcounter / 8) */ mov rretval, rcounter asr rretval, #3 // Return number of bytes (not bits) read. cmp rretval, #11 // Error out if we read more than 11 bytes. bgt usb_phy_read__overflow_exit // 4 usb_phy_read__return: add sp, #12 // Restore stack. pop {r2-r6} mov r8, r2 mov r9, r3 mov r10, r4 mov r11, r5 mov r12, r6 // 13 pop {r2-r7,pc} // 8 /* Read too many bits, return -2 */ usb_phy_read__overflow_exit: mov rretval, #0 sub rretval, #2 b usb_phy_read__return /* Unable to find pulse end, return -3 */ usb_phy_read__sync_timeout: mov rretval, #0 sub rretval, #3 b usb_phy_read__return /* Timeout while reading, return -1 */ usb_phy_read__timeout: mov rretval, #0 sub rretval, #1 b usb_phy_read__return .endfunc .type usbPhyReadI, %function .size usbPhyReadI, .-usbPhyReadI usb_phy__wait_32_cycles: nop usb_phy__wait_31_cycles: nop usb_phy__wait_30_cycles: nop usb_phy__wait_29_cycles: nop usb_phy__wait_28_cycles: nop usb_phy__wait_27_cycles: nop usb_phy__wait_26_cycles: nop usb_phy__wait_25_cycles: nop usb_phy__wait_24_cycles: nop usb_phy__wait_23_cycles: nop usb_phy__wait_22_cycles: nop usb_phy__wait_21_cycles: nop usb_phy__wait_20_cycles: nop usb_phy__wait_19_cycles: nop usb_phy__wait_18_cycles: nop usb_phy__wait_17_cycles: nop usb_phy__wait_16_cycles: nop usb_phy__wait_15_cycles: nop usb_phy__wait_14_cycles: nop usb_phy__wait_13_cycles: nop usb_phy__wait_12_cycles: nop usb_phy__wait_11_cycles: nop usb_phy__wait_10_cycles: nop usb_phy__wait_9_cycles: nop usb_phy__wait_8_cycles: nop usb_phy__wait_7_cycles: nop usb_phy__wait_6_cycles: nop usb_phy__wait_5_cycles: mov pc, lr /* * usbPhyWriteI * Register (arguments): * r0: USBPHY * r1: pointer to buffer data * r2: number of bytes to write */ wusbphy .req r0 /* Pointer to USBPHY value */ wlastsym .req r1 /* The last symbol (0 = j, 1 = k) */ wpkt .req r2 /* Current byte */ wleft .req r3 /* Number of bits left before we need to reload wpkt */ /* These are used when writing values out. May be repurposed. */ wpaddr .req r4 /* Write "address" for D+ line, during normal operation */ wnaddr .req r5 /* Write "address" for D- line, during normal operation */ wtmp1 .req r4 wtmp2 .req r5 /* These must remain unchanged during the whole operation. */ wpmask .req r6 /* Write mask for D+ line */ wnmask .req r7 /* Write mask for D- line */ wbytes .req r8 /* Pointer to bytes (from r2 arg) */ wend .req r9 /* End of the wbytes array (from r1+r2 arg) */ wdpsetreg .req r10 wdpclrreg .req r11 wdnclrreg .req r12 wstuff .req r0 /* The last six bits, used for bit stuffing (reuses wusbphy) */ /* Stack data: 0: D- set (wDnSAddr) 4: wusbphy */ /* Indexes off of internal data */ .equ wDnSAddr,0x00 /* D- Set Addr */ .equ wDnCAddr,0x04 /* D- Clear Addr */ .equ wPkt1,0x08 .equ wPkt1Num,0x0c .equ wPkt2,0x10 .equ wPkt2Num,0x14 .equ wPkt3,0x18 .equ wPkt3Num,0x1c .equ wFirstPkt,0x20 .equ wSpSave,0x24 .thumb .align 2 .thumb_func .global usbPhyWriteI .func usbPhyWriteI /*void */usbPhyWriteI/*(struct GrainuumUSB *usb, const uint8_t buffer[11], uint32_t count)*/: push {r3-r7,lr} mov r3, r8 mov r4, r9 mov r5, r10 mov r6, r11 push {r3-r6} // Save other arguments. /* Allocate and populate the stack */ sub sp, #8 // Use 8 bytes of stack /* Cache D+ set and clear registers early on */ ldr wtmp1, [wusbphy, #dpSAddr] // Registers are faster than RAM, and we mov wdpsetreg, wtmp1 // only have two free registers, so pre- ldr wtmp1, [wusbphy, #dpCAddr] // cache the D+ addresses to save one mov wdpclrreg, wtmp1 // clock cycle. ldr wtmp1, [wusbphy, #dnCAddr] // Cache D- clr as well. mov wdnclrreg, wtmp1 /* Load D+ and D- masks, used for direction and value setting */ ldr wpmask, [wusbphy, #dpMask] // USB D+ mask ldr wnmask, [wusbphy, #dnMask] // USB D- mask /* Pre-set the lines to J-state to prevent glitching */ #if 0 mov wpaddr, wdpsetreg // D+ set ldr wnaddr, [wusbphy, #dnCAddr] // D- clr #else mov wpaddr, wdpclrreg // D+ clr ldr wnaddr, [wusbphy, #dnSAddr] // D- set #endif str wpmask, [wpaddr] // Write D+ value str wnmask, [wnaddr] // Write D- value /* Set D+ line to OUTPUT */ ldr wtmp1, [wusbphy, #dpDAddr] // Get the direction address ldr wtmp2, [wtmp1] // Get the direction value orr wtmp2, wtmp2, wpmask // Set the direciton mask str wtmp2, [wtmp1] // Set the direction for D+ /* Set D- line to OUTPUT */ ldr wtmp1, [wusbphy, #dnDAddr] // Get the direction address ldr wtmp2, [wtmp1] // Get the direction value orr wtmp2, wtmp2, wnmask // Set the direciton mask str wtmp2, [wtmp1] // Set the direction for D- /* Set K state. This indicates the start of the packet. */ mov wpaddr, wdpclrreg // D+ clr ldr wnaddr, [wusbphy, #dnSAddr] // D- set str wpmask, [wpaddr] // Write D+ value str wnmask, [wnaddr] // Write D- value /* Now that the packet has started, we have 30 cycles to complete setup. */ ldr wtmp1, [wusbphy, #dnSAddr] // Cache D- Set addr str wtmp1, [sp, #0] // Save it on the stack // 4 /* Save passed-in values on the stack, before we scribble over them. */ str wusbphy, [sp, #4] // Save wusbphy, mov wbytes, r1 // byte pointer, add r2, r2, r1 // calculate the end of the array mov wend, r2 // and save it too. // 5 /* Load the next byte into wpkt */ mov wtmp1, wbytes // Load the byte pointer ldrb wpkt, [wtmp1] // Get the actual byte add wtmp1, #1 // Increase the byte pointer mov wbytes, wtmp1 // Save the byte pointer back into wbytes // 5 mvn wpkt, wpkt // Invert the value to make the tests work mov wleft, #8 // Start over with 8 bytes. // 2 usb_phy_write__get_first_packet: mov wlastsym, #1 // Last symbols were "KK" from the header, mov wstuff, #0b111100 // so load a run of 2 into the stuff value. // 2 // usb start-of-frame header // /*bl usb_phy_write__state_k // K state entered above already */ bl usb_phy__wait_6_cycles bl usb_phy_write__state_j bl usb_phy_write__state_k bl usb_phy_write__state_j bl usb_phy_write__state_k bl usb_phy_write__state_j bl usb_phy_write__state_k bl usb_phy__wait_26_cycles // Hold k state for one more cycle. Take // up the slack that would normally // follow this. // end of header // usb_phy_write__top: mov wtmp1, #0 // Clear wthisbit, so we can add later. lsr wpkt, #1 // Shift the bottom bit into the carry bit. adc wtmp1, wtmp1 // Pull the new bit out from the carry bit. add wstuff, wstuff, wstuff // Shift up the stuff bit, to allow for add wstuff, wstuff, wtmp1 // adding the new bit in. add wlastsym, wlastsym, wtmp1 // Add the new bit to the last symbol. // Since we're only looking at the last // bit, this becomes an XOR. mov wtmp1, #0b1 // Examine the last bit, to check for a tst wlastsym, wtmp1 // state transition or not. // 8 /* Write the desired state out (each branch is balanced) */ bne usb_phy_write__j usb_phy_write__k: mov wpaddr, wdpsetreg // D+ set mov wnaddr, wdnclrreg b usb_phy_write__out usb_phy_write__j: mov wpaddr, wdpclrreg // D+ clr ldr wnaddr, [sp, #0] // D- set usb_phy_write__out: str wpmask, [wpaddr] str wnmask, [wnaddr] // 7 (either branch taken) // 15 cycles total sub wleft, wleft, #1 // See how many bits we have left to write. bne usb_phy_write__continue_byte // If nonzero, write another bit. // 2 /* We just finished writing a byte. Load the next byte, or exit. */ usb_phy_write__finished_byte: mov wtmp1, wbytes // Move byte array into a lo reg cmp wtmp1, wend // See if we've reached the end. beq usb_phy_write__send_eof // Exit if it's now 0. ldrb wpkt, [wtmp1] // Read the next byte into wpkt. add wtmp1, #1 // Advance byte array by one. mov wbytes, wtmp1 // ...or store byte addr back in the hi reg. // 7 usb_phy_write__calculate_next_pkt: mvn wpkt, wpkt // Invert it to make the math work. mov wleft, #8 // Reset "bits left" to 8. // 2 nop // 1 /* If we just wrote "111111", then stuff one bit */ usb_phy_write__stuff_bit_maybe: mov wtmp2, #0b111111 // Compare it with the wstuff value and wtmp2, wstuff // AND the two together. If they're the beq usb_phy_write__stuff_bit // same, then stuff one bit. // 3 usb_phy_write__done_stuffing_bit: b usb_phy_write__top // 2 /* We're still writing this byte, so there's nothing to do. */ usb_phy_write__continue_byte: bl usb_phy__wait_7_cycles b usb_phy_write__stuff_bit_maybe // 2 usb_phy_write__stuff_bit: /* When we get here, we are already into the packet. */ // Need 18 cycles until packet is written bl usb_phy__wait_6_cycles mov wstuff, #0b111111 // Clear out the bit-stuff rcounter // 2 add wlastsym, wlastsym, #1 // Invert the last symbol. mov wtmp1, #0b1 // See if we need to send j or k tst wlastsym, wtmp1 // 3 /* Write the desired state out (each branch is balanced) */ bne usb_phy_write__stuff_j usb_phy_write_stuff_k: mov wpaddr, wdpsetreg // D+ set mov wnaddr, wdnclrreg // D- clr b usb_phy_write__stuff_out usb_phy_write__stuff_j: mov wpaddr, wdpclrreg // D+ clr ldr wnaddr, [sp, #0] // D- set usb_phy_write__stuff_out: str wpmask, [wpaddr] str wnmask, [wnaddr] // 7 (either branch taken) bl usb_phy__wait_13_cycles b usb_phy_write__done_stuffing_bit usb_phy_write__eof_stuff_bit: bl usb_phy__wait_12_cycles mov wstuff, #0b111111 // Clear out the bit-stuff rcounter // 2 add wlastsym, wlastsym, #1 // Invert the last symbol. mov wtmp1, #0b1 // See if we need to send j or k tst wlastsym, wtmp1 // 3 /* Write the desired state out (each branch is balanced) */ bne usb_phy_write__eof_stuff_j usb_phy_write__eof_stuff_k: mov wpaddr, wdpsetreg // D+ set mov wnaddr, wdnclrreg // D- clr b usb_phy_write__eof_stuff_out usb_phy_write__eof_stuff_j: mov wpaddr, wdpclrreg // D+ clr ldr wnaddr, [sp, #0] // D- set usb_phy_write__eof_stuff_out: str wpmask, [wpaddr] str wnmask, [wnaddr] bl usb_phy__wait_27_cycles b usb_phy_write__send_se0 // 7 (either branch taken) usb_phy_write__send_eof: mov wtmp2, #0b111111 // Compare it with the wstuff value and wtmp2, wstuff // AND the two together. If they're the beq usb_phy_write__eof_stuff_bit // same, then stuff one bit. bl usb_phy__wait_16_cycles usb_phy_write__send_se0: bl usb_phy_write__state_se0 bl usb_phy_write__state_se0 /* Set J-state, as required by the spec */ #if 1 bl usb_phy__wait_6_cycles mov wpaddr, wdpsetreg // D+ set mov wnaddr, wdnclrreg // D- clr str wpmask, [wpaddr] str wnmask, [wnaddr] #else #warning "Not setting J state (fix this before committing)" #endif /* Cheat a bit on the end-of-packet time, since the following * instructions take roughly 10 cycles before the lines reset. */ bl usb_phy__wait_28_cycles // --- Done Transmitting --- // // Restore sp, since we're done with it ldr wusbphy, [sp, #4] // Restore wusbphy add sp, #8 // Restore stack pointer. /* Now, set both lines back to INPUT */ /* Set D+ line to INPUT */ ldr wtmp1, [wusbphy, #dpDAddr] // Get the direction address ldr wtmp2, [wtmp1] // Get the direction value bic wtmp2, wtmp2, wpmask // Clear the direciton mask str wtmp2, [wtmp1] // Set the direction for D+ /* Set D- line to INPUT */ ldr wtmp1, [wusbphy, #dnDAddr] // Get the direction address ldr wtmp2, [wtmp1] // Get the direction value bic wtmp2, wtmp2, wnmask // Clear the direciton mask str wtmp2, [wtmp1] // Set the direction for D- pop {r3-r6} // Restore registers mov r11, r6 mov r10, r5 mov r9, r4 mov r8, r3 pop {r3-r7,pc} // Restore and return to caller. // Useful functions usb_phy_write__state_se0: mov wpaddr, wdpclrreg // D+ clr mov wnaddr, wdnclrreg // D- clr b usb_phy_write__out_func usb_phy_write__state_j: mov wpaddr, wdpsetreg // D+ set mov wnaddr, wdnclrreg // D- clr b usb_phy_write__out_func usb_phy_write__state_k: mov wpaddr, wdpclrreg // D+ clr ldr wnaddr, [sp, #0] // D- set nop usb_phy_write__out_func: str wpmask, [wpaddr] str wnmask, [wnaddr] b usb_phy__wait_25_cycles .endfunc .type usbPhyWriteI, %function .size usbPhyWriteI, .-usbPhyWriteI ================================================ FILE: grainuum-phy.c ================================================ /**************************************************************************** * Grainuum Software USB Stack * * * * MIT License: * * Copyright (c) 2016 Sean Cross * * * * 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, distribute with modifications, 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 ABOVE 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. * * * * Except as contained in this notice, the name(s) of the above copyright * * holders shall not be used in advertising or otherwise to promote the * * sale, use or other dealings in this Software without prior written * * authorization. * ****************************************************************************/ #include "grainuum.h" __attribute__((weak)) void grainuumConnectPre(struct GrainuumUSB *usb) { (void)usb; } __attribute__((weak)) void grainuumConnectPost(struct GrainuumUSB *usb) { (void)usb; } __attribute__((weak)) void grainuumDisconnectPre(struct GrainuumUSB *usb) { (void)usb; } __attribute__((weak)) void grainuumDisconnectPost(struct GrainuumUSB *usb) { (void)usb; } __attribute__((weak)) void grainuumReceivePacket(struct GrainuumUSB *usb) { (void)usb; } __attribute__((weak)) void grainuumInitPre(struct GrainuumUSB *usb) { (void)usb; } __attribute__((weak)) void grainuumInitPost(struct GrainuumUSB *usb) { (void)usb; } /* --- */ __attribute__((section(".ramtext"))) void grainuum_receive_packet(struct GrainuumUSB *usb) { grainuumReceivePacket(usb); } __attribute__((section(".ramtext"))) void grainuumCaptureI(struct GrainuumUSB *usb, uint8_t *samples) { int ret; const uint8_t nak_pkt[] = {USB_PID_NAK}; const uint8_t ack_pkt[] = {USB_PID_ACK}; ret = usbPhyReadI(usb, samples); if (ret <= 0) { if (ret != -1) usbPhyWriteI(usb, nak_pkt, sizeof(nak_pkt)); return; } /* Save the byte counter for later inspection */ samples[11] = ret; switch (samples[0]) { case USB_PID_IN: /* Make sure we have queued data, and that it's for this particular EP */ if ((!usb->queued_size) || (((((const uint16_t *)(samples+1))[0] >> 7) & 0xf) != usb->queued_epnum)) { usbPhyWriteI(usb, nak_pkt, sizeof(nak_pkt)); break; } usbPhyWriteI(usb, usb->queued_data, usb->queued_size); break; case USB_PID_SETUP: grainuum_receive_packet(usb); break; case USB_PID_OUT: grainuum_receive_packet(usb); break; case USB_PID_ACK: /* Allow the next byte to be sent */ usb->queued_size = 0; grainuum_receive_packet(usb); break; case USB_PID_DATA0: case USB_PID_DATA1: usbPhyWriteI(usb, ack_pkt, sizeof(ack_pkt)); grainuum_receive_packet(usb); break; default: usbPhyWriteI(usb, nak_pkt, sizeof(nak_pkt)); break; } return; } int grainuumInitialized(struct GrainuumUSB *usb) { if (!usb) return 0; return usb->initialized; } void grainuumWriteQueue(struct GrainuumUSB *usb, int epnum, const void *buffer, int size) { usb->queued_data = buffer; usb->queued_epnum = epnum; usb->queued_size = size; } void grainuumInit(struct GrainuumUSB *usb, struct GrainuumConfig *cfg) { if (usb->initialized) return; grainuumInitPre(usb); usb->cfg = cfg; usb->state.usb = usb; cfg->usb = usb; usb->initialized = 1; grainuumInitPost(usb); } void grainuumDisconnect(struct GrainuumUSB *usb) { grainuumDisconnectPre(usb); /* Set both lines to 0 (clear both D+ and D-) to simulate unplug. */ grainuumWritel(usb->usbdpMask, usb->usbdpCAddr); grainuumWritel(usb->usbdnMask, usb->usbdnCAddr); /* Set both lines to output */ grainuumWritel(grainuumReadl(usb->usbdpDAddr) | usb->usbdpMask, usb->usbdpDAddr); grainuumWritel(grainuumReadl(usb->usbdnDAddr) | usb->usbdnMask, usb->usbdnDAddr); grainuumDisconnectPost(usb); } void grainuumConnect(struct GrainuumUSB *usb) { grainuumConnectPre(usb); /* Set both lines to input */ grainuumWritel(grainuumReadl(usb->usbdpDAddr) & ~usb->usbdpMask, usb->usbdpDAddr); grainuumWritel(grainuumReadl(usb->usbdnDAddr) & ~usb->usbdnMask, usb->usbdnDAddr); grainuumConnectPost(usb); } ================================================ FILE: grainuum-state.c ================================================ /**************************************************************************** * Grainuum Software USB Stack * * * * MIT License: * * Copyright (c) 2016 Sean Cross * * * * 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, distribute with modifications, 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 ABOVE 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. * * * * Except as contained in this notice, the name(s) of the above copyright * * holders shall not be used in advertising or otherwise to promote the * * sale, use or other dealings in this Software without prior written * * authorization. * ****************************************************************************/ #include "grainuum.h" #ifndef NULL #define NULL ((void *)0) #endif void *memcpy(void *dest, const void *src, unsigned int n); enum usb_state_packet_type { packet_type_none, packet_type_setup, packet_type_setup_in, packet_type_setup_out, packet_type_in, packet_type_out, }; __attribute__((weak)) void grainuumSendWait(struct GrainuumUSB *usb, int epnum, const void *data, int size) { (void)usb; (void)epnum; (void)data; (void)size; } static uint16_t crc16_add(uint16_t crc, uint8_t c, uint16_t poly) { uint8_t i; for (i = 0; i < 8; i++) { if ((crc ^ c) & 1) crc = (crc >> 1) ^ poly; else crc >>= 1; c >>= 1; } return crc; } static uint16_t crc16(const uint8_t *data, uint32_t size, uint16_t init, uint32_t poly) { while (size--) init = crc16_add(init, *data++, poly); return init; } static void grainuum_state_clear_tx(struct GrainuumState *state, int result) { struct GrainuumUSB *usb = state->usb; /* If a thread is blocking, wake it up with a failure */ if (usb->cfg->sendDataFinished && state->packet_queued) usb->cfg->sendDataFinished(usb, result); state->data_out_left = 0; state->data_out_max = 0; state->data_out = NULL; state->packet_queued = 0; } static void grainuum_state_process_tx(struct GrainuumState *state) { uint16_t crc; struct GrainuumUSB *usb = state->usb; /* Don't allow us to re-prepare data */ if (state->packet_queued) { return; } state->packet_queued = 1; /* If there's no data to send, then don't send any */ if (!state->data_out) { state->packet_queued = 0; return; } /* If we've sent all of our data, then there's nothing else to send */ if ((state->data_out_left < 0) || (state->data_out_max < 0)) { grainuum_state_clear_tx(state, 0); return; } /* Pick the correct PID, DATA0 or DATA1 */ if (state->data_buffer & (1 << state->tok_epnum)) state->packet.pid = USB_PID_DATA1; else state->packet.pid = USB_PID_DATA0; /* If there's no data, prepare a special NULL packet */ if ((state->data_out_left == 0) || (state->data_out_max == 0)) { /* The special-null thing only happens for EP0 */ if (state->data_out_epnum != 0) { grainuum_state_clear_tx(state, 0); return; } state->packet.data[0] = 0; /* CRC16 for empty packets is 0 */ state->packet.data[1] = 0; state->packet.size = 2; grainuumWriteQueue(usb, state->data_out_epnum, &state->packet, state->packet.size + 1); return; } /* Keep the packet size to 8 bytes max */ if (state->data_out_left > 8) state->packet.size = 8; else state->packet.size = state->data_out_left; /* Limit the amount of data transferred to data_out_max */ if (state->packet.size > state->data_out_max) state->packet.size = state->data_out_max; /* Copy over data bytes */ memcpy(state->packet.data, state->data_out, state->packet.size); /* Calculate and copy the crc16 */ crc = ~crc16(state->packet.data, state->packet.size, 0xffff, 0xa001); state->packet.data[state->packet.size++] = crc; state->packet.data[state->packet.size++] = crc >> 8; /* Prepare the packet, including the PID at the end */ grainuumWriteQueue(usb, state->data_out_epnum, &state->packet, state->packet.size + 1); } /* Called when a packet is ACKed. * Updates the outgoing packet buffer. */ static void usbStateTransferSuccess(struct GrainuumState *state) { /* Reduce the amount of data left. * If the packet is divisible by 8, this will cause one more call * to this function with state->data_out_left == 0. This will send * a NULL packet, which indicates end-of-transfer. */ state->data_out_left -= 8; state->data_out_max -= 8; state->data_out += 8; if ((state->data_out_left < 0) || (state->data_out_max < 0)) { grainuum_state_clear_tx(state, 0); /* End of a State setup packet */ if (state->packet_type == packet_type_setup_out) state->packet_type = packet_type_none; if (state->packet_type == packet_type_setup_in) state->packet_type = packet_type_none; if (state->packet_type == packet_type_out) state->packet_type = packet_type_none; } state->packet_queued = 0; } /* Send data down the wire, interrupting any existing * data that may be queued. */ static int grainuum_state_send_data(struct GrainuumState *state, int epnum, const void *data, int size, int max) { /* De-queue any data that may already be queued. */ grainuum_state_clear_tx(state, 1); state->data_out_epnum = epnum; state->data_out_left = size; state->data_out_max = max; state->data_out = data; return 0; } void grainuumDropData(struct GrainuumUSB *usb) { usb->state.packet_queued = 0; usb->state.data_out = 0; grainuumWriteQueue(usb, 0, NULL, 0); } int grainuumDataQueued(struct GrainuumUSB *usb) { return (usb->state.data_out || usb->state.packet_queued); } int grainuumSendData(struct GrainuumUSB *usb, int epnum, const void *data, int size) { struct GrainuumState *state = &usb->state; int ret; if (state->data_out || !state->address || state->packet_queued) { return -11; /* EAGAIN */ } ret = grainuum_state_send_data(state, epnum, data, size, size); if (ret) return ret; grainuum_state_process_tx(state); if (usb->cfg->sendDataStarted) usb->cfg->sendDataStarted(usb, epnum, data, size); return 0; } static int grainuum_state_process_setup(struct GrainuumState *state, const uint8_t packet[10]) { const struct usb_setup_packet *setup; const void *response = (void *)-1; uint32_t response_len = 0; struct GrainuumUSB *usb = state->usb; struct GrainuumConfig *cfg = usb->cfg; setup = (const struct usb_setup_packet *)packet; if ((setup->bmRequestType == 0x00) && (setup->bRequest == SET_ADDRESS)) { state->address = setup->wValue; } else if ((setup->bmRequestType == 0x00) && (setup->bRequest == SET_CONFIGURATION)) { if (cfg->setConfigNum) cfg->setConfigNum(usb, setup->wValue); } else { response_len = cfg->getDescriptor(usb, setup, &response); } grainuum_state_send_data(state, state->tok_epnum, response, response_len, setup->wLength); return 0; } static void grainuum_state_parse_data(struct GrainuumState *state, const uint8_t packet[10], uint32_t size) { (void)size; struct GrainuumUSB *usb = state->usb; switch (state->packet_type) { case packet_type_setup: grainuum_state_process_setup(state, packet); grainuum_state_process_tx(state); state->packet_type = packet_type_none; break; case packet_type_out: // XXX HACK: An OUT packet gets generated (on Windows at least) when // terminating a SETUP sequence. This seems odd. if (state->tok_epnum == 0) break; // Copy over the packet, minus the CRC16 memcpy(state->tok_buf + state->tok_pos, packet, size - 2); state->tok_pos += (size - 2); if (!usb->cfg->receiveData(usb, state->tok_epnum, size - 2, packet)) state->packet_type = packet_type_none; break; case packet_type_in: case packet_type_none: default: break; } } static inline void grainuum_state_parse_token(struct GrainuumState *state, const uint8_t packet[2]) { state->tok_epnum = (((const uint16_t *)packet)[0] >> 7) & 0xf; /*state->tok_addr = (((const uint16_t *)packet)[0] >> 11) & 0x1f; // Field unused in this code*/ } void grainuumProcess(struct GrainuumUSB *usb, const uint8_t packet[12]) { uint32_t size = packet[11]; struct GrainuumState *state = &usb->state; switch(packet[0]) { case USB_PID_SETUP: state->packet_type = packet_type_setup; grainuum_state_clear_tx(state, 1); grainuum_state_parse_token(state, packet + 1); break; case USB_PID_DATA0: state->data_buffer |= (1 << state->tok_epnum); grainuum_state_parse_data(state, packet + 1, size - 1); break; case USB_PID_DATA1: state->data_buffer &= ~(1 << state->tok_epnum); grainuum_state_parse_data(state, packet + 1, size - 1); break; case USB_PID_OUT: grainuum_state_parse_token(state, packet + 1); state->packet_type = packet_type_out; state->tok_pos = 0; state->tok_buf = usb->cfg->getReceiveBuffer(usb, state->tok_epnum, NULL); break; case USB_PID_ACK: state->data_buffer ^= (1 << state->tok_epnum); usbStateTransferSuccess(state); if (state->data_out) { grainuum_state_process_tx(state); } else { grainuum_state_clear_tx(state, 0); } break; default: break; } } ================================================ FILE: grainuum.h ================================================ /**************************************************************************** * Grainuum Software USB Stack * * * * MIT License: * * Copyright (c) 2016 Sean Cross * * * * 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, distribute with modifications, 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 ABOVE 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. * * * * Except as contained in this notice, the name(s) of the above copyright * * holders shall not be used in advertising or otherwise to promote the * * sale, use or other dealings in this Software without prior written * * authorization. * ****************************************************************************/ #ifndef _GRAINUUM_H #define _GRAINUUM_H #include /** * @brief Extra fields for GrainuumState struct. * @note You probably can ignore this. */ #ifndef GRAINUUM_STATE_EXTRA #define GRAINUUM_STATE_EXTRA #endif /* GRAINUUM_STATE_EXTRA */ /** * @brief Extra fields for GrainuumUSB struct. * @note Use this to store context and thread information. */ #ifndef GRAINUUM_EXTRA #define GRAINUUM_EXTRA #endif /* GRAINUUM_EXTRA */ #define GET_STATUS 0 #define CLEAR_FEATURE 1 #define SET_FEATURE 3 #define SET_ADDRESS 5 #define GET_DESCRIPTOR 6 #define SET_DESCRIPTOR 7 #define GET_CONFIGURATION 8 #define SET_CONFIGURATION 9 #define GET_INTERFACE 10 #define SET_INTERFACE 11 #define SYNC_FRAME 12 #define GET_REPORT 1 #define GET_IDLE 2 #define GET_PROTOCOL 3 #define SET_REPORT 9 #define SET_IDLE 10 #define SET_PROTOCOL 11 enum usb_pids { USB_PID_RESERVED = 0xf0, USB_PID_OUT = 0xe1, USB_PID_ACK = 0xd2, USB_PID_DATA0 = 0xc3, USB_PID_PING = 0xb4, USB_PID_SOF = 0xa5, USB_PID_NYET = 0x96, USB_PID_DATA2 = 0x87, USB_PID_SPLIT = 0x78, USB_PID_IN = 0x69, USB_PID_NAK = 0x5a, USB_PID_DATA1 = 0x4b, USB_PID_ERR = 0x3c, USB_PID_SETUP = 0x2d, USB_PID_STALL = 0x1e, USB_PID_MDATA = 0x0f, }; struct GrainuumUSB; struct GrainuumState; struct GrainuumConfig; /* Function callbacks */ /* Each of these functions are called by the USB system to get a buffer. * On return, *data will point to the buffer, and the number of bytes * in the buffer will be returned. * * If the data does not exist, return 0. */ typedef int (*get_usb_descriptor_t)(struct GrainuumUSB *usb, const void *pkt, const void **data); typedef void (*usb_set_config_num_t)(struct GrainuumUSB *usb, int configNum); /* * Called when doing an OUT xfer (data to device) to get a buffer for * the specified endpoint. * It is up to the user to ensure the buffer is large enough. */ typedef void * (*usb_get_buffer_t)(struct GrainuumUSB *usb, uint8_t epnum, int32_t *size); /* * When data is received (i.e. OUT EP), this function will be called. */ typedef int (*usb_data_in_t)(struct GrainuumUSB *usb, uint8_t epnum, uint32_t bytes, const void *data); /** * @brief Called immediately after @p grainuumSendData() has queued data. * @note This function can be used to e.g. sleep a thread. * @param[in] usb pointer to the @p GrainuumUSB object * @param[in] epnum endpoint number of the transfer * @param[in] data pointer to the data being written * @param[in] size number of bytes being written * @api */ typedef void (*usb_data_out_start_t)(struct GrainuumUSB *usb, int epnum, const void *data, int size); /** * @brief Called once all data has been sent. * @note This function can be used to e.g. wake up a thread. * @param[out] usb pointer to the @p GrainuumUSB object * @param[out] result whether the transfer was successful (0), or had an error. * @api */ typedef int (*usb_data_out_finish_t)(struct GrainuumUSB *usb, int result); /* Structure of a USB packet on the wire, plus size field */ struct usb_packet { union { struct { uint8_t pid; uint8_t data[10]; /* Including CRC */ } __attribute((packed, aligned(4))); uint8_t raw_data[11]; } __attribute((packed, aligned(4))); uint8_t size; /* Not including pid (so may be 0) */ /* Checksum omitted */ } __attribute__((packed, aligned(4))); /* USB Descriptors */ #define DT_DEVICE 0x01 #define DT_CONFIGURATION 0x02 #define DT_STRING 0x03 #define DT_INTERFACE 0x04 #define DT_ENDPOINT 0x05 #define DT_DEVICE_QUALIFIER 0x06 #define DT_OTHER_SPEED_CONFIGURATION 0x07 #define DT_INTERFACE_POWER 0x08 #define DT_HID 0x21 #define DT_HID_REPORT 0x22 #define DT_PID 0x23 struct usb_setup_packet { uint8_t bmRequestType; uint8_t bRequest; union { uint16_t wValue; struct { uint8_t wValueL; uint8_t wValueH; }; }; uint16_t wIndex; uint16_t wLength; } __attribute__((packed, aligned(4))); struct usb_device_descriptor { uint8_t bLength; uint8_t bDescriptorType; uint16_t bcdUSB; uint8_t bDeviceClass; uint8_t bDeviceSubClass; uint8_t bDeviceProtocol; uint8_t bMaxPacketSize0; uint16_t idVendor; uint16_t idProduct; uint16_t bcdDevice; uint8_t iManufacturer; uint8_t iProduct; uint8_t iSerialNumber; uint8_t bNumConfigurations; } __attribute__((packed, aligned(4))); struct usb_configuration_descriptor { uint8_t bLength; /* Size of this descriptor, in bytes (9) */ uint8_t bDescriptorType; /* DT_CONFIGURATION (2) */ uint16_t wTotalLength; /* Total length of this, plus sizeof(data) */ uint8_t bNumInterfaces; /* Number of interfaces supported by config */ uint8_t bConfigurationValue; /* Value used by Set Configuration */ uint8_t iConfiguration; /* index of string descriptor for config */ uint8_t bmAttributes; /* Bitmap of attributes. D7 must be 1. */ uint8_t bMaxPower; /* Maximum power, in units of 2mA */ uint8_t data[]; /* Remaining descriptors */ } __attribute__((packed, aligned(4))); struct usb_string_descriptor { uint8_t bLength; /* sizeof(usb_string_descriptor) + sizeof(data) */ uint8_t bDescriptorType; /* DT_STRING (3) */ uint8_t data[]; /* UTF-16LE string data or lang data(for string 0 */ } __attribute__((packed, aligned(4))); struct usb_interface_descriptor { uint8_t bLength; /* sizeof(usb_interface_descriptor) (9) */ uint8_t bDescriptorType; /* DT_INTERFACE (4) */ uint8_t bInterfaceNumber; /* Which interface this describes. Usually 0. */ uint8_t bAlternateSetting; /* ??? */ uint8_t bNumEndpoints; /* Number of endpoints, minus 1 */ uint8_t bInterfaceClass; /* Class code */ uint8_t bInterfaceSubclass; /* Class sub-code */ uint8_t bInterfaceProtocol; /* Protocol code, assigned by USB */ uint8_t iInterface; /* Index of string for this interface */ } __attribute__((packed, aligned(4))); struct usb_endpoint_descriptor { uint8_t bLength; /* sizeof(usb_endpoint_descriptor) (7) */ uint8_t bDescriptorType; /* DT_ENDPOINT (5) */ uint8_t bEndpointAddress; /* High bit 1:IN, 0:OUT. Lower 4-bits are EP# */ uint8_t bmAttributes; /* 0=control, 2=bulk, 3=interrupt */ uint16_t wMaxPacketSize; /* Max packet size for this EP */ uint8_t bInterval; /* Polling rate (in 1ms units) */ } __attribute__((packed, aligned(4))); struct usb_hid_descriptor { uint8_t bLength; /* sizeof(usb_hid_descriptor) (9) */ uint8_t bDescriptorType; /* DT_HID (0x21) */ uint16_t bcdHID; /* HID class version number, in BCD */ uint8_t bCountryCode; /* Target country (usually 0) */ uint8_t bNumDescriptors; /* Number of HID class descriptors (usually 1) */ uint8_t bReportDescriptorType; /* Report descriptor type (usually 0x22) */ uint16_t wReportDescriptorLength; /* Length of the HID/PID report descriptor */ } __attribute__((packed, aligned(4))); #define GRAINUUM_BUFFER_ELEMENT_SIZE 12 /* 1 PID, 8 data, 2 CRC16, 1 size */ /* grainuum_buffer is aligned such that its first byte is on a word boundary. * This is because the first byte of every packet is a PID, which is * immediately discarded. This leaves the remainder of the packet * word-aligned. */ #define GRAINUUM_BUFFER(name, sz) \ struct { \ uint8_t head; \ uint8_t tail; \ uint8_t padding; \ union { \ uint8_t buffer[(sz) * GRAINUUM_BUFFER_ELEMENT_SIZE]; \ uint8_t elements[sz][GRAINUUM_BUFFER_ELEMENT_SIZE]; \ }; \ } name __attribute__((aligned(4))); \ uint8_t * name ## _head_ptr; #define GRAINUUM_BUFFER_INIT(name) \ do { \ (name).head = 0; \ (name).tail = 0; \ name ## _head_ptr = (name).buffer; \ } while(0) #define GRAINUUM_BUFFER_ADVANCE(name) \ do { \ (name).head += GRAINUUM_BUFFER_ELEMENT_SIZE; \ if ((name).head >= sizeof((name).buffer)) \ (name).head = 0; \ name ## _head_ptr = ((name).buffer + (name).head); \ } while(0) #define GRAINUUM_BUFFER_TOP(name) \ (&((name).buffer[(name).tail])) #define GRAINUUM_BUFFER_REMOVE(name) \ do { \ (name).tail += GRAINUUM_BUFFER_ELEMENT_SIZE; \ if ((name).tail >= sizeof((name).buffer)) \ (name).tail = 0; \ } while(0) #define GRAINUUM_BUFFER_IS_EMPTY(name) \ ((name).head == (name).tail) #define GRAINUUM_BUFFER_ENTRY(name) \ name ## _head_ptr /* Grainuum Structs */ struct GrainuumConfig { get_usb_descriptor_t getDescriptor; usb_set_config_num_t setConfigNum; usb_get_buffer_t getReceiveBuffer; usb_data_in_t receiveData; usb_data_out_start_t sendDataStarted; usb_data_out_finish_t sendDataFinished; void *data; struct GrainuumUSB *usb; } __attribute__((packed, aligned(4))); struct GrainuumState { struct GrainuumUSB *usb; uint8_t data_in[8]; const void *data_out; /* Pointer to the data that's being sent */ int32_t data_out_left; /* How much data has yet to be sent */ int32_t data_out_max; /* The maximum number of bytes to send */ int32_t data_out_epnum; /* Which endpoint the data is for */ struct usb_packet packet; /* Currently-queued packet */ int packet_queued; /* Whether a packet is queued */ uint32_t tok_pos; /* Position within the current token */ void *tok_buf; /* Buffer storing current token's data */ uint8_t tok_epnum; /* Last token's endpoint */ uint8_t data_buffer; /* Whether we're sending DATA0 or DATA1 */ uint8_t packet_type; /* PACKET_SETUP, PACKET_IN, or PACKET_OUT */ uint8_t address; /* Our configured address */ GRAINUUM_STATE_EXTRA } __attribute__((packed, aligned(4))); struct GrainuumUSB { struct GrainuumConfig *cfg; /* Callbacks */ int initialized; /* USB D- pin specification */ uint32_t usbdnIAddr; uint32_t usbdnSAddr; uint32_t usbdnCAddr; uint32_t usbdnDAddr; uint32_t usbdnShift; /* USB D+ pin specification */ uint32_t usbdpIAddr; uint32_t usbdpSAddr; uint32_t usbdpCAddr; uint32_t usbdpDAddr; uint32_t usbdpShift; uint32_t usbdnMask; uint32_t usbdpMask; uint32_t queued_size; uint32_t queued_epnum; const void *queued_data; struct GrainuumState state; /* Associated state */ GRAINUUM_EXTRA } __attribute__((packed, aligned(4))); #ifdef __cplusplus extern "C" { #endif static inline void grainuumWritel(uint32_t value, uint32_t addr) { *((volatile uint32_t *)addr) = value; } static inline uint32_t grainuumReadl(uint32_t addr) { return *(volatile uint32_t *)addr; } /*===========================================================================*/ /* Weak hook functions. */ /*===========================================================================*/ /** * @brief Called just before the USB device is plugged in. * @param[in] usb pointer to the @p GrainuumUSB object * @api */ void grainuumConnectPre(struct GrainuumUSB *usb); /** * @brief Called just after the USB device is plugged in. * @param[in] usb pointer to the @p GrainuumUSB object * @api */ void grainuumConnectPost(struct GrainuumUSB *usb); /** * @brief Called just before the USB device is unplugged. * @param[in] usb pointer to the @p GrainuumUSB object * @api */ void grainuumDisconnectPre(struct GrainuumUSB *usb); /** * @brief Called just after the USB device is unplugged. * @param[in] usb pointer to the @p GrainuumUSB object * @api */ void grainuumDisconnectPost(struct GrainuumUSB *usb); /** * @brief Called just before the USB device is first initialized. * @param[in] usb pointer to the @p GrainuumUSB object * @api */ void grainuumInitPre(struct GrainuumUSB *usb); /** * @brief Called just before the USB device is first initialized. * @param[in] usb pointer to the @p GrainuumUSB object * @api */ void grainuumInitPost(struct GrainuumUSB *usb); /** * @brief Called immediately after a packet has been received. * @note This is called from an interrupt context. Data will * be stored in the buffer that was passed to @p grainuumCaptureI() * @param[in] usb pointer to the @p GrainuumUSB object * @iclass */ void grainuumReceivePacket(struct GrainuumUSB *usb); /*===========================================================================*/ /* External declarations. */ /*===========================================================================*/ /** * @brief Returns nonzero if Grainuum has been initialized. * @param[in] usb pointer to the @p GrainuumUSB object. * @return nonzero if @p GrainuumUSB is initialized. * @retval 0 Object is not initilized. * @api */ int grainuumInitialized(struct GrainuumUSB *usb); /** * @brief Queues some data to be sent to the host. * @note After the first 8 bytes, @p data must remain valid * until the transfer has completed. This generally * means you can send const data stored in the text * section, or small 8-byte packets. * @param[in] usb pointer to the @p GrainuumUSB object. * @param[in] epnum endpoint number of the transfer. * @param[in] data pointer to the data being written. * @param[in] size number of bytes being written. * @return 0 if the transfer completed successfully. * @retval 0 Transfer completed successfully. * @api */ int grainuumSendData(struct GrainuumUSB *usb, int epnum, const void *data, int size); /** * @brief Clears the send buffer, if not empty. * @note If data has already been queued for the PHY, then * this will not prevent it from being sent. * This function is intended to be used to prevent * grainuumSendData() from returning -EAGAIN. * @param[in] usb pointer to the @p GrainuumUSB object. * @api */ void grainuumDropData(struct GrainuumUSB *usb); /** * @brief Determines if data is already queued. * @note If data has been queued, then this will return * nonzero. If this returns zero, then you can * trust grainuumSendData() will succeed. * @param[in] usb pointer to the @p GrainuumUSB object. * @return Nonzero if data is already queued. * @api */ int grainuumDataQueued(struct GrainuumUSB *usb); /** * @brief Process one received packet through the Grainuum state machine. * @note This feeds USB packets into the state machine. It should not * be called as part of an interrupt. * @param[in] usb pointer to the @p GrainuumUSB object. * @param[in] packet The USB packet that was most recently received, with byte 12 holding the size. * @api */ void grainuumProcess(struct GrainuumUSB *usb, const uint8_t packet[12]); /** * @brief Initialize the Grainuum USB system. * @note This is meant to run as part of an interrupt. Pass * the storage buffer in as @p samples. The number * of bytes that were read will be stored in the last * byte of the array. For best performance, make * sure that @p sample is on byte 3 of a 4-byte boundary, * so that samples[1] is on a word boundary. The @p GrainuumUSB * object will start out disconnected. * @param[in] usb Pointer to the @p GrainuumUSB object to initialize. * @param[in] link Pointer to the @p GrainuumConfig object to use. * @api */ void grainuumInit(struct GrainuumUSB *usb, struct GrainuumConfig *link); /** * @brief Capture a USB packet from the wire. * @note This is meant to run as part of an interrupt. Pass * the storage buffer in as @p samples. The number * of bytes that were read will be stored in the last * byte of the array. For best performance, make * sure that @p sample is on byte 3 of a 4-byte boundary, * so that samples[1] is on a word boundary. * @param[in] usb pointer to the @p GrainuumUSB object. * @param[in] packet Buffer to store the read samples. * @api */ void grainuumCaptureI(struct GrainuumUSB *usb, uint8_t packet[12]); /** * @brief Internal function. Queues 8 bytes to be sent by the phy. * @note This is an internal function, and is not meant to be called. * It is meant to queue properly-formatted USB packets complete * with CRC-16 (if required). * @param[in] usb pointer to the @p GrainuumUSB object. * @param[in] epnum The endpoint number to queue data for. * @param[in] buffer The data to queue. * @param[in] size The number of bytes that are queued. * @notapi */ void grainuumWriteQueue(struct GrainuumUSB *usb, int epnum, const void *buffer, int size); /** * @brief Simulates plugging the device into USB. * @note All USB Connect hooks will be called. * The default USB state is "disconnected", * so @p grainuumConnect() must be called * to start communications. * @param[in] usb pointer to the @p GrainuumUSB object. * @api */ void grainuumConnect(struct GrainuumUSB *usb); /** * @brief Simulates unplugging the device from USB. * @note All USB Disconnect hooks will be called. * @param[in] usb pointer to the @p GrainuumUSB object. * @api */ void grainuumDisconnect(struct GrainuumUSB *usb); /** * @brief Reads one packet from the wire. * @note This must be called from an interrupt context with * interrupts disabled. * @param[in] usb Pointer to the @p GrainuumUSB object. * @param[out] samples Buffer where the samples will be stored. * @return The number of bytes read, or negative on error * @retval -1 Timeout while reading. * @retval -2 Read too many bits. * @retval -3 Unable to find sync end. * @retval -4 Probably a keepalive packet. * @notapi */ int usbPhyReadI(const struct GrainuumUSB *usb, uint8_t samples[11]); /** * @brief Writes one packet from the wire. * @note This must be called from an interrupt context with * interrupts disabled. * @param[in] usb Pointer to the @p GrainuumUSB object. * @param[in] samples Buffer where the samples will be stored. * @param[in] size Number of bytes to write. * @notapi */ void usbPhyWriteI(const struct GrainuumUSB *usb, const void *buffer, uint32_t size); #ifdef __cplusplus }; #endif #endif /* _GRAINUUM_H */