1392571bd7
Use a consistent standard - Tabs in front for indenting, spaces after for anything else. This way everything stays nice and lined up while also letting users change there prefered indent level. Most of the new files from Haata where already in this format.
591 lines
14 KiB
C
591 lines
14 KiB
C
/* Copyright (c) 2011,2012 Simon Schubert <2@0x2c.org>.
|
|
* Modifications by Jacob Alexander 2014 <haata@kiibohd.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
// ----- Compiler Includes -----
|
|
|
|
#include <sys/types.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
|
|
|
|
// ----- Local Includes -----
|
|
|
|
#include "usb.h"
|
|
#include "usb-internal.h"
|
|
|
|
|
|
|
|
// ----- Variables -----
|
|
|
|
static uint8_t ep0_buf[2][EP0_BUFSIZE] __attribute__((aligned(4)));
|
|
struct usbd_t usb;
|
|
|
|
|
|
|
|
// ----- Functions -----
|
|
|
|
/**
|
|
* Returns: 0 when this is was the last transfer, 1 if there is still
|
|
* more to go.
|
|
*/
|
|
/* Defaults to EP0 for now */
|
|
static int usb_tx_next(struct usbd_ep_pipe_state_t *s)
|
|
{
|
|
|
|
/**
|
|
* Us being here means the previous transfer just completed
|
|
* successfully. That means the host just toggled its data
|
|
* sync bit, and so do we.
|
|
*/
|
|
s->data01 ^= 1;
|
|
|
|
if (s->transfer_size > 0) {
|
|
size_t thislen = s->transfer_size;
|
|
|
|
if (thislen > s->ep_maxsize)
|
|
thislen = s->ep_maxsize;
|
|
|
|
void *addr = s->data_buf + s->pos;
|
|
|
|
if (s->copy_source) {
|
|
/* Bounce buffer mode */
|
|
addr = s->data_buf;
|
|
memcpy(addr, s->copy_source + s->pos, thislen);
|
|
}
|
|
s->pos += thislen;
|
|
s->transfer_size -= thislen;
|
|
|
|
usb_queue_next(s, addr, thislen);
|
|
s->pingpong ^= 1;
|
|
|
|
return (1);
|
|
}
|
|
|
|
/**
|
|
* All data has been shipped. Do we need to send a short
|
|
* packet?
|
|
*/
|
|
if (s->short_transfer) {
|
|
s->short_transfer = 0;
|
|
usb_queue_next(s, NULL, 0);
|
|
s->pingpong ^= 1;
|
|
return (1);
|
|
}
|
|
|
|
if (s->callback)
|
|
s->callback(s->data_buf, s->pos, s->callback_data);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void setup_tx(struct usbd_ep_pipe_state_t *s, const void *buf, size_t len, size_t reqlen, ep_callback_t cb, void *cb_data)
|
|
{
|
|
s->data_buf = (void *)buf;
|
|
s->copy_source = NULL;
|
|
s->transfer_size = len;
|
|
s->pos = 0;
|
|
s->callback = cb;
|
|
s->callback_data = cb_data;
|
|
if (s->transfer_size > reqlen)
|
|
s->transfer_size = reqlen;
|
|
if (s->transfer_size < reqlen && s->transfer_size % s->ep_maxsize == 0)
|
|
s->short_transfer = 1;
|
|
else
|
|
s->short_transfer = 0;
|
|
}
|
|
|
|
static void submit_tx(struct usbd_ep_pipe_state_t *s)
|
|
{
|
|
/* usb_tx_next() flips the data toggle, so invert this here. */
|
|
s->data01 ^= 1;
|
|
usb_tx_next(s);
|
|
}
|
|
|
|
/**
|
|
* send USB data (IN device transaction)
|
|
*
|
|
* So far this function is specialized for EP 0 only.
|
|
*
|
|
* Returns: size to be transfered, or -1 on error.
|
|
*/
|
|
int usb_tx(struct usbd_ep_pipe_state_t *s, const void *buf, size_t len, size_t reqlen, ep_callback_t cb, void *cb_data)
|
|
{
|
|
setup_tx(s, buf, len, reqlen, cb, cb_data);
|
|
submit_tx(s);
|
|
return (s->transfer_size);
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns: 0 when this is was the last transfer, 1 if there is still
|
|
* more to go.
|
|
*/
|
|
/* Defaults to EP0 for now */
|
|
/* XXX pass usb_stat to validate pingpong */
|
|
static int usb_rx_next(struct usbd_ep_pipe_state_t *s)
|
|
{
|
|
/**
|
|
* Us being here means the previous transfer just completed
|
|
* successfully. That means the host just toggled its data
|
|
* sync bit, and so do we.
|
|
*/
|
|
s->data01 ^= 1;
|
|
|
|
size_t thislen = usb_ep_get_transfer_size(s);
|
|
|
|
s->transfer_size -= thislen;
|
|
s->pos += thislen;
|
|
|
|
/**
|
|
* We're done with this buffer now. Switch the pingpong now
|
|
* before we might have to receive the next piece of data.
|
|
*/
|
|
s->pingpong ^= 1;
|
|
|
|
/**
|
|
* If this is a short transfer, or we received what we
|
|
* expected, we're done.
|
|
*/
|
|
if (thislen < s->ep_maxsize || s->transfer_size == 0) {
|
|
if (s->callback)
|
|
s->callback(s->data_buf, s->pos, s->callback_data);
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Otherwise we still need to receive more data.
|
|
*/
|
|
size_t nextlen = s->transfer_size;
|
|
|
|
if (nextlen > s->ep_maxsize)
|
|
nextlen = s->ep_maxsize;
|
|
|
|
void *addr = s->data_buf + s->pos;
|
|
usb_queue_next(s, addr, nextlen);
|
|
|
|
return (1);
|
|
}
|
|
|
|
/**
|
|
* Receive USB data (OUT device transaction)
|
|
*
|
|
* Returns: size to be received, or -1 on error.
|
|
*/
|
|
int usb_rx(struct usbd_ep_pipe_state_t *s, void *buf, size_t len, ep_callback_t cb, void *cb_data)
|
|
{
|
|
s->data_buf = buf;
|
|
s->transfer_size = len;
|
|
s->pos = 0;
|
|
s->callback = cb;
|
|
s->callback_data = cb_data;
|
|
|
|
size_t thislen = s->transfer_size;
|
|
if (thislen > s->ep_maxsize)
|
|
thislen = s->ep_maxsize;
|
|
|
|
usb_queue_next(s, s->data_buf, thislen);
|
|
return (len);
|
|
}
|
|
|
|
int usb_ep0_tx_cp(const void *buf, size_t len, size_t reqlen, ep_callback_t cb, void *cb_data)
|
|
{
|
|
struct usbd_ep_pipe_state_t *s = &usb.ep_state[0].tx;
|
|
enum usb_ep_pingpong pp = s->pingpong;
|
|
|
|
setup_tx(s, ep0_buf[pp], len, reqlen, cb, cb_data);
|
|
s->copy_source = buf;
|
|
submit_tx(s);
|
|
return (s->transfer_size);
|
|
}
|
|
|
|
void *usb_ep0_tx_inplace_prepare(size_t len)
|
|
{
|
|
enum usb_ep_pingpong pp = usb.ep_state[0].tx.pingpong;
|
|
|
|
if (len > EP0_BUFSIZE)
|
|
return (NULL);
|
|
|
|
return (ep0_buf[pp]);
|
|
}
|
|
|
|
int usb_ep0_tx(void *buf, size_t len, size_t reqlen, ep_callback_t cb, void *cb_data)
|
|
{
|
|
return (usb_tx(&usb.ep_state[0].tx, buf, len, reqlen, cb, cb_data));
|
|
}
|
|
|
|
int usb_ep0_rx(void *buf, size_t len, ep_callback_t cb, void *cb_data)
|
|
{
|
|
return (usb_rx(&usb.ep_state[0].rx, buf, len, cb, cb_data));
|
|
}
|
|
|
|
|
|
const struct usbd_config *
|
|
usb_get_config_data(int config)
|
|
{
|
|
if (config <= 0)
|
|
config = usb.config;
|
|
|
|
if (config != 0)
|
|
return (usb.identity->configs[config - 1]);
|
|
else
|
|
return (NULL);
|
|
}
|
|
|
|
static int usb_set_config(int config)
|
|
{
|
|
const struct usbd_config *config_data;
|
|
|
|
if (usb.config != 0) {
|
|
config_data = usb_get_config_data(-1);
|
|
if (config_data != NULL && config_data->init != NULL)
|
|
config_data->init(0);
|
|
}
|
|
|
|
if (config != 0) {
|
|
/* XXX overflow */
|
|
config_data = usb_get_config_data(config);
|
|
if (config_data != NULL && config_data->init != NULL)
|
|
config_data->init(1);
|
|
}
|
|
usb.config = config;
|
|
return (0);
|
|
}
|
|
|
|
static int usb_set_interface(int iface, int altsetting)
|
|
{
|
|
int iface_count = 0;
|
|
|
|
for (struct usbd_function_ctx_header *fh = &usb.functions;
|
|
fh != NULL;
|
|
fh = fh->next, iface_count += fh->function->interface_count) {
|
|
if (iface - iface_count < fh->function->interface_count) {
|
|
if (fh->function->configure != NULL)
|
|
return (fh->function->configure(iface,
|
|
iface - iface_count,
|
|
altsetting,
|
|
fh));
|
|
|
|
/* Default to a single altsetting */
|
|
if (altsetting != 0)
|
|
return (-1);
|
|
else
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
return (-1);
|
|
}
|
|
|
|
static int usb_tx_config_desc(int idx, int reqlen)
|
|
{
|
|
const struct usb_desc_config_t *d = usb.identity->configs[idx]->desc;
|
|
|
|
usb_ep0_tx_cp(d, d->wTotalLength, reqlen, NULL, NULL);
|
|
return (0);
|
|
}
|
|
|
|
static int usb_tx_string_desc(int idx, int reqlen)
|
|
{
|
|
const struct usb_desc_string_t * const *d;
|
|
|
|
for (d = usb.identity->string_descs; idx != 0 && *d != NULL; ++d)
|
|
--idx;
|
|
switch ((uintptr_t)*d) {
|
|
case (uintptr_t)NULL:
|
|
return (-1);
|
|
case (uintptr_t)USB_DESC_STRING_SERIALNO:
|
|
return (usb_tx_serialno(reqlen));
|
|
default:
|
|
usb_ep0_tx_cp(*d, (*d)->bLength, reqlen, NULL, NULL);
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
|
|
static void usb_handle_control_done(void *data, ssize_t len, void *cbdata)
|
|
{
|
|
if (usb.state == USBD_STATE_SETTING_ADDRESS) {
|
|
usb.state = USBD_STATE_ADDRESS;
|
|
usb_set_addr(usb.address);
|
|
}
|
|
usb_setup_control();
|
|
}
|
|
|
|
void usb_handle_control_status_cb(ep_callback_t cb)
|
|
{
|
|
/* empty status transfer */
|
|
switch (usb.ctrl_dir) {
|
|
case USB_CTRL_REQ_IN:
|
|
usb.ep_state[0].rx.data01 = USB_DATA01_DATA1;
|
|
usb_rx(&usb.ep_state[0].rx, NULL, 0, cb, NULL);
|
|
break;
|
|
|
|
default:
|
|
usb.ep_state[0].tx.data01 = USB_DATA01_DATA1;
|
|
usb_ep0_tx_cp(NULL, 0, 1 /* short packet */, cb, NULL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void usb_handle_control_status(int fail)
|
|
{
|
|
if (fail) {
|
|
usb_pipe_stall(&usb.ep_state[0].rx);
|
|
usb_pipe_stall(&usb.ep_state[0].tx);
|
|
} else {
|
|
usb_handle_control_status_cb(usb_handle_control_done);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Dispatch non-standard request to registered USB functions.
|
|
*/
|
|
static void usb_handle_control_nonstd(struct usb_ctrl_req_t *req)
|
|
{
|
|
/* XXX filter by interface/endpoint? */
|
|
for (struct usbd_function_ctx_header *fh = &usb.functions; fh != NULL; fh = fh->next) {
|
|
/* ->control() returns != 0 if it handled the request */
|
|
if (fh->function->control != NULL &&
|
|
fh->function->control(req, fh))
|
|
return;
|
|
}
|
|
|
|
usb_handle_control_status(-1);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
* Great resource: http://wiki.osdev.org/Universal_Serial_Bus
|
|
*
|
|
* Control Transfers
|
|
* -----------------
|
|
*
|
|
* A control transfer consists of a SETUP transaction (1), zero or
|
|
* more data transactions (IN or OUT) (2), and a final status
|
|
* transaction (3).
|
|
*
|
|
* Token sequence (data toggle):
|
|
* 1. SETUP (0)
|
|
* (2a. OUT (1) ... (toggling))
|
|
* 3a. IN (1)
|
|
*
|
|
* or
|
|
* 1. SETUP (0)
|
|
* 2b. IN (1) ... (toggling)
|
|
* 3b. OUT (1)
|
|
*
|
|
* Report errors by STALLing the control EP after (1) or (2), so that
|
|
* (3) will STALL. Seems we need to clear the STALL after that so
|
|
* that the next SETUP can make it through.
|
|
*
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* The following code is not written defensively, but instead only
|
|
* asserts values that are essential for correct execution. It
|
|
* accepts a superset of the protocol defined by the standard. We do
|
|
* this to save space.
|
|
*/
|
|
|
|
static void usb_handle_control(void *data, ssize_t len, void *cbdata)
|
|
{
|
|
struct usb_ctrl_req_t *req = data;
|
|
uint16_t zero16 = 0;
|
|
int fail = 1;
|
|
|
|
usb.ctrl_dir = req->in;
|
|
|
|
if (req->type != USB_CTRL_REQ_STD) {
|
|
usb_handle_control_nonstd(req);
|
|
return;
|
|
}
|
|
|
|
/* Only STD requests here */
|
|
switch (req->bRequest) {
|
|
case USB_CTRL_REQ_GET_STATUS:
|
|
/**
|
|
* Because we don't support remote wakeup or
|
|
* self-powered operation, and we are specialized to
|
|
* only EP 0 so far, all GET_STATUS replies are just
|
|
* empty.
|
|
*/
|
|
usb_ep0_tx_cp(&zero16, sizeof(zero16), req->wLength, NULL, NULL);
|
|
break;
|
|
|
|
case USB_CTRL_REQ_CLEAR_FEATURE:
|
|
case USB_CTRL_REQ_SET_FEATURE:
|
|
/**
|
|
* Nothing to do. Maybe return STALLs on illegal
|
|
* accesses?
|
|
*/
|
|
break;
|
|
|
|
case USB_CTRL_REQ_SET_ADDRESS:
|
|
/**
|
|
* We must keep our previous address until the end of
|
|
* the status stage; therefore we can't set the
|
|
* address right now. Since this is a special case,
|
|
* the EP 0 handler will take care of this later on.
|
|
*/
|
|
usb.address = req->wValue & 0x7f;
|
|
usb.state = USBD_STATE_SETTING_ADDRESS;
|
|
break;
|
|
|
|
case USB_CTRL_REQ_GET_DESCRIPTOR:
|
|
switch (req->wValue >> 8) {
|
|
case USB_DESC_DEV:
|
|
usb_ep0_tx_cp(usb.identity->dev_desc, usb.identity->dev_desc->bLength,
|
|
req->wLength, NULL, NULL);
|
|
fail = 0;
|
|
break;
|
|
case USB_DESC_CONFIG:
|
|
fail = usb_tx_config_desc(req->wValue & 0xff, req->wLength);
|
|
break;
|
|
case USB_DESC_STRING:
|
|
fail = usb_tx_string_desc(req->wValue & 0xff, req->wLength);
|
|
break;
|
|
default:
|
|
fail = -1;
|
|
break;
|
|
}
|
|
/* we set fail already, so we can go directly to `err' */
|
|
goto err;
|
|
|
|
case USB_CTRL_REQ_GET_CONFIGURATION:
|
|
usb_ep0_tx_cp(&usb.config, 1, req->wLength, NULL, NULL); /* XXX implicit LE */
|
|
break;
|
|
|
|
case USB_CTRL_REQ_SET_CONFIGURATION:
|
|
if (usb_set_config(req->wValue) < 0)
|
|
goto err;
|
|
break;
|
|
|
|
case USB_CTRL_REQ_GET_INTERFACE:
|
|
/* We only support iface setting 0 */
|
|
usb_ep0_tx_cp(&zero16, 1, req->wLength, NULL, NULL);
|
|
break;
|
|
|
|
case USB_CTRL_REQ_SET_INTERFACE:
|
|
if (usb_set_interface(req->wIndex, req->wValue) < 0)
|
|
goto err;
|
|
break;
|
|
|
|
default:
|
|
goto err;
|
|
}
|
|
|
|
fail = 0;
|
|
|
|
err:
|
|
usb_handle_control_status(fail);
|
|
}
|
|
|
|
void usb_setup_control(void)
|
|
{
|
|
void *buf = ep0_buf[usb.ep_state[0].rx.pingpong];
|
|
|
|
usb.ep_state[0].rx.data01 = USB_DATA01_DATA0;
|
|
usb.ep_state[0].tx.data01 = USB_DATA01_DATA1;
|
|
usb_rx(&usb.ep_state[0].rx, buf, EP0_BUFSIZE, usb_handle_control, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* This is called by the interrupt handler
|
|
*/
|
|
void usb_handle_transaction(struct usb_xfer_info *info)
|
|
{
|
|
enum usb_tok_pid pid = usb_get_xfer_pid(info);
|
|
struct usbd_ep_state_t *eps = &usb.ep_state[usb_get_xfer_ep(info)];
|
|
struct usbd_ep_pipe_state_t *s = &eps->pipe[usb_get_xfer_dir(info)];
|
|
|
|
switch (pid) {
|
|
case USB_PID_SETUP:
|
|
case USB_PID_OUT:
|
|
/**
|
|
* If we receive a SETUP transaction, but don't expect
|
|
* it (callback set to somewhere else), stall the EP.
|
|
*/
|
|
if (pid == USB_PID_SETUP && s->callback != usb_handle_control)
|
|
usb_handle_control_status(1);
|
|
else
|
|
usb_rx_next(s);
|
|
if (pid == USB_PID_SETUP)
|
|
usb_enable_xfers();
|
|
break;
|
|
case USB_PID_IN:
|
|
usb_tx_next(s);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct usbd_ep_pipe_state_t *usb_init_ep(struct usbd_function_ctx_header *ctx, int ep, enum usb_ep_dir dir, size_t size)
|
|
{
|
|
struct usbd_ep_pipe_state_t *s;
|
|
|
|
if (dir == USB_EP_RX)
|
|
s = &usb.ep_state[ctx->ep_rx_offset + ep].rx;
|
|
else
|
|
s = &usb.ep_state[ctx->ep_tx_offset + ep].tx;
|
|
|
|
memset(s, 0, sizeof(*s));
|
|
s->ep_maxsize = size;
|
|
s->ep_num = ep;
|
|
s->ep_dir = dir;
|
|
usb_pipe_enable(s);
|
|
return (s);
|
|
}
|
|
|
|
void usb_restart(void)
|
|
{
|
|
const struct usbd_device *identity = usb.identity;
|
|
/* XXX reset existing functions? */
|
|
memset(&usb, 0, sizeof(usb));
|
|
usb.functions.function = &usb.control_function;
|
|
usb.identity = identity;
|
|
usb_init_ep(&usb.functions, 0, USB_EP_RX, EP0_BUFSIZE);
|
|
usb_init_ep(&usb.functions, 0, USB_EP_TX, EP0_BUFSIZE);
|
|
usb_setup_control();
|
|
}
|
|
|
|
void usb_attach_function(const struct usbd_function *function, struct usbd_function_ctx_header *ctx)
|
|
{
|
|
/* XXX right now this requires a sequential initialization */
|
|
struct usbd_function_ctx_header *prev = &usb.functions;
|
|
|
|
while (prev->next != NULL)
|
|
prev = prev->next;
|
|
ctx->next = NULL;
|
|
ctx->function = function;
|
|
ctx->interface_offset = prev->interface_offset + prev->function->interface_count;
|
|
ctx->ep_rx_offset = prev->ep_rx_offset + prev->function->ep_rx_count;
|
|
ctx->ep_tx_offset = prev->ep_tx_offset + prev->function->ep_tx_count;
|
|
prev->next = ctx;
|
|
}
|
|
|
|
void usb_init(const struct usbd_device *identity)
|
|
{
|
|
usb.identity = identity;
|
|
usb_enable();
|
|
}
|
|
|