mirror of
https://github.com/gdsports/USBHost_t36
synced 2024-11-11 19:55:04 -05:00
4e2ea2d96e
instead of having each HUB have 7 buffers, which can eat up space. We have each main object contribute currently one string buffer, which than when we initialize a Device_t we try to allocate one for it, likewise we release it when the Device is released. Hopefully less memory needed. Also updated such that the HIDInput classes can not retrieve these strings. Changed test program to now also have list of HIDInput objects and when I detect a new one, I again print out info on it...
509 lines
13 KiB
C++
509 lines
13 KiB
C++
/* USB EHCI Host for Teensy 3.6
|
|
* Copyright 2017 Paul Stoffregen (paul@pjrc.com)
|
|
*
|
|
* 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 <Arduino.h>
|
|
#include "USBHost_t36.h" // Read this header first for key info
|
|
|
|
#define print USBHost::print_
|
|
#define println USBHost::println_
|
|
|
|
/************************************************************/
|
|
// Initialization and claiming of devices & interfaces
|
|
/************************************************************/
|
|
|
|
void USBSerial::init()
|
|
{
|
|
contribute_Pipes(mypipes, sizeof(mypipes)/sizeof(Pipe_t));
|
|
contribute_Transfers(mytransfers, sizeof(mytransfers)/sizeof(Transfer_t));
|
|
contribute_String_Buffers(mystring_bufs, sizeof(mystring_bufs)/sizeof(strbuf_t));
|
|
driver_ready_for_device(this);
|
|
}
|
|
|
|
bool USBSerial::claim(Device_t *dev, int type, const uint8_t *descriptors, uint32_t len)
|
|
{
|
|
// only claim at interface level
|
|
println("USBSerial claim this=", (uint32_t)this, HEX);
|
|
print("vid=", dev->idVendor, HEX);
|
|
println(", pid=", dev->idProduct, HEX);
|
|
if (type == 0) {
|
|
if (dev->idVendor == 0x0403 && dev->idProduct == 0x6001) {
|
|
// FTDI FT232
|
|
println("len = ", len);
|
|
if (len < 23) return false;
|
|
if (descriptors[0] != 9) return false; // length 9
|
|
if (descriptors[9] != 7) return false; // length 7
|
|
if (descriptors[10] != 5) return false; // ep desc
|
|
uint32_t rxep = descriptors[11];
|
|
if (descriptors[12] != 2) return false; // bulk type
|
|
if (descriptors[13] != 64) return false; // size 64
|
|
if (descriptors[14] != 0) return false;
|
|
if (descriptors[16] != 7) return false; // length 7
|
|
if (descriptors[17] != 5) return false; // ep desc
|
|
uint32_t txep = descriptors[18];
|
|
if (descriptors[19] != 2) return false; // bulk type
|
|
if (descriptors[20] != 64) return false; // size 64
|
|
if (descriptors[21] != 0) return false;
|
|
if (!check_rxtx_ep(rxep, txep)) return false;
|
|
print("FTDI, rxep=", rxep & 15);
|
|
println(", txep=", txep);
|
|
if (!init_buffers(64, 64)) return false;
|
|
rxpipe = new_Pipe(dev, 2, rxep & 15, 1, 64);
|
|
if (!rxpipe) return false;
|
|
txpipe = new_Pipe(dev, 2, txep, 0, 64);
|
|
if (!txpipe) {
|
|
// TODO: free rxpipe
|
|
return false;
|
|
}
|
|
sertype = FTDI;
|
|
rxpipe->callback_function = rx_callback;
|
|
queue_Data_Transfer(rxpipe, rx1, 64, this);
|
|
rxstate = 1;
|
|
if (rxsize > 128) {
|
|
queue_Data_Transfer(rxpipe, rx2, 64, this);
|
|
rxstate = 3;
|
|
}
|
|
txstate = 0;
|
|
txpipe->callback_function = tx_callback;
|
|
baudrate = 115200;
|
|
pending_control = 0x0F;
|
|
mk_setup(setup, 0x40, 0, 0, 0, 0); // reset port
|
|
queue_Control_Transfer(dev, &setup, NULL, this);
|
|
control_queued = true;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// check if two legal endpoints, 1 receive & 1 transmit
|
|
bool USBSerial::check_rxtx_ep(uint32_t &rxep, uint32_t &txep)
|
|
{
|
|
if ((rxep & 0x0F) == 0) return false;
|
|
if ((txep & 0x0F) == 0) return false;
|
|
uint32_t rxdir = rxep & 0xF0;
|
|
uint32_t txdir = txep & 0xF0;
|
|
if (rxdir == 0x80 && txdir == 0x00) {
|
|
return true;
|
|
}
|
|
if (rxdir == 0x00 && txdir == 0x80) {
|
|
std::swap(rxep, txep);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// initialize buffer sizes and pointers
|
|
bool USBSerial::init_buffers(uint32_t rsize, uint32_t tsize)
|
|
{
|
|
// buffer must be able to hold 2 of each packet, plus have room to
|
|
if (sizeof(bigbuffer) < (rsize + tsize) * 3 + 2) return false;
|
|
rx1 = (uint8_t *)bigbuffer;
|
|
rx2 = rx1 + rsize;
|
|
tx1 = rx2 + rsize;
|
|
tx2 = tx1 + tsize;
|
|
rxbuf = tx2 + tsize;
|
|
// FIXME: this assume 50-50 split - not true when rsize != tsize
|
|
rxsize = (sizeof(bigbuffer) - (rsize + tsize) * 2) / 2;
|
|
txsize = rxsize;
|
|
txbuf = rxbuf + rxsize;
|
|
rxhead = 0;
|
|
rxtail = 0;
|
|
txhead = 0;
|
|
txtail = 0;
|
|
rxstate = 0;
|
|
return true;
|
|
}
|
|
|
|
void USBSerial::disconnect()
|
|
{
|
|
}
|
|
|
|
|
|
/************************************************************/
|
|
// Control Transfer For Configuration
|
|
/************************************************************/
|
|
|
|
|
|
void USBSerial::control(const Transfer_t *transfer)
|
|
{
|
|
println("control callback (serial)");
|
|
control_queued = false;
|
|
|
|
// set data format
|
|
if (pending_control & 1) {
|
|
pending_control &= ~1;
|
|
mk_setup(setup, 0x40, 4, 8, 0, 0); // data format 8N1
|
|
queue_Control_Transfer(device, &setup, NULL, this);
|
|
control_queued = true;
|
|
return;
|
|
}
|
|
// set baud rate
|
|
if (pending_control & 2) {
|
|
pending_control &= ~2;
|
|
uint32_t baudval = 3000000 / baudrate;
|
|
mk_setup(setup, 0x40, 3, baudval, 0, 0);
|
|
queue_Control_Transfer(device, &setup, NULL, this);
|
|
control_queued = true;
|
|
return;
|
|
}
|
|
// configure flow control
|
|
if (pending_control & 4) {
|
|
pending_control &= ~4;
|
|
mk_setup(setup, 0x40, 2, 0, 0, 0);
|
|
queue_Control_Transfer(device, &setup, NULL, this);
|
|
control_queued = true;
|
|
return;
|
|
}
|
|
// set DTR
|
|
if (pending_control & 8) {
|
|
pending_control &= ~8;
|
|
mk_setup(setup, 0x40, 1, 0x0101, 0, 0);
|
|
queue_Control_Transfer(device, &setup, NULL, this);
|
|
control_queued = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************/
|
|
// Interrupt-based Data Movement
|
|
/************************************************************/
|
|
|
|
void USBSerial::rx_callback(const Transfer_t *transfer)
|
|
{
|
|
if (!transfer->driver) return;
|
|
((USBSerial *)(transfer->driver))->rx_data(transfer);
|
|
}
|
|
|
|
void USBSerial::tx_callback(const Transfer_t *transfer)
|
|
{
|
|
if (!transfer->driver) return;
|
|
((USBSerial *)(transfer->driver))->tx_data(transfer);
|
|
}
|
|
|
|
void USBSerial::rx_data(const Transfer_t *transfer)
|
|
{
|
|
uint32_t len = transfer->length - ((transfer->qtd.token >> 16) & 0x7FFF);
|
|
|
|
// first update rxstate bitmask, since buffer is no longer queued
|
|
if (transfer->buffer == rx1) {
|
|
rxstate &= 0xFE;
|
|
} else if (transfer->buffer == rx2) {
|
|
rxstate &= 0xFD;
|
|
}
|
|
// get start of data and actual length
|
|
const uint8_t *p = (const uint8_t *)transfer->buffer;
|
|
if (sertype == FTDI) {
|
|
if (len >= 2) {
|
|
p += 2;
|
|
len -= 2;
|
|
} else {
|
|
len = 0;
|
|
}
|
|
}
|
|
//if (len > 0) {
|
|
//print("rx: ");
|
|
//print_hexbytes(p, len);
|
|
//}
|
|
// Copy data from packet buffer to circular buffer.
|
|
// Assume the buffer will always have space, since we
|
|
// check before queuing the buffers
|
|
uint32_t head = rxhead;
|
|
uint32_t tail = rxtail;
|
|
if (++head >= rxsize) head = 0;
|
|
uint32_t avail;
|
|
if (len > 0) {
|
|
//print("head=", head);
|
|
//print(", tail=", tail);
|
|
avail = rxsize - head;
|
|
//print(", avail=", avail);
|
|
//println(", rxsize=", rxsize);
|
|
if (avail > len) avail = len;
|
|
memcpy(rxbuf + head, p, avail);
|
|
if (len <= avail) {
|
|
head += avail - 1;
|
|
if (head >= rxsize) head = 0;
|
|
} else {
|
|
head = len - avail - 1;
|
|
memcpy(rxbuf, p + avail, head + 1);
|
|
}
|
|
rxhead = head;
|
|
}
|
|
// TODO: can be this more efficient? We know from above which
|
|
// buffer is no longer queued, so possible skip most of this work?
|
|
rx_queue_packets(head, tail);
|
|
}
|
|
|
|
// re-queue packet buffer(s) if possible
|
|
void USBSerial::rx_queue_packets(uint32_t head, uint32_t tail)
|
|
{
|
|
uint32_t avail;
|
|
if (head >= tail) {
|
|
avail = rxsize - 1 - head + tail;
|
|
} else {
|
|
avail = tail - head - 1;
|
|
}
|
|
uint32_t packetsize = rx2 - rx1;
|
|
if (avail >= packetsize) {
|
|
if ((rxstate & 0x01) == 0) {
|
|
queue_Data_Transfer(rxpipe, rx1, packetsize, this);
|
|
rxstate |= 0x01;
|
|
} else if ((rxstate & 0x02) == 0) {
|
|
queue_Data_Transfer(rxpipe, rx2, packetsize, this);
|
|
rxstate |= 0x02;
|
|
}
|
|
if ((rxstate & 0x03) != 0x03 && avail >= packetsize * 2) {
|
|
if ((rxstate & 0x01) == 0) {
|
|
queue_Data_Transfer(rxpipe, rx1, packetsize, this);
|
|
rxstate |= 0x01;
|
|
} else if ((rxstate & 0x02) == 0) {
|
|
queue_Data_Transfer(rxpipe, rx2, packetsize, this);
|
|
rxstate |= 0x02;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void USBSerial::tx_data(const Transfer_t *transfer)
|
|
{
|
|
uint32_t mask;
|
|
uint8_t *p = (uint8_t *)transfer->buffer;
|
|
if (p == tx1) {
|
|
println("tx1:");
|
|
mask = 1;
|
|
//txstate &= 0xFE;
|
|
} else if (p == tx2) {
|
|
println("tx2:");
|
|
mask = 2;
|
|
//txstate &= 0xFD;
|
|
} else {
|
|
return; // should never happen
|
|
}
|
|
// check how much more data remains in the transmit buffer
|
|
uint32_t head = txhead;
|
|
uint32_t tail = txtail;
|
|
uint32_t count;
|
|
if (head >= tail) {
|
|
count = head - tail;
|
|
} else {
|
|
count = txsize + head - tail;
|
|
}
|
|
uint32_t packetsize = tx2 - tx1;
|
|
if (count < packetsize) {
|
|
// not enough data in buffer to fill a full packet
|
|
txstate &= ~mask;
|
|
return;
|
|
}
|
|
// immediately transmit another full packet, if we have enough data
|
|
println("TX:moar data!!!!");
|
|
if (++tail >= txsize) tail = 0;
|
|
uint32_t n = txsize - tail;
|
|
if (n > packetsize) n = packetsize;
|
|
memcpy(p, txbuf + tail, n);
|
|
if (n >= packetsize) {
|
|
tail += n - 1;
|
|
if (tail >= txsize) tail = 0;
|
|
} else {
|
|
uint32_t len = packetsize - n;
|
|
memcpy(p + n, txbuf, len);
|
|
tail = len - 1;
|
|
}
|
|
txtail = tail;
|
|
queue_Data_Transfer(txpipe, p, packetsize, this);
|
|
}
|
|
|
|
|
|
void USBSerial::timer_event(USBDriverTimer *whichTimer)
|
|
{
|
|
println("txtimer");
|
|
uint32_t count;
|
|
uint32_t head = txhead;
|
|
uint32_t tail = txtail;
|
|
if (head == tail) {
|
|
return; // nothing to transmit
|
|
} else if (head > tail) {
|
|
count = head - tail;
|
|
} else {
|
|
count = txsize + head - tail;
|
|
}
|
|
uint8_t *p;
|
|
if ((txstate & 0x01) == 0) {
|
|
p = tx1;
|
|
txstate |= 0x01;
|
|
} else if ((txstate & 0x02) == 0) {
|
|
p = tx2;
|
|
txstate |= 0x02;
|
|
} else {
|
|
txtimer.start(1200);
|
|
return; // no outgoing buffers available, try again later
|
|
}
|
|
if (++tail >= txsize) tail = 0;
|
|
uint32_t n = txsize - tail;
|
|
if (n > count) n = count;
|
|
memcpy(p, txbuf + tail, n);
|
|
if (n >= count) {
|
|
tail += n - 1;
|
|
if (tail >= txsize) tail = 0;
|
|
} else {
|
|
uint32_t len = count - n;
|
|
memcpy(p + n, txbuf, len);
|
|
tail = len - 1;
|
|
}
|
|
txtail = tail;
|
|
queue_Data_Transfer(txpipe, p, count, this);
|
|
}
|
|
|
|
|
|
|
|
/************************************************************/
|
|
// User Functions - must disable USBHQ IRQ for EHCI access
|
|
/************************************************************/
|
|
|
|
void USBSerial::begin(uint32_t baud, uint32_t format)
|
|
{
|
|
NVIC_DISABLE_IRQ(IRQ_USBHS);
|
|
baudrate = baud;
|
|
pending_control |= 2;
|
|
if (!control_queued) control(NULL);
|
|
NVIC_ENABLE_IRQ(IRQ_USBHS);
|
|
}
|
|
|
|
void USBSerial::end(void)
|
|
{
|
|
// TODO: lower DTR
|
|
}
|
|
|
|
int USBSerial::available(void)
|
|
{
|
|
if (!device) return 0;
|
|
uint32_t head = rxhead;
|
|
uint32_t tail = rxtail;
|
|
if (head >= tail) return head - tail;
|
|
return rxsize + head - tail;
|
|
}
|
|
|
|
int USBSerial::peek(void)
|
|
{
|
|
if (!device) return -1;
|
|
uint32_t head = rxhead;
|
|
uint32_t tail = rxtail;
|
|
if (head == tail) return -1;
|
|
if (++tail >= rxsize) tail = 0;
|
|
return rxbuf[tail];
|
|
}
|
|
|
|
int USBSerial::read(void)
|
|
{
|
|
if (!device) return -1;
|
|
uint32_t head = rxhead;
|
|
uint32_t tail = rxtail;
|
|
if (head == tail) return -1;
|
|
if (++tail >= rxsize) tail = 0;
|
|
int c = rxbuf[tail];
|
|
rxtail = tail;
|
|
if ((rxstate & 0x03) != 0x03) {
|
|
NVIC_DISABLE_IRQ(IRQ_USBHS);
|
|
rx_queue_packets(head, tail);
|
|
NVIC_ENABLE_IRQ(IRQ_USBHS);
|
|
}
|
|
return c;
|
|
}
|
|
|
|
int USBSerial::availableForWrite()
|
|
{
|
|
if (!device) return 0;
|
|
uint32_t head = txhead;
|
|
uint32_t tail = txtail;
|
|
if (head >= tail) return txsize - 1 - head + tail;
|
|
return tail - head - 1;
|
|
}
|
|
|
|
size_t USBSerial::write(uint8_t c)
|
|
{
|
|
if (!device) return 0;
|
|
uint32_t head = txhead;
|
|
if (++head >= txsize) head = 0;
|
|
while (txtail == head) {
|
|
// wait...
|
|
}
|
|
txbuf[head] = c;
|
|
txhead = head;
|
|
//print("head=", head);
|
|
//println(", tail=", txtail);
|
|
|
|
// if full packet in buffer and tx packet ready, queue it
|
|
NVIC_DISABLE_IRQ(IRQ_USBHS);
|
|
uint32_t tail = txtail;
|
|
if ((txstate & 0x03) != 0x03) {
|
|
// at least one packet buffer is ready to transmit
|
|
uint32_t count;
|
|
if (head >= tail) {
|
|
count = head - tail;
|
|
} else {
|
|
count = txsize + head - tail;
|
|
}
|
|
uint32_t packetsize = tx2 - tx1;
|
|
if (count >= packetsize) {
|
|
//println("txsize=", txsize);
|
|
uint8_t *p;
|
|
if ((txstate & 0x01) == 0) {
|
|
p = tx1;
|
|
txstate |= 0x01;
|
|
} else /* if ((txstate & 0x02) == 0) */ {
|
|
p = tx2;
|
|
txstate |= 0x02;
|
|
}
|
|
// copy data to packet buffer
|
|
if (++tail >= txsize) tail = 0;
|
|
uint32_t n = txsize - tail;
|
|
if (n > packetsize) n = packetsize;
|
|
//print("memcpy, offset=", tail);
|
|
//println(", len=", n);
|
|
memcpy(p, txbuf + tail, n);
|
|
if (n >= packetsize) {
|
|
tail += n - 1;
|
|
if (tail >= txsize) tail = 0;
|
|
} else {
|
|
//n = txsize - n;
|
|
uint32_t len = packetsize - n;
|
|
//println("memcpy, offset=0, len=", len);
|
|
memcpy(p + n, txbuf, len);
|
|
tail = len - 1;
|
|
}
|
|
txtail = tail;
|
|
//println("queue tx packet, newtail=", tail);
|
|
queue_Data_Transfer(txpipe, p, packetsize, this);
|
|
NVIC_ENABLE_IRQ(IRQ_USBHS);
|
|
return 1;
|
|
}
|
|
}
|
|
// otherwise, set a latency timer to later transmit partial packet
|
|
txtimer.stop();
|
|
txtimer.start(3500);
|
|
NVIC_ENABLE_IRQ(IRQ_USBHS);
|
|
return 1;
|
|
}
|
|
|
|
|
|
|