1362 lines
44 KiB
C
1362 lines
44 KiB
C
|
/**
|
||
|
* Copyright (c) 2021 sekigon-gonnoc
|
||
|
* Ha Thach (thach@tinyusb.org)
|
||
|
*/
|
||
|
|
||
|
#pragma GCC push_options
|
||
|
#pragma GCC optimize("-O3")
|
||
|
|
||
|
#include <stdbool.h>
|
||
|
#include <stdint.h>
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include "hardware/sync.h"
|
||
|
|
||
|
#include "pio_usb.h"
|
||
|
#include "pio_usb_ll.h"
|
||
|
#include "usb_crc.h"
|
||
|
#include "usb_rx.pio.h"
|
||
|
#include "usb_tx.pio.h"
|
||
|
|
||
|
static alarm_pool_t *_alarm_pool = NULL;
|
||
|
static repeating_timer_t sof_rt;
|
||
|
// The sof_count may be incremented and then read on different cores.
|
||
|
static volatile uint32_t sof_count = 0;
|
||
|
static bool timer_active;
|
||
|
|
||
|
static volatile bool cancel_timer_flag;
|
||
|
static volatile bool start_timer_flag;
|
||
|
static __unused uint32_t int_stat;
|
||
|
|
||
|
static bool sof_timer(repeating_timer_t *_rt);
|
||
|
|
||
|
//--------------------------------------------------------------------+
|
||
|
// Application API
|
||
|
//--------------------------------------------------------------------+
|
||
|
|
||
|
static void start_timer(alarm_pool_t *alarm_pool) {
|
||
|
if (timer_active) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (alarm_pool != NULL) {
|
||
|
alarm_pool_add_repeating_timer_us(alarm_pool, -1000, sof_timer, NULL,
|
||
|
&sof_rt);
|
||
|
}
|
||
|
|
||
|
timer_active = true;
|
||
|
}
|
||
|
|
||
|
static __unused void stop_timer(void) {
|
||
|
cancel_repeating_timer(&sof_rt);
|
||
|
timer_active = false;
|
||
|
}
|
||
|
|
||
|
usb_device_t *pio_usb_host_init(const pio_usb_configuration_t *c) {
|
||
|
pio_port_t *pp = PIO_USB_PIO_PORT(0);
|
||
|
root_port_t *root = PIO_USB_ROOT_PORT(0);
|
||
|
|
||
|
pio_usb_bus_init(pp, c, root);
|
||
|
root->mode = PIO_USB_MODE_HOST;
|
||
|
|
||
|
float const cpu_freq = (float)clock_get_hz(clk_sys);
|
||
|
pio_calculate_clkdiv_from_float(cpu_freq / 48000000,
|
||
|
&pp->clk_div_fs_tx.div_int,
|
||
|
&pp->clk_div_fs_tx.div_frac);
|
||
|
pio_calculate_clkdiv_from_float(cpu_freq / 6000000,
|
||
|
&pp->clk_div_ls_tx.div_int,
|
||
|
&pp->clk_div_ls_tx.div_frac);
|
||
|
|
||
|
pio_calculate_clkdiv_from_float(cpu_freq / 96000000,
|
||
|
&pp->clk_div_fs_rx.div_int,
|
||
|
&pp->clk_div_fs_rx.div_frac);
|
||
|
pio_calculate_clkdiv_from_float(cpu_freq / 12000000,
|
||
|
&pp->clk_div_ls_rx.div_int,
|
||
|
&pp->clk_div_ls_rx.div_frac);
|
||
|
|
||
|
if (!c->skip_alarm_pool) {
|
||
|
_alarm_pool = c->alarm_pool;
|
||
|
if (!_alarm_pool) {
|
||
|
_alarm_pool = alarm_pool_create(2, 1);
|
||
|
}
|
||
|
}
|
||
|
start_timer(_alarm_pool);
|
||
|
|
||
|
return &pio_usb_device[0];
|
||
|
}
|
||
|
|
||
|
void pio_usb_host_stop(void) {
|
||
|
cancel_timer_flag = true;
|
||
|
while (cancel_timer_flag) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void pio_usb_host_restart(void) {
|
||
|
start_timer_flag = true;
|
||
|
while (start_timer_flag) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------+
|
||
|
// Bus functions
|
||
|
//--------------------------------------------------------------------+
|
||
|
|
||
|
static void __no_inline_not_in_flash_func(override_pio_program)(PIO pio, const pio_program_t* program, uint offset) {
|
||
|
for (uint i = 0; i < program->length; ++i) {
|
||
|
uint16_t instr = program->instructions[i];
|
||
|
pio->instr_mem[offset + i] =
|
||
|
pio_instr_bits_jmp != _pio_major_instr_bits(instr) ? instr
|
||
|
: instr + offset;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static __always_inline void override_pio_rx_program(PIO pio,
|
||
|
const pio_program_t *program,
|
||
|
const pio_program_t *debug_program,
|
||
|
uint offset, int debug_pin) {
|
||
|
if (debug_pin < 0) {
|
||
|
override_pio_program(pio, program, offset);
|
||
|
} else {
|
||
|
override_pio_program(pio, debug_program, offset);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void __no_inline_not_in_flash_func(configure_fullspeed_host)(
|
||
|
pio_port_t const *pp, root_port_t *port) {
|
||
|
override_pio_program(pp->pio_usb_tx, pp->fs_tx_program, pp->offset_tx);
|
||
|
SM_SET_CLKDIV(pp->pio_usb_tx, pp->sm_tx, pp->clk_div_fs_tx);
|
||
|
|
||
|
pio_sm_set_jmp_pin(pp->pio_usb_rx, pp->sm_rx, port->pin_dp);
|
||
|
SM_SET_CLKDIV_MAXSPEED(pp->pio_usb_rx, pp->sm_rx);
|
||
|
|
||
|
pio_sm_set_jmp_pin(pp->pio_usb_rx, pp->sm_eop, port->pin_dm);
|
||
|
pio_sm_set_in_pins(pp->pio_usb_rx, pp->sm_eop, port->pin_dp);
|
||
|
SM_SET_CLKDIV(pp->pio_usb_rx, pp->sm_eop, pp->clk_div_fs_rx);
|
||
|
|
||
|
usb_tx_configure_pins(pp->pio_usb_tx, pp->sm_tx, port->pin_dp, port->pin_dm);
|
||
|
}
|
||
|
|
||
|
static void __no_inline_not_in_flash_func(configure_lowspeed_host)(
|
||
|
pio_port_t const *pp, root_port_t *port) {
|
||
|
override_pio_program(pp->pio_usb_tx, pp->ls_tx_program, pp->offset_tx);
|
||
|
SM_SET_CLKDIV(pp->pio_usb_tx, pp->sm_tx, pp->clk_div_ls_tx);
|
||
|
|
||
|
pio_sm_set_jmp_pin(pp->pio_usb_rx, pp->sm_rx, port->pin_dm);
|
||
|
SM_SET_CLKDIV_MAXSPEED(pp->pio_usb_rx, pp->sm_rx);
|
||
|
|
||
|
pio_sm_set_jmp_pin(pp->pio_usb_rx, pp->sm_eop, port->pin_dp);
|
||
|
pio_sm_set_in_pins(pp->pio_usb_rx, pp->sm_eop, port->pin_dm);
|
||
|
SM_SET_CLKDIV(pp->pio_usb_rx, pp->sm_eop, pp->clk_div_ls_rx);
|
||
|
|
||
|
usb_tx_configure_pins(pp->pio_usb_tx, pp->sm_tx, port->pin_dp, port->pin_dm);
|
||
|
}
|
||
|
|
||
|
static void __no_inline_not_in_flash_func(configure_root_port)(
|
||
|
pio_port_t *pp, root_port_t *root) {
|
||
|
if (root->is_fullspeed) {
|
||
|
configure_fullspeed_host(pp, root);
|
||
|
} else {
|
||
|
configure_lowspeed_host(pp, root);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void __no_inline_not_in_flash_func(restore_fs_bus)(const pio_port_t *pp) {
|
||
|
// change bus speed to full-speed
|
||
|
pio_sm_set_enabled(pp->pio_usb_tx, pp->sm_tx, false);
|
||
|
SM_SET_CLKDIV(pp->pio_usb_tx, pp->sm_tx, pp->clk_div_fs_tx);
|
||
|
|
||
|
pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_rx, false);
|
||
|
SM_SET_CLKDIV_MAXSPEED(pp->pio_usb_rx, pp->sm_rx);
|
||
|
pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_rx, true);
|
||
|
|
||
|
pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_eop, false);
|
||
|
SM_SET_CLKDIV(pp->pio_usb_rx, pp->sm_eop, pp->clk_div_fs_rx);
|
||
|
pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_eop, true);
|
||
|
}
|
||
|
|
||
|
// Time about 1us ourselves so it lives in RAM.
|
||
|
static void __not_in_flash_func(busy_wait_1_us)(void) {
|
||
|
uint32_t start = timer_hw->timerawl;
|
||
|
while (timer_hw->timerawl == start) {
|
||
|
tight_loop_contents();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static bool __no_inline_not_in_flash_func(connection_check)(root_port_t *port) {
|
||
|
if (pio_usb_bus_get_line_state(port) == PORT_PIN_SE0) {
|
||
|
busy_wait_1_us();
|
||
|
|
||
|
if (pio_usb_bus_get_line_state(port) == PORT_PIN_SE0) {
|
||
|
busy_wait_1_us();
|
||
|
// device disconnect
|
||
|
port->connected = false;
|
||
|
port->suspended = true;
|
||
|
port->ints |= PIO_USB_INTS_DISCONNECT_BITS;
|
||
|
|
||
|
// failed/retired all queuing transfer in this root
|
||
|
uint8_t root_idx = port - PIO_USB_ROOT_PORT(0);
|
||
|
for (int ep_idx = 0; ep_idx < PIO_USB_EP_POOL_CNT; ep_idx++) {
|
||
|
endpoint_t *ep = PIO_USB_ENDPOINT(ep_idx);
|
||
|
if ((ep->root_idx == root_idx) && ep->size && ep->has_transfer) {
|
||
|
pio_usb_ll_transfer_complete(ep, PIO_USB_INTS_ENDPOINT_ERROR_BITS);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------+
|
||
|
// SOF
|
||
|
//--------------------------------------------------------------------+
|
||
|
static int usb_setup_transaction(pio_port_t *pp, endpoint_t *ep);
|
||
|
static int usb_in_transaction(pio_port_t *pp, endpoint_t *ep);
|
||
|
static int usb_out_transaction(pio_port_t *pp, endpoint_t *ep);
|
||
|
|
||
|
void __not_in_flash_func(pio_usb_host_frame)(void) {
|
||
|
if (!timer_active) {
|
||
|
return;
|
||
|
}
|
||
|
static uint8_t sof_packet[4] = {USB_SYNC, USB_PID_SOF, 0x00, 0x10};
|
||
|
|
||
|
pio_port_t *pp = PIO_USB_PIO_PORT(0);
|
||
|
|
||
|
// Send SOF
|
||
|
for (int root_idx = 0; root_idx < PIO_USB_ROOT_PORT_CNT; root_idx++) {
|
||
|
root_port_t *root = PIO_USB_ROOT_PORT(root_idx);
|
||
|
if (!(root->initialized && root->connected && !root->suspended &&
|
||
|
connection_check(root))) {
|
||
|
continue;
|
||
|
}
|
||
|
configure_root_port(pp, root);
|
||
|
pio_usb_bus_usb_transfer(pp, sof_packet, 4);
|
||
|
}
|
||
|
|
||
|
// Carry out all queued endpoint transaction
|
||
|
for (int root_idx = 0; root_idx < PIO_USB_ROOT_PORT_CNT; root_idx++) {
|
||
|
root_port_t *root = PIO_USB_ROOT_PORT(root_idx);
|
||
|
if (!(root->initialized && root->connected && !root->suspended)) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
configure_root_port(pp, root);
|
||
|
|
||
|
for (int ep_pool_idx = 0; ep_pool_idx < PIO_USB_EP_POOL_CNT;
|
||
|
ep_pool_idx++) {
|
||
|
endpoint_t *ep = PIO_USB_ENDPOINT(ep_pool_idx);
|
||
|
if ((ep->root_idx == root_idx) && ep->size) {
|
||
|
bool const is_periodic = ((ep->attr & 0x03) == EP_ATTR_INTERRUPT);
|
||
|
|
||
|
if (is_periodic && (ep->interval_counter > 0)) {
|
||
|
ep->interval_counter--;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (ep->has_transfer && !ep->transfer_aborted) {
|
||
|
ep->transfer_started = true;
|
||
|
|
||
|
if (ep->need_pre) {
|
||
|
pp->need_pre = true;
|
||
|
}
|
||
|
|
||
|
if (ep->ep_num == 0 && ep->data_id == USB_PID_SETUP) {
|
||
|
usb_setup_transaction(pp, ep);
|
||
|
} else {
|
||
|
if (ep->ep_num & EP_IN) {
|
||
|
usb_in_transaction(pp, ep);
|
||
|
} else {
|
||
|
usb_out_transaction(pp, ep);
|
||
|
}
|
||
|
|
||
|
if (is_periodic) {
|
||
|
ep->interval_counter = ep->interval - 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (ep->need_pre) {
|
||
|
pp->need_pre = false;
|
||
|
restore_fs_bus(pp);
|
||
|
}
|
||
|
|
||
|
ep->transfer_started = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check for new connection to root hub
|
||
|
for (int root_idx = 0; root_idx < PIO_USB_ROOT_PORT_CNT; root_idx++) {
|
||
|
root_port_t *root = PIO_USB_ROOT_PORT(root_idx);
|
||
|
if (root->initialized && !root->connected) {
|
||
|
port_pin_status_t const line_state = pio_usb_bus_get_line_state(root);
|
||
|
if (line_state == PORT_PIN_FS_IDLE || line_state == PORT_PIN_LS_IDLE) {
|
||
|
root->is_fullspeed = (line_state == PORT_PIN_FS_IDLE);
|
||
|
root->connected = true;
|
||
|
root->suspended = true; // need a bus reset before operating
|
||
|
root->ints |= PIO_USB_INTS_CONNECT_BITS;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Invoke IRQHandler if interrupt status is set
|
||
|
for (uint8_t root_idx = 0; root_idx < PIO_USB_ROOT_PORT_CNT; root_idx++) {
|
||
|
if (PIO_USB_ROOT_PORT(root_idx)->ints) {
|
||
|
pio_usb_host_irq_handler(root_idx);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sof_count++;
|
||
|
|
||
|
// SOF counter is 11-bit
|
||
|
uint16_t const sof_count_11b = sof_count & 0x7ff;
|
||
|
sof_packet[2] = sof_count_11b & 0xff;
|
||
|
sof_packet[3] = (calc_usb_crc5(sof_count_11b) << 3) | (sof_count_11b >> 8);
|
||
|
}
|
||
|
|
||
|
static bool __no_inline_not_in_flash_func(sof_timer)(repeating_timer_t *_rt) {
|
||
|
(void)_rt;
|
||
|
|
||
|
pio_usb_host_frame();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------+
|
||
|
// Host Controller functions
|
||
|
//--------------------------------------------------------------------+
|
||
|
|
||
|
uint32_t pio_usb_host_get_frame_number(void) {
|
||
|
return sof_count;
|
||
|
}
|
||
|
|
||
|
void pio_usb_host_port_reset_start(uint8_t root_idx) {
|
||
|
root_port_t *root = PIO_USB_ROOT_PORT(root_idx);
|
||
|
pio_port_t *pp = PIO_USB_PIO_PORT(0);
|
||
|
|
||
|
// bus is not operating while in reset
|
||
|
root->suspended = true;
|
||
|
|
||
|
// Force line state to SE0
|
||
|
pio_sm_set_pins_with_mask(pp->pio_usb_tx, pp->sm_tx, 0,
|
||
|
(1 << root->pin_dp) | (1 << root->pin_dm));
|
||
|
pio_sm_set_pindirs_with_mask(pp->pio_usb_tx, pp->sm_tx,
|
||
|
(1 << root->pin_dp) | (1 << root->pin_dm),
|
||
|
(1 << root->pin_dp) | (1 << root->pin_dm));
|
||
|
}
|
||
|
|
||
|
void pio_usb_host_port_reset_end(uint8_t root_idx) {
|
||
|
root_port_t *root = PIO_USB_ROOT_PORT(root_idx);
|
||
|
pio_port_t *pp = PIO_USB_PIO_PORT(0);
|
||
|
|
||
|
// line state to input
|
||
|
pio_sm_set_pindirs_with_mask(pp->pio_usb_tx, pp->sm_tx, 0,
|
||
|
(1 << root->pin_dp) | (1 << root->pin_dm));
|
||
|
|
||
|
busy_wait_us(100); // TODO check if this is neccessary
|
||
|
|
||
|
// bus back to operating
|
||
|
root->suspended = false;
|
||
|
}
|
||
|
|
||
|
void pio_usb_host_close_device(uint8_t root_idx, uint8_t device_address) {
|
||
|
for (int ep_pool_idx = 0; ep_pool_idx < PIO_USB_EP_POOL_CNT; ep_pool_idx++) {
|
||
|
endpoint_t *ep = PIO_USB_ENDPOINT(ep_pool_idx);
|
||
|
if ((ep->root_idx == root_idx) && (ep->dev_addr == device_address) &&
|
||
|
ep->size) {
|
||
|
ep->size = 0;
|
||
|
ep->has_transfer = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline __force_inline endpoint_t * _find_ep(uint8_t root_idx,
|
||
|
uint8_t device_address, uint8_t ep_address) {
|
||
|
for (int ep_pool_idx = 0; ep_pool_idx < PIO_USB_EP_POOL_CNT; ep_pool_idx++) {
|
||
|
endpoint_t *ep = PIO_USB_ENDPOINT(ep_pool_idx);
|
||
|
// note 0x00 and 0x80 are matched as control endpoint of opposite direction
|
||
|
if ((ep->root_idx == root_idx) && (ep->dev_addr == device_address) &&
|
||
|
ep->size &&
|
||
|
((ep->ep_num == ep_address) ||
|
||
|
(((ep_address & 0x7f) == 0) && ((ep->ep_num & 0x7f) == 0)))) {
|
||
|
return ep;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
bool pio_usb_host_endpoint_open(uint8_t root_idx, uint8_t device_address,
|
||
|
uint8_t const *desc_endpoint, bool need_pre) {
|
||
|
const endpoint_descriptor_t *d = (const endpoint_descriptor_t *)desc_endpoint;
|
||
|
for (int ep_pool_idx = 0; ep_pool_idx < PIO_USB_EP_POOL_CNT; ep_pool_idx++) {
|
||
|
endpoint_t *ep = PIO_USB_ENDPOINT(ep_pool_idx);
|
||
|
// ep size is used as valid indicator
|
||
|
if (ep->size == 0) {
|
||
|
pio_usb_ll_configure_endpoint(ep, desc_endpoint);
|
||
|
ep->root_idx = root_idx;
|
||
|
ep->dev_addr = device_address;
|
||
|
ep->need_pre = need_pre;
|
||
|
ep->is_tx = (d->epaddr & 0x80) ? false : true; // host endpoint out is tx
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool pio_usb_host_send_setup(uint8_t root_idx, uint8_t device_address,
|
||
|
uint8_t const setup_packet[8]) {
|
||
|
endpoint_t *ep = _find_ep(root_idx, device_address, 0);
|
||
|
if (!ep) {
|
||
|
printf("cannot find ep 0x00\r\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
ep->ep_num = 0; // setup is is OUT
|
||
|
ep->data_id = USB_PID_SETUP;
|
||
|
ep->is_tx = true;
|
||
|
|
||
|
return pio_usb_ll_transfer_start(ep, (uint8_t *)setup_packet, 8);
|
||
|
}
|
||
|
|
||
|
bool pio_usb_host_endpoint_transfer(uint8_t root_idx, uint8_t device_address,
|
||
|
uint8_t ep_address, uint8_t *buffer,
|
||
|
uint16_t buflen) {
|
||
|
endpoint_t *ep = _find_ep(root_idx, device_address, ep_address);
|
||
|
if (!ep) {
|
||
|
printf("no endpoint 0x%02X\r\n", ep_address);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Control endpoint, address may switch between 0x00 <-> 0x80
|
||
|
// therefore we need to update ep_num and is_tx
|
||
|
if ((ep_address & 0x7f) == 0) {
|
||
|
ep->ep_num = ep_address;
|
||
|
ep->is_tx = ep_address == 0;
|
||
|
ep->data_id = 1; // data and status always start with DATA1
|
||
|
}
|
||
|
|
||
|
return pio_usb_ll_transfer_start(ep, buffer, buflen);
|
||
|
}
|
||
|
|
||
|
bool pio_usb_host_endpoint_abort_transfer(uint8_t root_idx, uint8_t device_address,
|
||
|
uint8_t ep_address) {
|
||
|
endpoint_t *ep = _find_ep(root_idx, device_address, ep_address);
|
||
|
if (!ep) {
|
||
|
printf("no endpoint 0x%02X\r\n", ep_address);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!ep->has_transfer) {
|
||
|
return false; // no transfer to abort
|
||
|
}
|
||
|
|
||
|
// mark transfer as aborted
|
||
|
ep->transfer_aborted = true;
|
||
|
|
||
|
// Race potential: SOF timer can be called before transfer_aborted is actually set
|
||
|
// and started the transfer. Wait 1 usb frame for transaction to complete.
|
||
|
// On the next SOF timer, transfer_aborted will be checked and skipped
|
||
|
while (ep->has_transfer && ep->transfer_started) {
|
||
|
busy_wait_ms(1);
|
||
|
}
|
||
|
|
||
|
// check if transfer is still active (could be completed)
|
||
|
bool const still_active = ep->has_transfer;
|
||
|
if (still_active) {
|
||
|
ep->has_transfer = false;
|
||
|
}
|
||
|
ep->transfer_aborted = false;
|
||
|
|
||
|
return still_active; // still active means transfer is successfully aborted
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------+
|
||
|
// Transaction helper
|
||
|
//--------------------------------------------------------------------+
|
||
|
|
||
|
static int __no_inline_not_in_flash_func(usb_in_transaction)(pio_port_t *pp,
|
||
|
endpoint_t *ep) {
|
||
|
int res = 0;
|
||
|
uint8_t expect_pid = (ep->data_id == 1) ? USB_PID_DATA1 : USB_PID_DATA0;
|
||
|
|
||
|
pio_usb_bus_prepare_receive(pp);
|
||
|
pio_usb_bus_send_token(pp, USB_PID_IN, ep->dev_addr, ep->ep_num);
|
||
|
pio_usb_bus_start_receive(pp);
|
||
|
|
||
|
int receive_len = pio_usb_bus_receive_packet_and_handshake(pp, USB_PID_ACK);
|
||
|
uint8_t const receive_pid = pp->usb_rx_buffer[1];
|
||
|
|
||
|
if (receive_len >= 0) {
|
||
|
if (receive_pid == expect_pid) {
|
||
|
memcpy(ep->app_buf, &pp->usb_rx_buffer[2], receive_len);
|
||
|
pio_usb_ll_transfer_continue(ep, receive_len);
|
||
|
} else {
|
||
|
// DATA0/1 mismatched, 0 for re-try next frame
|
||
|
}
|
||
|
} else if (receive_pid == USB_PID_NAK) {
|
||
|
// NAK try again next frame
|
||
|
} else if (receive_pid == USB_PID_STALL) {
|
||
|
pio_usb_ll_transfer_complete(ep, PIO_USB_INTS_ENDPOINT_STALLED_BITS);
|
||
|
} else {
|
||
|
res = -1;
|
||
|
if ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0) {
|
||
|
res = -2;
|
||
|
}
|
||
|
pio_usb_ll_transfer_complete(ep, PIO_USB_INTS_ENDPOINT_ERROR_BITS);
|
||
|
}
|
||
|
|
||
|
pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_rx, false);
|
||
|
pp->usb_rx_buffer[0] = 0;
|
||
|
pp->usb_rx_buffer[1] = 0;
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static int __no_inline_not_in_flash_func(usb_out_transaction)(pio_port_t *pp,
|
||
|
endpoint_t *ep) {
|
||
|
int res = 0;
|
||
|
|
||
|
uint16_t const xact_len = pio_usb_ll_get_transaction_len(ep);
|
||
|
|
||
|
pio_usb_bus_prepare_receive(pp);
|
||
|
pio_usb_bus_send_token(pp, USB_PID_OUT, ep->dev_addr, ep->ep_num);
|
||
|
// ensure previous tx complete
|
||
|
while ((pp->pio_usb_tx->irq & IRQ_TX_COMP_MASK) == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
pio_usb_bus_usb_transfer(pp, ep->buffer, xact_len + 4);
|
||
|
pio_usb_bus_start_receive(pp);
|
||
|
|
||
|
pio_usb_bus_wait_handshake(pp);
|
||
|
|
||
|
uint8_t const receive_token = pp->usb_rx_buffer[1];
|
||
|
|
||
|
if (receive_token == USB_PID_ACK) {
|
||
|
pio_usb_ll_transfer_continue(ep, xact_len);
|
||
|
} else if (receive_token == USB_PID_NAK) {
|
||
|
// NAK try again next frame
|
||
|
} else if (receive_token == USB_PID_STALL) {
|
||
|
pio_usb_ll_transfer_complete(ep, PIO_USB_INTS_ENDPOINT_STALLED_BITS);
|
||
|
} else {
|
||
|
pio_usb_ll_transfer_complete(ep, PIO_USB_INTS_ENDPOINT_ERROR_BITS);
|
||
|
}
|
||
|
|
||
|
pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_rx, false);
|
||
|
pp->usb_rx_buffer[0] = 0;
|
||
|
pp->usb_rx_buffer[1] = 0;
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static int __no_inline_not_in_flash_func(usb_setup_transaction)(
|
||
|
pio_port_t *pp, endpoint_t *ep) {
|
||
|
|
||
|
int res = 0;
|
||
|
|
||
|
// Setup token
|
||
|
pio_usb_bus_prepare_receive(pp);
|
||
|
|
||
|
pio_usb_bus_send_token(pp, USB_PID_SETUP, ep->dev_addr, 0);
|
||
|
// ensure previous tx complete
|
||
|
while ((pp->pio_usb_tx->irq & IRQ_TX_COMP_MASK) == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Data
|
||
|
ep->data_id = 0; // set to DATA0
|
||
|
pio_usb_bus_usb_transfer(pp, ep->buffer, 12);
|
||
|
|
||
|
// Handshake
|
||
|
pio_usb_bus_start_receive(pp);
|
||
|
pio_usb_bus_wait_handshake(pp);
|
||
|
|
||
|
ep->actual_len = 8;
|
||
|
|
||
|
if (pp->usb_rx_buffer[0] == USB_SYNC && pp->usb_rx_buffer[1] == USB_PID_ACK) {
|
||
|
pio_usb_ll_transfer_complete(ep, PIO_USB_INTS_ENDPOINT_COMPLETE_BITS);
|
||
|
} else {
|
||
|
res = -1;
|
||
|
pio_usb_ll_transfer_complete(ep, PIO_USB_INTS_ENDPOINT_ERROR_BITS);
|
||
|
}
|
||
|
|
||
|
pp->usb_rx_buffer[1] = 0; // reset buffer
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------+
|
||
|
// USB Host Stack
|
||
|
//--------------------------------------------------------------------+
|
||
|
static void on_device_connect(pio_port_t *pp, root_port_t *root,
|
||
|
int device_idx) {
|
||
|
bool fullspeed_flag = false;
|
||
|
|
||
|
if (pio_usb_bus_get_line_state(root) == PORT_PIN_FS_IDLE) {
|
||
|
fullspeed_flag = true;
|
||
|
} else if (pio_usb_bus_get_line_state(root) == PORT_PIN_LS_IDLE) {
|
||
|
fullspeed_flag = false;
|
||
|
}
|
||
|
|
||
|
pio_sm_set_pins_with_mask(pp->pio_usb_tx, pp->sm_tx, 0,
|
||
|
(1 << root->pin_dp) | (1 << root->pin_dm));
|
||
|
pio_sm_set_pindirs_with_mask(pp->pio_usb_tx, pp->sm_tx,
|
||
|
(1 << root->pin_dp) | (1 << root->pin_dm),
|
||
|
(1 << root->pin_dp) | (1 << root->pin_dm));
|
||
|
|
||
|
busy_wait_ms(100);
|
||
|
|
||
|
pio_sm_set_pindirs_with_mask(pp->pio_usb_tx, pp->sm_tx, 0,
|
||
|
(1 << root->pin_dp) | (1 << root->pin_dm));
|
||
|
|
||
|
busy_wait_us(100);
|
||
|
|
||
|
root->suspended = false;
|
||
|
|
||
|
if (fullspeed_flag && pio_usb_bus_get_line_state(root) == PORT_PIN_FS_IDLE) {
|
||
|
root->root_device = &pio_usb_device[device_idx];
|
||
|
if (!root->root_device->connected) {
|
||
|
configure_fullspeed_host(pp, root);
|
||
|
root->root_device->is_fullspeed = true;
|
||
|
root->root_device->is_root = true;
|
||
|
root->root_device->connected = true;
|
||
|
root->root_device->root = root;
|
||
|
root->root_device->event = EVENT_CONNECT;
|
||
|
}
|
||
|
} else if (!fullspeed_flag && pio_usb_bus_get_line_state(root) == PORT_PIN_LS_IDLE) {
|
||
|
root->root_device = &pio_usb_device[device_idx];
|
||
|
if (!root->root_device->connected) {
|
||
|
configure_lowspeed_host(pp, root);
|
||
|
root->root_device->is_fullspeed = false;
|
||
|
root->root_device->is_root = true;
|
||
|
root->root_device->connected = true;
|
||
|
root->root_device->root = root;
|
||
|
root->root_device->event = EVENT_CONNECT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
endpoint_descriptor_t ep0_desc = {
|
||
|
sizeof(endpoint_descriptor_t), DESC_TYPE_ENDPOINT, 0x00, 0x00, { 0x08, 0x00 }, 0x00
|
||
|
};
|
||
|
pio_usb_host_endpoint_open(root - pio_usb_root_port, 0x00,
|
||
|
(uint8_t const *)&ep0_desc, false);
|
||
|
}
|
||
|
|
||
|
static int __no_inline_not_in_flash_func(control_out_protocol)(
|
||
|
usb_device_t *device, uint8_t *setup_data, uint16_t setup_length,
|
||
|
uint8_t *out_data, uint16_t out_length) {
|
||
|
int res = 0;
|
||
|
|
||
|
control_pipe_t *pipe = &device->control_pipe;
|
||
|
|
||
|
if (pipe->operation == CONTROL_NONE) {
|
||
|
pipe->setup_packet.tx_address = setup_data;
|
||
|
pipe->setup_packet.tx_length = setup_length;
|
||
|
pipe->out_data_packet.tx_address = out_data;
|
||
|
pipe->out_data_packet.tx_length = out_length;
|
||
|
pipe->operation = CONTROL_OUT;
|
||
|
|
||
|
pio_usb_host_send_setup(device->root - pio_usb_root_port, device->address, setup_data);
|
||
|
} else {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
const uint64_t timeout = 5000 * 1000; // 5s
|
||
|
uint64_t start_time = time_us_64();
|
||
|
while (pipe->operation == CONTROL_OUT &&
|
||
|
time_us_64() - start_time < timeout) {
|
||
|
if (!device->connected) {
|
||
|
pipe->operation = CONTROL_NONE;
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (time_us_64() - start_time >= timeout) {
|
||
|
printf("control out[timeout]\n");
|
||
|
res = -2;
|
||
|
} else if (pipe->operation == CONTROL_ERROR) {
|
||
|
printf("control out[error]\n");
|
||
|
res = -1;
|
||
|
} else if (pipe->operation == CONTROL_COMPLETE) {
|
||
|
printf("control out[complete]\n");
|
||
|
res = 0;
|
||
|
}
|
||
|
pipe->operation = CONTROL_NONE;
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static int __no_inline_not_in_flash_func(control_in_protocol)(
|
||
|
usb_device_t *device, uint8_t *tx_data, uint16_t tx_length,
|
||
|
uint8_t *rx_buffer, uint16_t request_length) {
|
||
|
int res = 0;
|
||
|
|
||
|
control_pipe_t *pipe = &device->control_pipe;
|
||
|
|
||
|
if (pipe->operation == CONTROL_NONE) {
|
||
|
pipe->setup_packet.tx_address = tx_data;
|
||
|
pipe->setup_packet.tx_length = tx_length;
|
||
|
pipe->rx_buffer = rx_buffer;
|
||
|
pipe->request_length = request_length;
|
||
|
pipe->operation = CONTROL_IN;
|
||
|
|
||
|
pio_usb_host_send_setup(device->root - pio_usb_root_port, device->address, tx_data);
|
||
|
} else {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
const uint64_t timeout = 5000 * 1000; // 5s
|
||
|
uint64_t start_time = time_us_64();
|
||
|
while (pipe->operation == CONTROL_IN &&
|
||
|
time_us_64() - start_time < timeout) {
|
||
|
if (!device->connected) {
|
||
|
pipe->operation = CONTROL_NONE;
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (time_us_64() - start_time >= timeout) {
|
||
|
printf("control in[timeout]\n");
|
||
|
res = -2;
|
||
|
} else if (pipe->operation == CONTROL_ERROR) {
|
||
|
printf("control in[error]\n");
|
||
|
res = -1;
|
||
|
} else if (pipe->operation == CONTROL_COMPLETE) {
|
||
|
printf("control in[complete]\n");
|
||
|
res = 0;
|
||
|
}
|
||
|
pipe->operation = CONTROL_NONE;
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static int set_hub_feature(usb_device_t *device, uint8_t port, uint8_t value) {
|
||
|
usb_setup_packet_t req = SET_HUB_FEATURE_REQUEST;
|
||
|
req.index_lsb = port + 1;
|
||
|
req.value_lsb = value;
|
||
|
return control_out_protocol(device, (uint8_t *)&req, sizeof(req), NULL, 0);
|
||
|
}
|
||
|
|
||
|
static int clear_hub_feature(usb_device_t *device, uint8_t port,
|
||
|
uint8_t value) {
|
||
|
usb_setup_packet_t req = CLEAR_HUB_FEATURE_REQUEST;
|
||
|
req.index_lsb = port + 1;
|
||
|
req.value_lsb = value;
|
||
|
return control_out_protocol(device, (uint8_t *)&req, sizeof(req), NULL, 0);
|
||
|
}
|
||
|
|
||
|
static int get_hub_port_status(usb_device_t *device, uint8_t port,
|
||
|
hub_port_status_t *status) {
|
||
|
usb_setup_packet_t req = GET_HUB_PORT_STATUS_REQUEST;
|
||
|
req.index_lsb = port + 1;
|
||
|
return control_in_protocol(device, (uint8_t *)&req, sizeof(req), (uint8_t*)status,
|
||
|
sizeof(*status));
|
||
|
}
|
||
|
|
||
|
static int initialize_hub(usb_device_t *device) {
|
||
|
uint8_t rx_buffer[16];
|
||
|
int res = 0;
|
||
|
printf("USB Hub detected\n");
|
||
|
usb_setup_packet_t get_hub_desc_request = GET_HUB_DESCRPTOR_REQUEST;
|
||
|
control_in_protocol(device, (uint8_t *)&get_hub_desc_request,
|
||
|
sizeof(get_hub_desc_request), rx_buffer, 8);
|
||
|
const hub_descriptor_t *desc = (hub_descriptor_t *)rx_buffer;
|
||
|
uint8_t port_num = desc->port_num;
|
||
|
|
||
|
printf("\tTurn on port powers\n");
|
||
|
for (int idx = 0; idx < port_num; idx++) {
|
||
|
res = set_hub_feature(device, idx, HUB_SET_PORT_POWER);
|
||
|
if (res != 0) {
|
||
|
printf("\tFailed to turn on ports\n");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
busy_wait_ms(500);
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static int get_string_descriptor(usb_device_t *device, uint8_t idx,
|
||
|
uint8_t *rx_buffer, uint8_t *str_buffer) {
|
||
|
int res = -1;
|
||
|
usb_setup_packet_t req = GET_DEVICE_DESCRIPTOR_REQ_DEFAULT;
|
||
|
req.value_msb = DESC_TYPE_STRING;
|
||
|
req.value_lsb = idx;
|
||
|
req.length_lsb = 1;
|
||
|
req.length_msb = 0;
|
||
|
res = control_in_protocol(device, (uint8_t *)&req, sizeof(req), rx_buffer, 1);
|
||
|
if (res != 0) {
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
uint8_t len = rx_buffer[0];
|
||
|
req.length_lsb = len;
|
||
|
req.length_msb = 0;
|
||
|
res =
|
||
|
control_in_protocol(device, (uint8_t *)&req, sizeof(req), rx_buffer, len);
|
||
|
if (res != 0) {
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
uint16_t *wchar_buffer = (uint16_t *)(uintptr_t) rx_buffer;
|
||
|
for (int i = 0; i < (len - 2) / 2; i++) {
|
||
|
str_buffer[i] = wchar_buffer[i + 1];
|
||
|
}
|
||
|
str_buffer[(len - 2) / 2] = '\0';
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static int enumerate_device(usb_device_t *device, uint8_t address) {
|
||
|
int res = 0;
|
||
|
uint8_t rx_buffer[512];
|
||
|
|
||
|
usb_setup_packet_t get_device_descriptor_request =
|
||
|
GET_DEVICE_DESCRIPTOR_REQ_DEFAULT;
|
||
|
res = control_in_protocol(device, (uint8_t *)&get_device_descriptor_request,
|
||
|
sizeof(get_device_descriptor_request), rx_buffer, 18);
|
||
|
if (res != 0) {
|
||
|
pio_usb_host_close_device(device->root - pio_usb_root_port, 0);
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
const device_descriptor_t *desc =
|
||
|
(device_descriptor_t *)device->control_pipe.rx_buffer;
|
||
|
device->vid = desc->vid[0] | (desc->vid[1] << 8);
|
||
|
device->pid = desc->pid[0] | (desc->pid[1] << 8);
|
||
|
device->device_class = desc->device_class;
|
||
|
uint8_t idx_manufacture = desc->manufacture;
|
||
|
uint8_t idx_product = desc->product;
|
||
|
uint8_t idx_serial = desc->serial;
|
||
|
|
||
|
printf("Enumerating %04x:%04x, class:%d, address:%d\n", device->vid,
|
||
|
device->pid, device->device_class, address);
|
||
|
|
||
|
usb_setup_packet_t set_address_request = SET_ADDRESS_REQ_DEFAULT;
|
||
|
set_address_request.value_lsb = address;
|
||
|
set_address_request.value_msb = 0;
|
||
|
res = control_out_protocol(device, (uint8_t *)&set_address_request,
|
||
|
sizeof(set_address_request), NULL, 0);
|
||
|
pio_usb_host_close_device(device->root - pio_usb_root_port, 0);
|
||
|
if (res != 0) {
|
||
|
return res;
|
||
|
}
|
||
|
device->address = address;
|
||
|
|
||
|
endpoint_descriptor_t ep0_desc = {
|
||
|
sizeof(endpoint_descriptor_t), DESC_TYPE_ENDPOINT, 0x00, 0x00, { desc->max_packet_size, 0x00 }, 0x00
|
||
|
};
|
||
|
pio_usb_host_endpoint_open(device->root - pio_usb_root_port, address,
|
||
|
(uint8_t const *)&ep0_desc,
|
||
|
!device->is_root && !device->is_fullspeed);
|
||
|
|
||
|
uint8_t str[64];
|
||
|
if (idx_manufacture != 0) {
|
||
|
res = get_string_descriptor(device, idx_manufacture, rx_buffer, str);
|
||
|
if (res == 0) {
|
||
|
printf("Manufacture:%s\n", str);
|
||
|
} else {
|
||
|
printf("Failed to get string descriptor (Manufacture)\n");
|
||
|
}
|
||
|
stdio_flush();
|
||
|
}
|
||
|
|
||
|
if (idx_product != 0) {
|
||
|
res = get_string_descriptor(device, idx_product, rx_buffer, str);
|
||
|
if (res == 0) {
|
||
|
printf("Product:%s\n", str);
|
||
|
} else {
|
||
|
printf("Failed to get string descriptor (Product)\n");
|
||
|
}
|
||
|
stdio_flush();
|
||
|
}
|
||
|
|
||
|
if (idx_serial != 0) {
|
||
|
res = get_string_descriptor(device, idx_serial, rx_buffer, str);
|
||
|
if (res == 0) {
|
||
|
printf("Serial:%s\n", str);
|
||
|
} else {
|
||
|
printf("Failed to get string descriptor (Serial)\n");
|
||
|
}
|
||
|
stdio_flush();
|
||
|
}
|
||
|
|
||
|
usb_setup_packet_t get_configuration_descriptor_request =
|
||
|
GET_CONFIGURATION_DESCRIPTOR_REQ_DEFAULT;
|
||
|
get_configuration_descriptor_request.length_lsb = 9;
|
||
|
get_configuration_descriptor_request.length_msb = 0;
|
||
|
res = control_in_protocol(
|
||
|
device, (uint8_t *)&get_configuration_descriptor_request,
|
||
|
sizeof(get_configuration_descriptor_request), rx_buffer, 9);
|
||
|
if (res != 0) {
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
get_configuration_descriptor_request.length_lsb =
|
||
|
((configuration_descriptor_t *)(device->control_pipe.rx_buffer))
|
||
|
->total_length_lsb;
|
||
|
get_configuration_descriptor_request.length_msb =
|
||
|
((configuration_descriptor_t *)(device->control_pipe.rx_buffer))
|
||
|
->total_length_msb;
|
||
|
uint16_t request_length =
|
||
|
get_configuration_descriptor_request.length_lsb |
|
||
|
(get_configuration_descriptor_request.length_msb << 8);
|
||
|
res = control_in_protocol(
|
||
|
device, (uint8_t *)&get_configuration_descriptor_request,
|
||
|
sizeof(get_configuration_descriptor_request), rx_buffer, request_length);
|
||
|
|
||
|
if (res != 0) {
|
||
|
return res;
|
||
|
}
|
||
|
uint8_t configuration_descrptor_data[512];
|
||
|
int16_t configuration_descrptor_length =
|
||
|
request_length > 512 ? 512 : request_length;
|
||
|
memcpy(configuration_descrptor_data,
|
||
|
(const void *)device->control_pipe.rx_buffer,
|
||
|
configuration_descrptor_length);
|
||
|
|
||
|
usb_setup_packet_t set_usb_configuration_request =
|
||
|
SET_CONFIGURATION_REQ_DEFAULT;
|
||
|
set_usb_configuration_request.value_lsb =
|
||
|
((configuration_descriptor_t *)(device->control_pipe.rx_buffer))
|
||
|
->configuration_value;
|
||
|
res = control_out_protocol(device, (uint8_t *)&set_usb_configuration_request,
|
||
|
sizeof(set_usb_configuration_request), NULL, 0);
|
||
|
|
||
|
if (res != 0) {
|
||
|
return res;
|
||
|
}
|
||
|
volatile uint8_t ep_id_idx = 0;
|
||
|
volatile uint8_t interface = 0;
|
||
|
volatile uint8_t class = 0;
|
||
|
uint8_t *descriptor = configuration_descrptor_data;
|
||
|
while (configuration_descrptor_length > 0) {
|
||
|
switch (descriptor[1]) {
|
||
|
case DESC_TYPE_INTERFACE: {
|
||
|
const interface_descriptor_t *d =
|
||
|
(const interface_descriptor_t *)descriptor;
|
||
|
printf(
|
||
|
"inum:%d, altsetting:%d, numep:%d, iclass:%d, isubclass:%d, "
|
||
|
"iprotcol:%d, iface:%d\n",
|
||
|
d->inum, d->altsetting, d->numep, d->iclass, d->isubclass,
|
||
|
d->iprotocol, d->iface);
|
||
|
interface = d->inum;
|
||
|
class = d->iclass;
|
||
|
} break;
|
||
|
case DESC_TYPE_ENDPOINT: {
|
||
|
const endpoint_descriptor_t *d =
|
||
|
(const endpoint_descriptor_t *)descriptor;
|
||
|
printf("\t\t\tepaddr:0x%02x, attr:%d, size:%d, interval:%d\n",
|
||
|
d->epaddr, d->attr, d->max_size[0] | (d->max_size[1] << 8),
|
||
|
d->interval);
|
||
|
|
||
|
if ((class == CLASS_HID || class == CLASS_HUB) &&
|
||
|
d->attr == EP_ATTR_INTERRUPT) {
|
||
|
volatile endpoint_t *ep = NULL;
|
||
|
for (int ep_pool_idx = 0; ep_pool_idx < PIO_USB_EP_POOL_CNT;
|
||
|
ep_pool_idx++) {
|
||
|
if (pio_usb_ep_pool[ep_pool_idx].size == 0) {
|
||
|
ep = &pio_usb_ep_pool[ep_pool_idx];
|
||
|
device->endpoint_id[ep_id_idx] = ep_pool_idx + 1;
|
||
|
ep_id_idx++;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (ep != NULL) {
|
||
|
ep->interval = d->interval;
|
||
|
ep->interval_counter = 0;
|
||
|
ep->size = d->max_size[0] | (d->max_size[1] << 8);
|
||
|
ep->attr = d->attr | EP_ATTR_ENUMERATING;
|
||
|
ep->ep_num = d->epaddr;
|
||
|
|
||
|
ep->root_idx = device->root - pio_usb_root_port;
|
||
|
ep->dev_addr = device->address;
|
||
|
ep->need_pre = !device->is_root && !device->is_fullspeed;
|
||
|
ep->is_tx = (d->epaddr & 0x80) ? false : true;
|
||
|
} else {
|
||
|
printf("No empty EP\n");
|
||
|
}
|
||
|
}
|
||
|
} break;
|
||
|
case DESC_TYPE_HID: {
|
||
|
const hid_descriptor_t *d = (const hid_descriptor_t *)descriptor;
|
||
|
printf(
|
||
|
"\tbcdHID:%x.%x, country:%d, desc num:%d, desc_type:%d, "
|
||
|
"desc_size:%d\n",
|
||
|
d->bcd_hid[1], d->bcd_hid[0], d->contry_code, d->num_desc,
|
||
|
d->desc_type, d->desc_len[0] | (d->desc_len[1] << 8));
|
||
|
|
||
|
usb_setup_packet_t set_hid_idle_request = SET_HID_IDLE_REQ_DEFAULT;
|
||
|
set_hid_idle_request.value_lsb = interface;
|
||
|
set_hid_idle_request.value_msb = 0;
|
||
|
control_out_protocol(device, (uint8_t *)&set_hid_idle_request,
|
||
|
sizeof(set_hid_idle_request), NULL, 0);
|
||
|
|
||
|
usb_setup_packet_t get_hid_report_descrpitor_request =
|
||
|
GET_HID_REPORT_DESCRIPTOR_DEFAULT;
|
||
|
get_hid_report_descrpitor_request.index_lsb = interface;
|
||
|
get_hid_report_descrpitor_request.index_msb = 0;
|
||
|
get_hid_report_descrpitor_request.length_lsb = d->desc_len[0];
|
||
|
get_hid_report_descrpitor_request.length_msb = d->desc_len[1];
|
||
|
uint16_t desc_len = d->desc_len[0] | (d->desc_len[1] << 8);
|
||
|
control_in_protocol(
|
||
|
device, (uint8_t *)&get_hid_report_descrpitor_request,
|
||
|
sizeof(get_hid_report_descrpitor_request), rx_buffer, desc_len);
|
||
|
printf("\t\tReport descriptor:");
|
||
|
for (int i = 0; i < desc_len; i++) {
|
||
|
printf("%02x ", device->control_pipe.rx_buffer[i]);
|
||
|
}
|
||
|
printf("\n");
|
||
|
stdio_flush();
|
||
|
|
||
|
} break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
configuration_descrptor_length -= descriptor[0];
|
||
|
descriptor += descriptor[0];
|
||
|
}
|
||
|
|
||
|
for (int epidx = 0; epidx < PIO_USB_DEV_EP_CNT; epidx++) {
|
||
|
endpoint_t *ep = pio_usb_get_endpoint(device, epidx);
|
||
|
if (ep == NULL) {
|
||
|
break;
|
||
|
}
|
||
|
ep->attr &= ~EP_ATTR_ENUMERATING;
|
||
|
|
||
|
// prepare transfer
|
||
|
if (ep->ep_num & EP_IN) {
|
||
|
pio_usb_ll_transfer_start(ep, ep->buffer, ep->size);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static void device_disconnect(usb_device_t *device) {
|
||
|
printf("Disconnect device %d\n", device->address);
|
||
|
for (int port = 0; port < PIO_USB_HUB_PORT_CNT; port++) {
|
||
|
if (device->child_devices[port] != 0) {
|
||
|
device_disconnect(&pio_usb_device[device->child_devices[port]]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (int ep_idx = 0; ep_idx < PIO_USB_DEV_EP_CNT; ep_idx++) {
|
||
|
endpoint_t *ep = pio_usb_get_endpoint(device, ep_idx);
|
||
|
if (ep == NULL) {
|
||
|
break;
|
||
|
}
|
||
|
memset(ep, 0, sizeof(*ep));
|
||
|
}
|
||
|
|
||
|
if (device->address == 0 && device->root != NULL) {
|
||
|
device->root->addr0_exists = false;
|
||
|
}
|
||
|
|
||
|
memset(device, 0, sizeof(*device));
|
||
|
}
|
||
|
|
||
|
static int device_pool_vacant(void) {
|
||
|
for (int idx = 0; idx < PIO_USB_DEVICE_CNT; idx++) {
|
||
|
if (!pio_usb_device[idx].connected) {
|
||
|
return idx;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static int assign_new_device_to_port(usb_device_t *hub_device, uint8_t port, bool is_ls) {
|
||
|
int idx = device_pool_vacant();
|
||
|
if (idx >= 0) {
|
||
|
hub_device->child_devices[port] = idx;
|
||
|
pio_usb_device[idx].parent_device = hub_device;
|
||
|
pio_usb_device[idx].parent_port = port;
|
||
|
pio_usb_device[idx].root = hub_device->root;
|
||
|
pio_usb_device[idx].connected = true;
|
||
|
pio_usb_device[idx].is_fullspeed = !is_ls;
|
||
|
pio_usb_device[idx].event = EVENT_CONNECT;
|
||
|
printf("Assign device %d to %d-%d\n", idx, hub_device->address, port);
|
||
|
|
||
|
endpoint_descriptor_t ep0_desc = {
|
||
|
sizeof(endpoint_descriptor_t), DESC_TYPE_ENDPOINT, 0x00, 0x00, { 0x08, 0x00 }, 0x00
|
||
|
};
|
||
|
pio_usb_host_endpoint_open(hub_device->root - pio_usb_root_port, 0x00,
|
||
|
(uint8_t const *)&ep0_desc, is_ls);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
printf("Failed to assign device\n");
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static void __no_inline_not_in_flash_func(process_hub_event)(
|
||
|
usb_device_t *device) {
|
||
|
volatile endpoint_t *ep = pio_usb_get_endpoint(device, 0);
|
||
|
uint8_t bitmap = ep->buffer[0];
|
||
|
for (int bit = 1; bit < 8; bit++) {
|
||
|
if (!(bitmap & (1 << bit))) {
|
||
|
continue;
|
||
|
}
|
||
|
uint8_t port = bit - 1;
|
||
|
hub_port_status_t status;
|
||
|
int res = get_hub_port_status(device, port, &status);
|
||
|
if (res != 0) {
|
||
|
printf("Failed to get port%d-%d status\n", device->address, port);
|
||
|
continue;
|
||
|
}
|
||
|
printf("port%d-%d status:%d %d\n", device->address, port,
|
||
|
status.port_change, status.port_status);
|
||
|
|
||
|
if (status.port_change & HUB_CHANGE_PORT_CONNECTION) {
|
||
|
if (status.port_status & HUB_STAT_PORT_CONNECTION) {
|
||
|
printf("new device on port %d, reset port\n", port);
|
||
|
if (device->child_devices[port] != 0) {
|
||
|
printf("device is already assigned. disconnect previous\n");
|
||
|
device_disconnect(&pio_usb_device[device->child_devices[port]]);
|
||
|
}
|
||
|
|
||
|
if (device->root->addr0_exists) {
|
||
|
printf("Address 0 already exists\n");
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (device_pool_vacant() >= 0) {
|
||
|
set_hub_feature(device, port, HUB_SET_PORT_RESET);
|
||
|
device->root->addr0_exists = true;
|
||
|
} else {
|
||
|
printf("No vacant in device pool\n");
|
||
|
}
|
||
|
} else {
|
||
|
printf("device removed from port %d\n", port);
|
||
|
if (device->child_devices[port] != 0) {
|
||
|
device_disconnect(&pio_usb_device[device->child_devices[port]]);
|
||
|
}
|
||
|
}
|
||
|
clear_hub_feature(device, port, HUB_CLR_PORT_CONNECTION);
|
||
|
} else if (status.port_change & HUB_CHANGE_PORT_RESET) {
|
||
|
printf("reset port %d complete\n", port);
|
||
|
res = clear_hub_feature(device, port, HUB_CLR_PORT_RESET);
|
||
|
if (res == 0) {
|
||
|
assign_new_device_to_port(device, port,
|
||
|
status.port_status & HUB_STAT_PORT_LOWSPEED);
|
||
|
}
|
||
|
} else if (status.port_change & HUB_CHANGE_PORT_ENABLE) {
|
||
|
clear_hub_feature(device, port, HUB_CLR_PORT_ENABLE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
device->event = EVENT_NONE;
|
||
|
}
|
||
|
|
||
|
void __no_inline_not_in_flash_func(pio_usb_host_task)(void) {
|
||
|
for (int root_idx = 0; root_idx < PIO_USB_ROOT_PORT_CNT; root_idx++) {
|
||
|
if (pio_usb_root_port[root_idx].event == EVENT_CONNECT) {
|
||
|
printf("Root %d connected\n", root_idx);
|
||
|
int dev_idx = device_pool_vacant();
|
||
|
if (dev_idx >= 0) {
|
||
|
on_device_connect(&pio_port[0], &pio_usb_root_port[root_idx], dev_idx);
|
||
|
pio_usb_root_port[root_idx].addr0_exists = true;
|
||
|
}
|
||
|
pio_usb_root_port[root_idx].event = EVENT_NONE;
|
||
|
} else if (pio_usb_root_port[root_idx].event == EVENT_DISCONNECT) {
|
||
|
printf("Root %d disconnected\n", root_idx);
|
||
|
pio_usb_host_close_device(
|
||
|
root_idx, pio_usb_root_port[root_idx].root_device->address);
|
||
|
pio_usb_root_port[root_idx].root_device->connected = false;
|
||
|
pio_usb_root_port[root_idx].root_device->event = EVENT_DISCONNECT;
|
||
|
pio_usb_root_port[root_idx].root_device = NULL;
|
||
|
pio_usb_root_port[root_idx].event = EVENT_NONE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (int idx = 0; idx < PIO_USB_DEVICE_CNT; idx++) {
|
||
|
usb_device_t *device = &pio_usb_device[idx];
|
||
|
|
||
|
if (device->event == EVENT_CONNECT) {
|
||
|
device->event = EVENT_NONE;
|
||
|
printf("Device %d Connected\n", idx);
|
||
|
int res = enumerate_device(device, idx + 1);
|
||
|
if (res == 0) {
|
||
|
device->enumerated = true;
|
||
|
device->root->addr0_exists = false;
|
||
|
if (device->device_class == CLASS_HUB) {
|
||
|
res = initialize_hub(device);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (res != 0) {
|
||
|
printf("Enumeration failed(%d)\n", res);
|
||
|
// retry
|
||
|
if (device->is_root) {
|
||
|
device->root->event = EVENT_DISCONNECT;
|
||
|
} else {
|
||
|
set_hub_feature(device->parent_device, device->parent_port,
|
||
|
HUB_SET_PORT_RESET);
|
||
|
device_disconnect(device);
|
||
|
}
|
||
|
}
|
||
|
} else if (device->event == EVENT_DISCONNECT) {
|
||
|
device->event = EVENT_NONE;
|
||
|
printf("Disconnect\n");
|
||
|
device_disconnect(device);
|
||
|
} else if (device->event == EVENT_HUB_PORT_CHANGE) {
|
||
|
process_hub_event(device);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (cancel_timer_flag) {
|
||
|
int_stat = save_and_disable_interrupts();
|
||
|
stop_timer();
|
||
|
if (pio_usb_root_port->root_device != NULL) {
|
||
|
device_disconnect(pio_usb_root_port->root_device);
|
||
|
}
|
||
|
cancel_timer_flag = false;
|
||
|
}
|
||
|
|
||
|
if (start_timer_flag) {
|
||
|
start_timer(_alarm_pool);
|
||
|
restore_interrupts(int_stat);
|
||
|
start_timer_flag = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void __no_inline_not_in_flash_func(handle_endpoint_irq)(
|
||
|
root_port_t *root, uint32_t flag, volatile uint32_t *ep_reg) {
|
||
|
(void)root;
|
||
|
const uint32_t ep_all = *ep_reg;
|
||
|
|
||
|
for (uint8_t ep_idx = 0; ep_idx < PIO_USB_EP_POOL_CNT; ep_idx++) {
|
||
|
if (ep_all & (1u << ep_idx)) {
|
||
|
endpoint_t *ep = PIO_USB_ENDPOINT(ep_idx);
|
||
|
usb_device_t *device = NULL;
|
||
|
|
||
|
// find device this endpoint belongs to
|
||
|
for (int idx = 0; idx < PIO_USB_DEVICE_CNT; idx++) {
|
||
|
usb_device_t *dev = &pio_usb_device[idx];
|
||
|
if (dev->connected && (ep->dev_addr == dev->address)) {
|
||
|
device = dev;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (device) {
|
||
|
// control endpoint is either 0x00 or 0x80
|
||
|
if ((ep->ep_num & 0x7f) == 0) {
|
||
|
control_pipe_t *pipe = &device->control_pipe;
|
||
|
|
||
|
if (flag != PIO_USB_INTS_ENDPOINT_COMPLETE_BITS) {
|
||
|
pipe->stage = STAGE_SETUP;
|
||
|
pipe->operation = CONTROL_ERROR;
|
||
|
} else {
|
||
|
ep->data_id = 1; // both data and status have DATA1
|
||
|
if (pipe->stage == STAGE_SETUP) {
|
||
|
if (pipe->operation == CONTROL_IN) {
|
||
|
pipe->stage = STAGE_IN;
|
||
|
ep->ep_num = 0x80;
|
||
|
ep->is_tx = false;
|
||
|
pio_usb_ll_transfer_start(ep,
|
||
|
(uint8_t *)(uintptr_t)pipe->rx_buffer,
|
||
|
pipe->request_length);
|
||
|
} else if (pipe->operation == CONTROL_OUT) {
|
||
|
if (pipe->out_data_packet.tx_address != NULL) {
|
||
|
pipe->stage = STAGE_OUT;
|
||
|
ep->ep_num = 0x00;
|
||
|
ep->is_tx = true;
|
||
|
pio_usb_ll_transfer_start(ep,
|
||
|
pipe->out_data_packet.tx_address,
|
||
|
pipe->out_data_packet.tx_length);
|
||
|
} else {
|
||
|
pipe->stage = STAGE_STATUS;
|
||
|
ep->ep_num = 0x80;
|
||
|
ep->is_tx = false;
|
||
|
pio_usb_ll_transfer_start(ep, NULL, 0);
|
||
|
}
|
||
|
}
|
||
|
} else if (pipe->stage == STAGE_IN) {
|
||
|
pipe->stage = STAGE_STATUS;
|
||
|
ep->ep_num = 0x00;
|
||
|
ep->is_tx = true;
|
||
|
pio_usb_ll_transfer_start(ep, NULL, 0);
|
||
|
} else if (pipe->stage == STAGE_OUT) {
|
||
|
pipe->stage = STAGE_STATUS;
|
||
|
ep->ep_num = 0x80;
|
||
|
ep->is_tx = false;
|
||
|
pio_usb_ll_transfer_start(ep, NULL, 0);
|
||
|
} else if (pipe->stage == STAGE_STATUS) {
|
||
|
pipe->stage = STAGE_SETUP;
|
||
|
pipe->operation = CONTROL_COMPLETE;
|
||
|
}
|
||
|
}
|
||
|
} else if (device->device_class == CLASS_HUB && (ep->ep_num & EP_IN)) {
|
||
|
// hub interrupt endpoint
|
||
|
device->event = EVENT_HUB_PORT_CHANGE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// clear all
|
||
|
(*ep_reg) &= ~ep_all;
|
||
|
}
|
||
|
|
||
|
// IRQ Handler
|
||
|
static void __no_inline_not_in_flash_func(__pio_usb_host_irq_handler)(uint8_t root_id) {
|
||
|
root_port_t *root = PIO_USB_ROOT_PORT(root_id);
|
||
|
uint32_t const ints = root->ints;
|
||
|
|
||
|
if (ints & PIO_USB_INTS_CONNECT_BITS) {
|
||
|
root->event = EVENT_CONNECT;
|
||
|
}
|
||
|
|
||
|
if (ints & PIO_USB_INTS_DISCONNECT_BITS) {
|
||
|
root->event = EVENT_DISCONNECT;
|
||
|
}
|
||
|
|
||
|
if (ints & PIO_USB_INTS_ENDPOINT_COMPLETE_BITS) {
|
||
|
handle_endpoint_irq(root, PIO_USB_INTS_ENDPOINT_COMPLETE_BITS,
|
||
|
&root->ep_complete);
|
||
|
}
|
||
|
|
||
|
if (ints & PIO_USB_INTS_ENDPOINT_STALLED_BITS) {
|
||
|
handle_endpoint_irq(root, PIO_USB_INTS_ENDPOINT_STALLED_BITS,
|
||
|
&root->ep_stalled);
|
||
|
}
|
||
|
|
||
|
if (ints & PIO_USB_INTS_ENDPOINT_ERROR_BITS) {
|
||
|
handle_endpoint_irq(root, PIO_USB_INTS_ENDPOINT_ERROR_BITS,
|
||
|
&root->ep_error);
|
||
|
}
|
||
|
|
||
|
// clear all
|
||
|
root->ints &= ~ints;
|
||
|
}
|
||
|
|
||
|
// weak alias to __pio_usb_host_irq_handler
|
||
|
void pio_usb_host_irq_handler(uint8_t root_id) __attribute__ ((weak, alias("__pio_usb_host_irq_handler")));
|
||
|
|
||
|
#pragma GCC pop_options
|
||
|
|
||
|
//--------------------------------------------------------------------+
|
||
|
// Misc functions
|
||
|
//--------------------------------------------------------------------+
|
||
|
int pio_usb_kbd_set_leds(usb_device_t *device, uint8_t port, uint8_t value) {
|
||
|
usb_setup_packet_t req = SET_REPORT_REQ_DEFAULT;
|
||
|
req.index_lsb = port;
|
||
|
req.length_lsb = 1;
|
||
|
req.length_msb = 0;
|
||
|
return control_out_protocol(device, (uint8_t *)&req, sizeof(req), &value, 1);
|
||
|
}
|