From 1dc7d758f96dd2b9bd7b03f01ca032d68b696cf0 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 2 Nov 2014 10:14:39 +0000 Subject: fish --- libopencm3/lib/usb/usb.c | 175 ++++++++ libopencm3/lib/usb/usb_control.c | 288 +++++++++++++ libopencm3/lib/usb/usb_f103.c | 346 +++++++++++++++ libopencm3/lib/usb/usb_f107.c | 91 ++++ libopencm3/lib/usb/usb_f207.c | 91 ++++ libopencm3/lib/usb/usb_fx07_common.c | 338 +++++++++++++++ libopencm3/lib/usb/usb_fx07_common.h | 39 ++ libopencm3/lib/usb/usb_msc.c | 814 +++++++++++++++++++++++++++++++++++ libopencm3/lib/usb/usb_private.h | 163 +++++++ libopencm3/lib/usb/usb_standard.c | 532 +++++++++++++++++++++++ 10 files changed, 2877 insertions(+) create mode 100644 libopencm3/lib/usb/usb.c create mode 100644 libopencm3/lib/usb/usb_control.c create mode 100644 libopencm3/lib/usb/usb_f103.c create mode 100644 libopencm3/lib/usb/usb_f107.c create mode 100644 libopencm3/lib/usb/usb_f207.c create mode 100644 libopencm3/lib/usb/usb_fx07_common.c create mode 100644 libopencm3/lib/usb/usb_fx07_common.h create mode 100644 libopencm3/lib/usb/usb_msc.c create mode 100644 libopencm3/lib/usb/usb_private.h create mode 100644 libopencm3/lib/usb/usb_standard.c (limited to 'libopencm3/lib/usb') diff --git a/libopencm3/lib/usb/usb.c b/libopencm3/lib/usb/usb.c new file mode 100644 index 0000000..7863736 --- /dev/null +++ b/libopencm3/lib/usb/usb.c @@ -0,0 +1,175 @@ +/** @defgroup usb_drivers_file Generic USB Drivers + +@ingroup USB + +@brief Generic USB Drivers + +@version 1.0.0 + +@author @htmlonly © @endhtmlonly 2010 +Gareth McMullin + +@date 10 March 2013 + +LGPL License Terms @ref lgpl_license +*/ + +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2010 Gareth McMullin + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/**@{*/ + +#include +#include +#include "usb_private.h" + +/** + * Main initialization entry point. + * + * Initialize the USB firmware library to implement the USB device described + * by the descriptors provided. + * + * It is required that the 48MHz USB clock is already available. + * + * @param driver TODO + * @param dev Pointer to USB device descriptor. This must not be changed while + * the device is in use. + * @param conf Pointer to array of USB configuration descriptors. These must + * not be changed while the device is in use. The length of this + * array is determined by the bNumConfigurations field in the + * device descriptor. + * @param strings TODO + * @param control_buffer Pointer to array that would hold the data + * received during control requests with DATA + * stage + * @param control_buffer_size Size of control_buffer + * @return Zero on success (currently cannot fail). + */ +usbd_device *usbd_init(const usbd_driver *driver, + const struct usb_device_descriptor *dev, + const struct usb_config_descriptor *conf, + const char **strings, int num_strings, + uint8_t *control_buffer, uint16_t control_buffer_size) +{ + usbd_device *usbd_dev; + + usbd_dev = driver->init(); + + usbd_dev->driver = driver; + usbd_dev->desc = dev; + usbd_dev->config = conf; + usbd_dev->strings = strings; + usbd_dev->num_strings = num_strings; + usbd_dev->ctrl_buf = control_buffer; + usbd_dev->ctrl_buf_len = control_buffer_size; + + usbd_dev->user_callback_ctr[0][USB_TRANSACTION_SETUP] = + _usbd_control_setup; + usbd_dev->user_callback_ctr[0][USB_TRANSACTION_OUT] = + _usbd_control_out; + usbd_dev->user_callback_ctr[0][USB_TRANSACTION_IN] = + _usbd_control_in; + + return usbd_dev; +} + +void usbd_register_reset_callback(usbd_device *usbd_dev, void (*callback)(void)) +{ + usbd_dev->user_callback_reset = callback; +} + +void usbd_register_suspend_callback(usbd_device *usbd_dev, + void (*callback)(void)) +{ + usbd_dev->user_callback_suspend = callback; +} + +void usbd_register_resume_callback(usbd_device *usbd_dev, + void (*callback)(void)) +{ + usbd_dev->user_callback_resume = callback; +} + +void usbd_register_sof_callback(usbd_device *usbd_dev, void (*callback)(void)) +{ + usbd_dev->user_callback_sof = callback; +} + +void _usbd_reset(usbd_device *usbd_dev) +{ + usbd_dev->current_address = 0; + usbd_dev->current_config = 0; + usbd_ep_setup(usbd_dev, 0, USB_ENDPOINT_ATTR_CONTROL, 64, NULL); + usbd_dev->driver->set_address(usbd_dev, 0); + + if (usbd_dev->user_callback_reset) { + usbd_dev->user_callback_reset(); + } +} + +/* Functions to wrap the low-level driver */ +void usbd_poll(usbd_device *usbd_dev) +{ + usbd_dev->driver->poll(usbd_dev); +} + +void usbd_disconnect(usbd_device *usbd_dev, bool disconnected) +{ + /* not all drivers support disconnection */ + if (usbd_dev->driver->disconnect) { + usbd_dev->driver->disconnect(usbd_dev, disconnected); + } +} + +void usbd_ep_setup(usbd_device *usbd_dev, uint8_t addr, uint8_t type, + uint16_t max_size, + void (*callback)(usbd_device *usbd_dev, uint8_t ep)) +{ + usbd_dev->driver->ep_setup(usbd_dev, addr, type, max_size, callback); +} + +uint16_t usbd_ep_write_packet(usbd_device *usbd_dev, uint8_t addr, + const void *buf, uint16_t len) +{ + return usbd_dev->driver->ep_write_packet(usbd_dev, addr, buf, len); +} + +uint16_t usbd_ep_read_packet(usbd_device *usbd_dev, uint8_t addr, void *buf, + uint16_t len) +{ + return usbd_dev->driver->ep_read_packet(usbd_dev, addr, buf, len); +} + +void usbd_ep_stall_set(usbd_device *usbd_dev, uint8_t addr, uint8_t stall) +{ + usbd_dev->driver->ep_stall_set(usbd_dev, addr, stall); +} + +uint8_t usbd_ep_stall_get(usbd_device *usbd_dev, uint8_t addr) +{ + return usbd_dev->driver->ep_stall_get(usbd_dev, addr); +} + +void usbd_ep_nak_set(usbd_device *usbd_dev, uint8_t addr, uint8_t nak) +{ + usbd_dev->driver->ep_nak_set(usbd_dev, addr, nak); +} + +/**@}*/ + diff --git a/libopencm3/lib/usb/usb_control.c b/libopencm3/lib/usb/usb_control.c new file mode 100644 index 0000000..1b87057 --- /dev/null +++ b/libopencm3/lib/usb/usb_control.c @@ -0,0 +1,288 @@ +/** @defgroup usb_control_file Generic USB Control Requests + +@ingroup USB + +@brief Generic USB Control Requests + +@version 1.0.0 + +@author @htmlonly © @endhtmlonly 2010 +Gareth McMullin + +@date 10 March 2013 + +LGPL License Terms @ref lgpl_license +*/ + +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2010 Gareth McMullin + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/**@{*/ + +#include +#include +#include "usb_private.h" + +/* + * According to the USB 2.0 specification, section 8.5.3, when a control + * transfer is stalled, the pipe becomes idle. We provide one utility to stall + * a transaction to reduce boilerplate code. + */ +static void stall_transaction(usbd_device *usbd_dev) +{ + usbd_ep_stall_set(usbd_dev, 0, 1); + usbd_dev->control_state.state = IDLE; +} + +/* Register application callback function for handling USB control requests. */ +int usbd_register_control_callback(usbd_device *usbd_dev, uint8_t type, + uint8_t type_mask, + usbd_control_callback callback) +{ + int i; + + for (i = 0; i < MAX_USER_CONTROL_CALLBACK; i++) { + if (usbd_dev->user_control_callback[i].cb) { + continue; + } + + usbd_dev->user_control_callback[i].type = type; + usbd_dev->user_control_callback[i].type_mask = type_mask; + usbd_dev->user_control_callback[i].cb = callback; + return 0; + } + + return -1; +} + +static void usb_control_send_chunk(usbd_device *usbd_dev) +{ + if (usbd_dev->desc->bMaxPacketSize0 < + usbd_dev->control_state.ctrl_len) { + /* Data stage, normal transmission */ + usbd_ep_write_packet(usbd_dev, 0, + usbd_dev->control_state.ctrl_buf, + usbd_dev->desc->bMaxPacketSize0); + usbd_dev->control_state.state = DATA_IN; + usbd_dev->control_state.ctrl_buf += + usbd_dev->desc->bMaxPacketSize0; + usbd_dev->control_state.ctrl_len -= + usbd_dev->desc->bMaxPacketSize0; + } else { + /* Data stage, end of transmission */ + usbd_ep_write_packet(usbd_dev, 0, + usbd_dev->control_state.ctrl_buf, + usbd_dev->control_state.ctrl_len); + usbd_dev->control_state.state = LAST_DATA_IN; + usbd_dev->control_state.ctrl_len = 0; + usbd_dev->control_state.ctrl_buf = NULL; + } +} + +static int usb_control_recv_chunk(usbd_device *usbd_dev) +{ + uint16_t packetsize = MIN(usbd_dev->desc->bMaxPacketSize0, + usbd_dev->control_state.req.wLength - + usbd_dev->control_state.ctrl_len); + uint16_t size = usbd_ep_read_packet(usbd_dev, 0, + usbd_dev->control_state.ctrl_buf + + usbd_dev->control_state.ctrl_len, + packetsize); + + if (size != packetsize) { + stall_transaction(usbd_dev); + return -1; + } + + usbd_dev->control_state.ctrl_len += size; + + return packetsize; +} + +static int usb_control_request_dispatch(usbd_device *usbd_dev, + struct usb_setup_data *req) +{ + int i, result = 0; + struct user_control_callback *cb = usbd_dev->user_control_callback; + + /* Call user command hook function. */ + for (i = 0; i < MAX_USER_CONTROL_CALLBACK; i++) { + if (cb[i].cb == NULL) { + break; + } + + if ((req->bmRequestType & cb[i].type_mask) == cb[i].type) { + result = cb[i].cb(usbd_dev, req, + &(usbd_dev->control_state.ctrl_buf), + &(usbd_dev->control_state.ctrl_len), + &(usbd_dev->control_state.complete)); + if (result == USBD_REQ_HANDLED || + result == USBD_REQ_NOTSUPP) { + return result; + } + } + } + + /* Try standard request if not already handled. */ + return _usbd_standard_request(usbd_dev, req, + &(usbd_dev->control_state.ctrl_buf), + &(usbd_dev->control_state.ctrl_len)); +} + +/* Handle commands and read requests. */ +static void usb_control_setup_read(usbd_device *usbd_dev, + struct usb_setup_data *req) +{ + usbd_dev->control_state.ctrl_buf = usbd_dev->ctrl_buf; + usbd_dev->control_state.ctrl_len = req->wLength; + + if (usb_control_request_dispatch(usbd_dev, req)) { + if (usbd_dev->control_state.ctrl_len) { + /* Go to data out stage if handled. */ + usb_control_send_chunk(usbd_dev); + } else { + /* Go to status stage if handled. */ + usbd_ep_write_packet(usbd_dev, 0, NULL, 0); + usbd_dev->control_state.state = STATUS_IN; + } + } else { + /* Stall endpoint on failure. */ + stall_transaction(usbd_dev); + } +} + +static void usb_control_setup_write(usbd_device *usbd_dev, + struct usb_setup_data *req) +{ + if (req->wLength > usbd_dev->ctrl_buf_len) { + stall_transaction(usbd_dev); + return; + } + + /* Buffer into which to write received data. */ + usbd_dev->control_state.ctrl_buf = usbd_dev->ctrl_buf; + usbd_dev->control_state.ctrl_len = 0; + /* Wait for DATA OUT stage. */ + if (req->wLength > usbd_dev->desc->bMaxPacketSize0) { + usbd_dev->control_state.state = DATA_OUT; + } else { + usbd_dev->control_state.state = LAST_DATA_OUT; + } +} + +/* Do not appear to belong to the API, so are omitted from docs */ +/**@}*/ + +void _usbd_control_setup(usbd_device *usbd_dev, uint8_t ea) +{ + struct usb_setup_data *req = &usbd_dev->control_state.req; + (void)ea; + + usbd_dev->control_state.complete = NULL; + + if (usbd_ep_read_packet(usbd_dev, 0, req, 8) != 8) { + stall_transaction(usbd_dev); + return; + } + + if (req->wLength == 0) { + usb_control_setup_read(usbd_dev, req); + } else if (req->bmRequestType & 0x80) { + usb_control_setup_read(usbd_dev, req); + } else { + usb_control_setup_write(usbd_dev, req); + } +} + +void _usbd_control_out(usbd_device *usbd_dev, uint8_t ea) +{ + (void)ea; + + switch (usbd_dev->control_state.state) { + case DATA_OUT: + if (usb_control_recv_chunk(usbd_dev) < 0) { + break; + } + if ((usbd_dev->control_state.req.wLength - + usbd_dev->control_state.ctrl_len) <= + usbd_dev->desc->bMaxPacketSize0) { + usbd_dev->control_state.state = LAST_DATA_OUT; + } + break; + case LAST_DATA_OUT: + if (usb_control_recv_chunk(usbd_dev) < 0) { + break; + } + /* + * We have now received the full data payload. + * Invoke callback to process. + */ + if (usb_control_request_dispatch(usbd_dev, + &(usbd_dev->control_state.req))) { + /* Got to status stage on success. */ + usbd_ep_write_packet(usbd_dev, 0, NULL, 0); + usbd_dev->control_state.state = STATUS_IN; + } else { + stall_transaction(usbd_dev); + } + break; + case STATUS_OUT: + usbd_ep_read_packet(usbd_dev, 0, NULL, 0); + usbd_dev->control_state.state = IDLE; + if (usbd_dev->control_state.complete) { + usbd_dev->control_state.complete(usbd_dev, + &(usbd_dev->control_state.req)); + } + usbd_dev->control_state.complete = NULL; + break; + default: + stall_transaction(usbd_dev); + } +} + +void _usbd_control_in(usbd_device *usbd_dev, uint8_t ea) +{ + (void)ea; + struct usb_setup_data *req = &(usbd_dev->control_state.req); + + switch (usbd_dev->control_state.state) { + case DATA_IN: + usb_control_send_chunk(usbd_dev); + break; + case LAST_DATA_IN: + usbd_dev->control_state.state = STATUS_OUT; + break; + case STATUS_IN: + if (usbd_dev->control_state.complete) { + usbd_dev->control_state.complete(usbd_dev, + &(usbd_dev->control_state.req)); + } + + /* Exception: Handle SET ADDRESS function here... */ + if ((req->bmRequestType == 0) && + (req->bRequest == USB_REQ_SET_ADDRESS)) { + usbd_dev->driver->set_address(usbd_dev, req->wValue); + } + usbd_dev->control_state.state = IDLE; + break; + default: + stall_transaction(usbd_dev); + } +} + diff --git a/libopencm3/lib/usb/usb_f103.c b/libopencm3/lib/usb/usb_f103.c new file mode 100644 index 0000000..2985650 --- /dev/null +++ b/libopencm3/lib/usb/usb_f103.c @@ -0,0 +1,346 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2010 Gareth McMullin + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include +#include +#include "usb_private.h" + +static usbd_device *stm32f103_usbd_init(void); +static void stm32f103_set_address(usbd_device *usbd_dev, uint8_t addr); +static void stm32f103_ep_setup(usbd_device *usbd_dev, uint8_t addr, + uint8_t type, uint16_t max_size, + void (*callback) (usbd_device *usbd_dev, + uint8_t ep)); +static void stm32f103_endpoints_reset(usbd_device *usbd_dev); +static void stm32f103_ep_stall_set(usbd_device *usbd_dev, uint8_t addr, + uint8_t stall); +static uint8_t stm32f103_ep_stall_get(usbd_device *usbd_dev, uint8_t addr); +static void stm32f103_ep_nak_set(usbd_device *usbd_dev, uint8_t addr, + uint8_t nak); +static uint16_t stm32f103_ep_write_packet(usbd_device *usbd_dev, uint8_t addr, + const void *buf, uint16_t len); +static uint16_t stm32f103_ep_read_packet(usbd_device *usbd_dev, uint8_t addr, + void *buf, uint16_t len); +static void stm32f103_poll(usbd_device *usbd_dev); + +static uint8_t force_nak[8]; +static struct _usbd_device usbd_dev; + +const struct _usbd_driver stm32f103_usb_driver = { + .init = stm32f103_usbd_init, + .set_address = stm32f103_set_address, + .ep_setup = stm32f103_ep_setup, + .ep_reset = stm32f103_endpoints_reset, + .ep_stall_set = stm32f103_ep_stall_set, + .ep_stall_get = stm32f103_ep_stall_get, + .ep_nak_set = stm32f103_ep_nak_set, + .ep_write_packet = stm32f103_ep_write_packet, + .ep_read_packet = stm32f103_ep_read_packet, + .poll = stm32f103_poll, +}; + +/** Initialize the USB device controller hardware of the STM32. */ +static usbd_device *stm32f103_usbd_init(void) +{ + rcc_peripheral_enable_clock(&RCC_APB1ENR, RCC_APB1ENR_USBEN); + SET_REG(USB_CNTR_REG, 0); + SET_REG(USB_BTABLE_REG, 0); + SET_REG(USB_ISTR_REG, 0); + + /* Enable RESET, SUSPEND, RESUME and CTR interrupts. */ + SET_REG(USB_CNTR_REG, USB_CNTR_RESETM | USB_CNTR_CTRM | + USB_CNTR_SUSPM | USB_CNTR_WKUPM); + return &usbd_dev; +} + +static void stm32f103_set_address(usbd_device *dev, uint8_t addr) +{ + (void)dev; + /* Set device address and enable. */ + SET_REG(USB_DADDR_REG, (addr & USB_DADDR_ADDR) | USB_DADDR_ENABLE); +} + +/** + * Set the receive buffer size for a given USB endpoint. + * + * @param ep Index of endpoint to configure. + * @param size Size in bytes of the RX buffer. + */ +static void usb_set_ep_rx_bufsize(usbd_device *dev, uint8_t ep, uint32_t size) +{ + (void)dev; + if (size > 62) { + if (size & 0x1f) { + size -= 32; + } + USB_SET_EP_RX_COUNT(ep, (size << 5) | 0x8000); + } else { + if (size & 1) { + size++; + } + USB_SET_EP_RX_COUNT(ep, size << 10); + } +} + +static void stm32f103_ep_setup(usbd_device *dev, uint8_t addr, uint8_t type, + uint16_t max_size, + void (*callback) (usbd_device *usbd_dev, + uint8_t ep)) +{ + /* Translate USB standard type codes to STM32. */ + const uint16_t typelookup[] = { + [USB_ENDPOINT_ATTR_CONTROL] = USB_EP_TYPE_CONTROL, + [USB_ENDPOINT_ATTR_ISOCHRONOUS] = USB_EP_TYPE_ISO, + [USB_ENDPOINT_ATTR_BULK] = USB_EP_TYPE_BULK, + [USB_ENDPOINT_ATTR_INTERRUPT] = USB_EP_TYPE_INTERRUPT, + }; + uint8_t dir = addr & 0x80; + addr &= 0x7f; + + /* Assign address. */ + USB_SET_EP_ADDR(addr, addr); + USB_SET_EP_TYPE(addr, typelookup[type]); + + if (dir || (addr == 0)) { + USB_SET_EP_TX_ADDR(addr, dev->pm_top); + if (callback) { + dev->user_callback_ctr[addr][USB_TRANSACTION_IN] = + (void *)callback; + } + USB_CLR_EP_TX_DTOG(addr); + USB_SET_EP_TX_STAT(addr, USB_EP_TX_STAT_NAK); + dev->pm_top += max_size; + } + + if (!dir) { + USB_SET_EP_RX_ADDR(addr, dev->pm_top); + usb_set_ep_rx_bufsize(dev, addr, max_size); + if (callback) { + dev->user_callback_ctr[addr][USB_TRANSACTION_OUT] = + (void *)callback; + } + USB_CLR_EP_RX_DTOG(addr); + USB_SET_EP_RX_STAT(addr, USB_EP_RX_STAT_VALID); + dev->pm_top += max_size; + } +} + +static void stm32f103_endpoints_reset(usbd_device *dev) +{ + int i; + + /* Reset all endpoints. */ + for (i = 1; i < 8; i++) { + USB_SET_EP_TX_STAT(i, USB_EP_TX_STAT_DISABLED); + USB_SET_EP_RX_STAT(i, USB_EP_RX_STAT_DISABLED); + } + dev->pm_top = 0x40 + (2 * dev->desc->bMaxPacketSize0); +} + +static void stm32f103_ep_stall_set(usbd_device *dev, uint8_t addr, + uint8_t stall) +{ + (void)dev; + if (addr == 0) { + USB_SET_EP_TX_STAT(addr, stall ? USB_EP_TX_STAT_STALL : + USB_EP_TX_STAT_NAK); + } + + if (addr & 0x80) { + addr &= 0x7F; + + USB_SET_EP_TX_STAT(addr, stall ? USB_EP_TX_STAT_STALL : + USB_EP_TX_STAT_NAK); + + /* Reset to DATA0 if clearing stall condition. */ + if (!stall) { + USB_CLR_EP_TX_DTOG(addr); + } + } else { + /* Reset to DATA0 if clearing stall condition. */ + if (!stall) { + USB_CLR_EP_RX_DTOG(addr); + } + + USB_SET_EP_RX_STAT(addr, stall ? USB_EP_RX_STAT_STALL : + USB_EP_RX_STAT_VALID); + } +} + +static uint8_t stm32f103_ep_stall_get(usbd_device *dev, uint8_t addr) +{ + (void)dev; + if (addr & 0x80) { + if ((*USB_EP_REG(addr & 0x7F) & USB_EP_TX_STAT) == + USB_EP_TX_STAT_STALL) { + return 1; + } + } else { + if ((*USB_EP_REG(addr) & USB_EP_RX_STAT) == + USB_EP_RX_STAT_STALL) { + return 1; + } + } + return 0; +} + +static void stm32f103_ep_nak_set(usbd_device *dev, uint8_t addr, uint8_t nak) +{ + (void)dev; + /* It does not make sence to force NAK on IN endpoints. */ + if (addr & 0x80) { + return; + } + + force_nak[addr] = nak; + + if (nak) { + USB_SET_EP_RX_STAT(addr, USB_EP_RX_STAT_NAK); + } else { + USB_SET_EP_RX_STAT(addr, USB_EP_RX_STAT_VALID); + } +} + +/** + * Copy a data buffer to packet memory. + * + * @param vPM Destination pointer into packet memory. + * @param buf Source pointer to data buffer. + * @param len Number of bytes to copy. + */ +static void usb_copy_to_pm(volatile void *vPM, const void *buf, uint16_t len) +{ + const uint16_t *lbuf = buf; + volatile uint16_t *PM = vPM; + + for (len = (len + 1) >> 1; len; PM += 2, lbuf++, len--) { + *PM = *lbuf; + } +} + +static uint16_t stm32f103_ep_write_packet(usbd_device *dev, uint8_t addr, + const void *buf, uint16_t len) +{ + (void)dev; + addr &= 0x7F; + + if ((*USB_EP_REG(addr) & USB_EP_TX_STAT) == USB_EP_TX_STAT_VALID) { + return 0; + } + + usb_copy_to_pm(USB_GET_EP_TX_BUFF(addr), buf, len); + USB_SET_EP_TX_COUNT(addr, len); + USB_SET_EP_TX_STAT(addr, USB_EP_TX_STAT_VALID); + + return len; +} + +/** + * Copy a data buffer from packet memory. + * + * @param buf Source pointer to data buffer. + * @param vPM Destination pointer into packet memory. + * @param len Number of bytes to copy. + */ +static void usb_copy_from_pm(void *buf, const volatile void *vPM, uint16_t len) +{ + uint16_t *lbuf = buf; + const volatile uint16_t *PM = vPM; + uint8_t odd = len & 1; + + for (len >>= 1; len; PM += 2, lbuf++, len--) { + *lbuf = *PM; + } + + if (odd) { + *(uint8_t *) lbuf = *(uint8_t *) PM; + } +} + +static uint16_t stm32f103_ep_read_packet(usbd_device *dev, uint8_t addr, + void *buf, uint16_t len) +{ + (void)dev; + if ((*USB_EP_REG(addr) & USB_EP_RX_STAT) == USB_EP_RX_STAT_VALID) { + return 0; + } + + len = MIN(USB_GET_EP_RX_COUNT(addr) & 0x3ff, len); + usb_copy_from_pm(buf, USB_GET_EP_RX_BUFF(addr), len); + USB_CLR_EP_RX_CTR(addr); + + if (!force_nak[addr]) { + USB_SET_EP_RX_STAT(addr, USB_EP_RX_STAT_VALID); + } + + return len; +} + +static void stm32f103_poll(usbd_device *dev) +{ + uint16_t istr = *USB_ISTR_REG; + + if (istr & USB_ISTR_RESET) { + dev->pm_top = 0x40; + _usbd_reset(dev); + USB_CLR_ISTR_RESET(); + return; + } + + if (istr & USB_ISTR_CTR) { + uint8_t ep = istr & USB_ISTR_EP_ID; + uint8_t type = (istr & USB_ISTR_DIR) ? 1 : 0; + + if (type) { /* OUT or SETUP transaction */ + type += (*USB_EP_REG(ep) & USB_EP_SETUP) ? 1 : 0; + } else { /* IN transaction */ + USB_CLR_EP_TX_CTR(ep); + } + + if (dev->user_callback_ctr[ep][type]) { + dev->user_callback_ctr[ep][type] (dev, ep); + } else { + USB_CLR_EP_RX_CTR(ep); + } + } + + if (istr & USB_ISTR_SUSP) { + USB_CLR_ISTR_SUSP(); + if (dev->user_callback_suspend) { + dev->user_callback_suspend(); + } + } + + if (istr & USB_ISTR_WKUP) { + USB_CLR_ISTR_WKUP(); + if (dev->user_callback_resume) { + dev->user_callback_resume(); + } + } + + if (istr & USB_ISTR_SOF) { + if (dev->user_callback_sof) { + dev->user_callback_sof(); + } + USB_CLR_ISTR_SOF(); + } +} diff --git a/libopencm3/lib/usb/usb_f107.c b/libopencm3/lib/usb/usb_f107.c new file mode 100644 index 0000000..32aecab --- /dev/null +++ b/libopencm3/lib/usb/usb_f107.c @@ -0,0 +1,91 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2011 Gareth McMullin + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include +#include +#include "usb_private.h" +#include "usb_fx07_common.h" + +/* Receive FIFO size in 32-bit words. */ +#define RX_FIFO_SIZE 128 + +static usbd_device *stm32f107_usbd_init(void); + +static struct _usbd_device usbd_dev; + +const struct _usbd_driver stm32f107_usb_driver = { + .init = stm32f107_usbd_init, + .set_address = stm32fx07_set_address, + .ep_setup = stm32fx07_ep_setup, + .ep_reset = stm32fx07_endpoints_reset, + .ep_stall_set = stm32fx07_ep_stall_set, + .ep_stall_get = stm32fx07_ep_stall_get, + .ep_nak_set = stm32fx07_ep_nak_set, + .ep_write_packet = stm32fx07_ep_write_packet, + .ep_read_packet = stm32fx07_ep_read_packet, + .poll = stm32fx07_poll, + .disconnect = stm32fx07_disconnect, + .base_address = USB_OTG_FS_BASE, + .set_address_before_status = 1, + .rx_fifo_size = RX_FIFO_SIZE, +}; + +/** Initialize the USB device controller hardware of the STM32. */ +static usbd_device *stm32f107_usbd_init(void) +{ + OTG_FS_GINTSTS = OTG_FS_GINTSTS_MMIS; + + OTG_FS_GUSBCFG |= OTG_FS_GUSBCFG_PHYSEL; + /* Enable VBUS sensing in device mode and power down the PHY. */ + OTG_FS_GCCFG |= OTG_FS_GCCFG_VBUSBSEN | OTG_FS_GCCFG_PWRDWN; + + /* Wait for AHB idle. */ + while (!(OTG_FS_GRSTCTL & OTG_FS_GRSTCTL_AHBIDL)); + /* Do core soft reset. */ + OTG_FS_GRSTCTL |= OTG_FS_GRSTCTL_CSRST; + while (OTG_FS_GRSTCTL & OTG_FS_GRSTCTL_CSRST); + + /* Force peripheral only mode. */ + OTG_FS_GUSBCFG |= OTG_FS_GUSBCFG_FDMOD | OTG_FS_GUSBCFG_TRDT_MASK; + + /* Full speed device. */ + OTG_FS_DCFG |= OTG_FS_DCFG_DSPD; + + /* Restart the PHY clock. */ + OTG_FS_PCGCCTL = 0; + + OTG_FS_GRXFSIZ = stm32f107_usb_driver.rx_fifo_size; + usbd_dev.fifo_mem_top = stm32f107_usb_driver.rx_fifo_size; + + /* Unmask interrupts for TX and RX. */ + OTG_FS_GAHBCFG |= OTG_FS_GAHBCFG_GINT; + OTG_FS_GINTMSK = OTG_FS_GINTMSK_ENUMDNEM | + OTG_FS_GINTMSK_RXFLVLM | + OTG_FS_GINTMSK_IEPINT | + OTG_FS_GINTMSK_USBSUSPM | + OTG_FS_GINTMSK_WUIM | + OTG_FS_GINTMSK_SOFM; + OTG_FS_DAINTMSK = 0xF; + OTG_FS_DIEPMSK = OTG_FS_DIEPMSK_XFRCM; + + return &usbd_dev; +} diff --git a/libopencm3/lib/usb/usb_f207.c b/libopencm3/lib/usb/usb_f207.c new file mode 100644 index 0000000..b27d3cd --- /dev/null +++ b/libopencm3/lib/usb/usb_f207.c @@ -0,0 +1,91 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2011 Gareth McMullin + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include +#include +#include "usb_private.h" +#include "usb_fx07_common.h" + +/* Receive FIFO size in 32-bit words. */ +#define RX_FIFO_SIZE 512 + +static usbd_device *stm32f207_usbd_init(void); + +static struct _usbd_device usbd_dev; + +const struct _usbd_driver stm32f207_usb_driver = { + .init = stm32f207_usbd_init, + .set_address = stm32fx07_set_address, + .ep_setup = stm32fx07_ep_setup, + .ep_reset = stm32fx07_endpoints_reset, + .ep_stall_set = stm32fx07_ep_stall_set, + .ep_stall_get = stm32fx07_ep_stall_get, + .ep_nak_set = stm32fx07_ep_nak_set, + .ep_write_packet = stm32fx07_ep_write_packet, + .ep_read_packet = stm32fx07_ep_read_packet, + .poll = stm32fx07_poll, + .disconnect = stm32fx07_disconnect, + .base_address = USB_OTG_HS_BASE, + .set_address_before_status = 1, + .rx_fifo_size = RX_FIFO_SIZE, +}; + +/** Initialize the USB device controller hardware of the STM32. */ +static usbd_device *stm32f207_usbd_init(void) +{ + OTG_HS_GINTSTS = OTG_HS_GINTSTS_MMIS; + + OTG_HS_GUSBCFG |= OTG_HS_GUSBCFG_PHYSEL; + /* Enable VBUS sensing in device mode and power down the PHY. */ + OTG_HS_GCCFG |= OTG_HS_GCCFG_VBUSBSEN | OTG_HS_GCCFG_PWRDWN; + + /* Wait for AHB idle. */ + while (!(OTG_HS_GRSTCTL & OTG_HS_GRSTCTL_AHBIDL)); + /* Do core soft reset. */ + OTG_HS_GRSTCTL |= OTG_HS_GRSTCTL_CSRST; + while (OTG_HS_GRSTCTL & OTG_HS_GRSTCTL_CSRST); + + /* Force peripheral only mode. */ + OTG_HS_GUSBCFG |= OTG_HS_GUSBCFG_FDMOD | OTG_HS_GUSBCFG_TRDT_MASK; + + /* Full speed device. */ + OTG_HS_DCFG |= OTG_HS_DCFG_DSPD; + + /* Restart the PHY clock. */ + OTG_HS_PCGCCTL = 0; + + OTG_HS_GRXFSIZ = stm32f207_usb_driver.rx_fifo_size; + usbd_dev.fifo_mem_top = stm32f207_usb_driver.rx_fifo_size; + + /* Unmask interrupts for TX and RX. */ + OTG_HS_GAHBCFG |= OTG_HS_GAHBCFG_GINT; + OTG_HS_GINTMSK = OTG_HS_GINTMSK_ENUMDNEM | + OTG_HS_GINTMSK_RXFLVLM | + OTG_HS_GINTMSK_IEPINT | + OTG_HS_GINTMSK_USBSUSPM | + OTG_HS_GINTMSK_WUIM | + OTG_HS_GINTMSK_SOFM; + OTG_HS_DAINTMSK = 0xF; + OTG_HS_DIEPMSK = OTG_HS_DIEPMSK_XFRCM; + + return &usbd_dev; +} diff --git a/libopencm3/lib/usb/usb_fx07_common.c b/libopencm3/lib/usb/usb_fx07_common.c new file mode 100644 index 0000000..c54c7a3 --- /dev/null +++ b/libopencm3/lib/usb/usb_fx07_common.c @@ -0,0 +1,338 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2011 Gareth McMullin + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include "usb_private.h" +#include "usb_fx07_common.h" + +/* The FS core and the HS core have the same register layout. + * As the code can be used on both cores, the registers offset is modified + * according to the selected cores base address. */ +#define dev_base_address (usbd_dev->driver->base_address) +#define REBASE(x) MMIO32((x) + (dev_base_address)) +#define REBASE_FIFO(x) (&MMIO32((dev_base_address) + (OTG_FIFO(x)))) + +void stm32fx07_set_address(usbd_device *usbd_dev, uint8_t addr) +{ + REBASE(OTG_DCFG) = (REBASE(OTG_DCFG) & ~OTG_FS_DCFG_DAD) | (addr << 4); +} + +void stm32fx07_ep_setup(usbd_device *usbd_dev, uint8_t addr, uint8_t type, + uint16_t max_size, + void (*callback) (usbd_device *usbd_dev, uint8_t ep)) +{ + /* + * Configure endpoint address and type. Allocate FIFO memory for + * endpoint. Install callback funciton. + */ + uint8_t dir = addr & 0x80; + addr &= 0x7f; + + if (addr == 0) { /* For the default control endpoint */ + /* Configure IN part. */ + if (max_size >= 64) { + REBASE(OTG_DIEPCTL0) = OTG_FS_DIEPCTL0_MPSIZ_64; + } else if (max_size >= 32) { + REBASE(OTG_DIEPCTL0) = OTG_FS_DIEPCTL0_MPSIZ_32; + } else if (max_size >= 16) { + REBASE(OTG_DIEPCTL0) = OTG_FS_DIEPCTL0_MPSIZ_16; + } else { + REBASE(OTG_DIEPCTL0) = OTG_FS_DIEPCTL0_MPSIZ_8; + } + + REBASE(OTG_DIEPTSIZ0) = + (max_size & OTG_FS_DIEPSIZ0_XFRSIZ_MASK); + REBASE(OTG_DIEPCTL0) |= + OTG_FS_DIEPCTL0_EPENA | OTG_FS_DIEPCTL0_SNAK; + + /* Configure OUT part. */ + usbd_dev->doeptsiz[0] = OTG_FS_DIEPSIZ0_STUPCNT_1 | + OTG_FS_DIEPSIZ0_PKTCNT | + (max_size & OTG_FS_DIEPSIZ0_XFRSIZ_MASK); + REBASE(OTG_DOEPTSIZ(0)) = usbd_dev->doeptsiz[0]; + REBASE(OTG_DOEPCTL(0)) |= + OTG_FS_DOEPCTL0_EPENA | OTG_FS_DIEPCTL0_SNAK; + + REBASE(OTG_GNPTXFSIZ) = ((max_size / 4) << 16) | + usbd_dev->driver->rx_fifo_size; + usbd_dev->fifo_mem_top += max_size / 4; + usbd_dev->fifo_mem_top_ep0 = usbd_dev->fifo_mem_top; + + return; + } + + if (dir) { + REBASE(OTG_DIEPTXF(addr)) = ((max_size / 4) << 16) | + usbd_dev->fifo_mem_top; + usbd_dev->fifo_mem_top += max_size / 4; + + REBASE(OTG_DIEPTSIZ(addr)) = + (max_size & OTG_FS_DIEPSIZ0_XFRSIZ_MASK); + REBASE(OTG_DIEPCTL(addr)) |= + OTG_FS_DIEPCTL0_EPENA | OTG_FS_DIEPCTL0_SNAK | (type << 18) + | OTG_FS_DIEPCTL0_USBAEP | OTG_FS_DIEPCTLX_SD0PID + | (addr << 22) | max_size; + + if (callback) { + usbd_dev->user_callback_ctr[addr][USB_TRANSACTION_IN] = + (void *)callback; + } + } + + if (!dir) { + usbd_dev->doeptsiz[addr] = OTG_FS_DIEPSIZ0_PKTCNT | + (max_size & OTG_FS_DIEPSIZ0_XFRSIZ_MASK); + REBASE(OTG_DOEPTSIZ(addr)) = usbd_dev->doeptsiz[addr]; + REBASE(OTG_DOEPCTL(addr)) |= OTG_FS_DOEPCTL0_EPENA | + OTG_FS_DOEPCTL0_USBAEP | OTG_FS_DIEPCTL0_CNAK | + OTG_FS_DOEPCTLX_SD0PID | (type << 18) | max_size; + + if (callback) { + usbd_dev->user_callback_ctr[addr][USB_TRANSACTION_OUT] = + (void *)callback; + } + } +} + +void stm32fx07_endpoints_reset(usbd_device *usbd_dev) +{ + /* The core resets the endpoints automatically on reset. */ + usbd_dev->fifo_mem_top = usbd_dev->fifo_mem_top_ep0; +} + +void stm32fx07_ep_stall_set(usbd_device *usbd_dev, uint8_t addr, uint8_t stall) +{ + if (addr == 0) { + if (stall) { + REBASE(OTG_DIEPCTL(addr)) |= OTG_FS_DIEPCTL0_STALL; + } else { + REBASE(OTG_DIEPCTL(addr)) &= ~OTG_FS_DIEPCTL0_STALL; + } + } + + if (addr & 0x80) { + addr &= 0x7F; + + if (stall) { + REBASE(OTG_DIEPCTL(addr)) |= OTG_FS_DIEPCTL0_STALL; + } else { + REBASE(OTG_DIEPCTL(addr)) &= ~OTG_FS_DIEPCTL0_STALL; + REBASE(OTG_DIEPCTL(addr)) |= OTG_FS_DIEPCTLX_SD0PID; + } + } else { + if (stall) { + REBASE(OTG_DOEPCTL(addr)) |= OTG_FS_DOEPCTL0_STALL; + } else { + REBASE(OTG_DOEPCTL(addr)) &= ~OTG_FS_DOEPCTL0_STALL; + REBASE(OTG_DOEPCTL(addr)) |= OTG_FS_DOEPCTLX_SD0PID; + } + } +} + +uint8_t stm32fx07_ep_stall_get(usbd_device *usbd_dev, uint8_t addr) +{ + /* Return non-zero if STALL set. */ + if (addr & 0x80) { + return (REBASE(OTG_DIEPCTL(addr & 0x7f)) & + OTG_FS_DIEPCTL0_STALL) ? 1 : 0; + } else { + return (REBASE(OTG_DOEPCTL(addr)) & + OTG_FS_DOEPCTL0_STALL) ? 1 : 0; + } +} + +void stm32fx07_ep_nak_set(usbd_device *usbd_dev, uint8_t addr, uint8_t nak) +{ + /* It does not make sence to force NAK on IN endpoints. */ + if (addr & 0x80) { + return; + } + + usbd_dev->force_nak[addr] = nak; + + if (nak) { + REBASE(OTG_DOEPCTL(addr)) |= OTG_FS_DOEPCTL0_SNAK; + } else { + REBASE(OTG_DOEPCTL(addr)) |= OTG_FS_DOEPCTL0_CNAK; + } +} + +uint16_t stm32fx07_ep_write_packet(usbd_device *usbd_dev, uint8_t addr, + const void *buf, uint16_t len) +{ + const uint32_t *buf32 = buf; + int i; + + addr &= 0x7F; + + /* Return if endpoint is already enabled. */ + if (REBASE(OTG_DIEPTSIZ(addr)) & OTG_FS_DIEPSIZ0_PKTCNT) { + return 0; + } + + /* Enable endpoint for transmission. */ + REBASE(OTG_DIEPTSIZ(addr)) = OTG_FS_DIEPSIZ0_PKTCNT | len; + REBASE(OTG_DIEPCTL(addr)) |= OTG_FS_DIEPCTL0_EPENA | + OTG_FS_DIEPCTL0_CNAK; + volatile uint32_t *fifo = REBASE_FIFO(addr); + + /* Copy buffer to endpoint FIFO, note - memcpy does not work */ + for (i = len; i > 0; i -= 4) { + *fifo++ = *buf32++; + } + + return len; +} + +uint16_t stm32fx07_ep_read_packet(usbd_device *usbd_dev, uint8_t addr, + void *buf, uint16_t len) +{ + int i; + uint32_t *buf32 = buf; + uint32_t extra; + + len = MIN(len, usbd_dev->rxbcnt); + usbd_dev->rxbcnt -= len; + + volatile uint32_t *fifo = REBASE_FIFO(addr); + for (i = len; i >= 4; i -= 4) { + *buf32++ = *fifo++; + } + + if (i) { + extra = *fifo++; + memcpy(buf32, &extra, i); + } + + REBASE(OTG_DOEPTSIZ(addr)) = usbd_dev->doeptsiz[addr]; + REBASE(OTG_DOEPCTL(addr)) |= OTG_FS_DOEPCTL0_EPENA | + (usbd_dev->force_nak[addr] ? + OTG_FS_DOEPCTL0_SNAK : OTG_FS_DOEPCTL0_CNAK); + + return len; +} + +void stm32fx07_poll(usbd_device *usbd_dev) +{ + /* Read interrupt status register. */ + uint32_t intsts = REBASE(OTG_GINTSTS); + int i; + + if (intsts & OTG_FS_GINTSTS_ENUMDNE) { + /* Handle USB RESET condition. */ + REBASE(OTG_GINTSTS) = OTG_FS_GINTSTS_ENUMDNE; + usbd_dev->fifo_mem_top = usbd_dev->driver->rx_fifo_size; + _usbd_reset(usbd_dev); + return; + } + + /* Note: RX and TX handled differently in this device. */ + if (intsts & OTG_FS_GINTSTS_RXFLVL) { + /* Receive FIFO non-empty. */ + uint32_t rxstsp = REBASE(OTG_GRXSTSP); + uint32_t pktsts = rxstsp & OTG_FS_GRXSTSP_PKTSTS_MASK; + if ((pktsts != OTG_FS_GRXSTSP_PKTSTS_OUT) && + (pktsts != OTG_FS_GRXSTSP_PKTSTS_SETUP)) { + return; + } + + uint8_t ep = rxstsp & OTG_FS_GRXSTSP_EPNUM_MASK; + uint8_t type; + if (pktsts == OTG_FS_GRXSTSP_PKTSTS_SETUP) { + type = USB_TRANSACTION_SETUP; + } else { + type = USB_TRANSACTION_OUT; + } + + /* Save packet size for stm32f107_ep_read_packet(). */ + usbd_dev->rxbcnt = (rxstsp & OTG_FS_GRXSTSP_BCNT_MASK) >> 4; + + /* + * FIXME: Why is a delay needed here? + * This appears to fix a problem where the first 4 bytes + * of the DATA OUT stage of a control transaction are lost. + */ + for (i = 0; i < 1000; i++) { + __asm__("nop"); + } + + if (usbd_dev->user_callback_ctr[ep][type]) { + usbd_dev->user_callback_ctr[ep][type] (usbd_dev, ep); + } + + /* Discard unread packet data. */ + for (i = 0; i < usbd_dev->rxbcnt; i += 4) { + (void)*REBASE_FIFO(ep); + } + + usbd_dev->rxbcnt = 0; + } + + /* + * There is no global interrupt flag for transmit complete. + * The XFRC bit must be checked in each OTG_FS_DIEPINT(x). + */ + for (i = 0; i < 4; i++) { /* Iterate over endpoints. */ + if (REBASE(OTG_DIEPINT(i)) & OTG_FS_DIEPINTX_XFRC) { + /* Transfer complete. */ + if (usbd_dev->user_callback_ctr[i] + [USB_TRANSACTION_IN]) { + usbd_dev->user_callback_ctr[i] + [USB_TRANSACTION_IN](usbd_dev, i); + } + + REBASE(OTG_DIEPINT(i)) = OTG_FS_DIEPINTX_XFRC; + } + } + + if (intsts & OTG_FS_GINTSTS_USBSUSP) { + if (usbd_dev->user_callback_suspend) { + usbd_dev->user_callback_suspend(); + } + REBASE(OTG_GINTSTS) = OTG_FS_GINTSTS_USBSUSP; + } + + if (intsts & OTG_FS_GINTSTS_WKUPINT) { + if (usbd_dev->user_callback_resume) { + usbd_dev->user_callback_resume(); + } + REBASE(OTG_GINTSTS) = OTG_FS_GINTSTS_WKUPINT; + } + + if (intsts & OTG_FS_GINTSTS_SOF) { + if (usbd_dev->user_callback_sof) { + usbd_dev->user_callback_sof(); + } + REBASE(OTG_GINTSTS) = OTG_FS_GINTSTS_SOF; + } +} + +void stm32fx07_disconnect(usbd_device *usbd_dev, bool disconnected) +{ + if (disconnected) { + REBASE(OTG_DCTL) |= OTG_FS_DCTL_SDIS; + } else { + REBASE(OTG_DCTL) &= ~OTG_FS_DCTL_SDIS; + } +} diff --git a/libopencm3/lib/usb/usb_fx07_common.h b/libopencm3/lib/usb/usb_fx07_common.h new file mode 100644 index 0000000..31c4030 --- /dev/null +++ b/libopencm3/lib/usb/usb_fx07_common.h @@ -0,0 +1,39 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2011 Gareth McMullin + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __USB_FX07_COMMON_H_ +#define __USB_FX07_COMMON_H_ + +void stm32fx07_set_address(usbd_device *usbd_dev, uint8_t addr); +void stm32fx07_ep_setup(usbd_device *usbd_dev, uint8_t addr, uint8_t type, + uint16_t max_size, + void (*callback)(usbd_device *usbd_dev, uint8_t ep)); +void stm32fx07_endpoints_reset(usbd_device *usbd_dev); +void stm32fx07_ep_stall_set(usbd_device *usbd_dev, uint8_t addr, uint8_t stall); +uint8_t stm32fx07_ep_stall_get(usbd_device *usbd_dev, uint8_t addr); +void stm32fx07_ep_nak_set(usbd_device *usbd_dev, uint8_t addr, uint8_t nak); +uint16_t stm32fx07_ep_write_packet(usbd_device *usbd_dev, uint8_t addr, + const void *buf, uint16_t len); +uint16_t stm32fx07_ep_read_packet(usbd_device *usbd_dev, uint8_t addr, + void *buf, uint16_t len); +void stm32fx07_poll(usbd_device *usbd_dev); +void stm32fx07_disconnect(usbd_device *usbd_dev, bool disconnected); + + +#endif /* __USB_FX07_COMMON_H_ */ diff --git a/libopencm3/lib/usb/usb_msc.c b/libopencm3/lib/usb/usb_msc.c new file mode 100644 index 0000000..573590d --- /dev/null +++ b/libopencm3/lib/usb/usb_msc.c @@ -0,0 +1,814 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2013 Weston Schmidt + * Copyright (C) 2013 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include "usb_private.h" + +/* Definitions of Mass Storage Class from: + * + * (A) "Universal Serial Bus Mass Storage Class Bulk-Only Transport + * Revision 1.0" + * + * (B) "Universal Serial Bus Mass Storage Class Specification Overview + * Revision 1.0" + */ + +/* Command Block Wrapper */ +#define CBW_SIGNATURE 0x43425355 +#define CBW_STATUS_SUCCESS 0 +#define CBW_STATUS_FAILED 1 +#define CBW_STATUS_PHASE_ERROR 2 + +/* Command Status Wrapper */ +#define CSW_SIGNATURE 0x53425355 +#define CSW_STATUS_SUCCESS 0 +#define CSW_STATUS_FAILED 1 +#define CSW_STATUS_PHASE_ERROR 2 + +/* Implemented SCSI Commands */ +#define SCSI_TEST_UNIT_READY 0x00 +#define SCSI_REQUEST_SENSE 0x03 +#define SCSI_FORMAT_UNIT 0x04 +#define SCSI_READ_6 0x08 +#define SCSI_WRITE_6 0x0A +#define SCSI_INQUIRY 0x12 +#define SCSI_MODE_SENSE_6 0x1A +#define SCSI_SEND_DIAGNOSTIC 0x1D +#define SCSI_READ_CAPACITY 0x25 +#define SCSI_READ_10 0x28 + + +/* Required SCSI Commands */ + +/* Optional SCSI Commands */ +#define SCSI_REPORT_LUNS 0xA0 +#define SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E +#define SCSI_MODE_SELECT_6 0x15 +#define SCSI_MODE_SELECT_10 0x55 +#define SCSI_MODE_SENSE_10 0x5A +#define SCSI_READ_12 0xA8 +#define SCSI_READ_FORMAT_CAPACITIES 0x23 +#define SCSI_READ_TOC_PMA_ATIP 0x43 +#define SCSI_START_STOP_UNIT 0x1B +#define SCSI_SYNCHRONIZE_CACHE 0x35 +#define SCSI_VERIFY 0x2F +#define SCSI_WRITE_10 0x2A +#define SCSI_WRITE_12 0xAA + +/* The sense codes */ +enum sbc_sense_key { + SBC_SENSE_KEY_NO_SENSE = 0x00, + SBC_SENSE_KEY_RECOVERED_ERROR = 0x01, + SBC_SENSE_KEY_NOT_READY = 0x02, + SBC_SENSE_KEY_MEDIUM_ERROR = 0x03, + SBC_SENSE_KEY_HARDWARE_ERROR = 0x04, + SBC_SENSE_KEY_ILLEGAL_REQUEST = 0x05, + SBC_SENSE_KEY_UNIT_ATTENTION = 0x06, + SBC_SENSE_KEY_DATA_PROTECT = 0x07, + SBC_SENSE_KEY_BLANK_CHECK = 0x08, + SBC_SENSE_KEY_VENDOR_SPECIFIC = 0x09, + SBC_SENSE_KEY_COPY_ABORTED = 0x0A, + SBC_SENSE_KEY_ABORTED_COMMAND = 0x0B, + SBC_SENSE_KEY_VOLUME_OVERFLOW = 0x0D, + SBC_SENSE_KEY_MISCOMPARE = 0x0E +}; + +enum sbc_asc { + SBC_ASC_NO_ADDITIONAL_SENSE_INFORMATION = 0x00, + SBC_ASC_PERIPHERAL_DEVICE_WRITE_FAULT = 0x03, + SBC_ASC_LOGICAL_UNIT_NOT_READY = 0x04, + SBC_ASC_UNRECOVERED_READ_ERROR = 0x11, + SBC_ASC_INVALID_COMMAND_OPERATION_CODE = 0x20, + SBC_ASC_LBA_OUT_OF_RANGE = 0x21, + SBC_ASC_INVALID_FIELD_IN_CDB = 0x24, + SBC_ASC_WRITE_PROTECTED = 0x27, + SBC_ASC_NOT_READY_TO_READY_CHANGE = 0x28, + SBC_ASC_FORMAT_ERROR = 0x31, + SBC_ASC_MEDIUM_NOT_PRESENT = 0x3A +}; + +enum sbc_ascq { + SBC_ASCQ_NA = 0x00, + SBC_ASCQ_FORMAT_COMMAND_FAILED = 0x01, + SBC_ASCQ_INITIALIZING_COMMAND_REQUIRED = 0x02, + SBC_ASCQ_OPERATION_IN_PROGRESS = 0x07 +}; + +enum trans_event { + EVENT_CBW_VALID, + EVENT_NEED_STATUS +}; + +struct usb_msc_cbw { + uint32_t dCBWSignature; + uint32_t dCBWTag; + uint32_t dCBWDataTransferLength; + uint8_t bmCBWFlags; + uint8_t bCBWLUN; + uint8_t bCBWCBLength; + uint8_t CBWCB[16]; +} __attribute__((packed)); + +struct usb_msc_csw { + uint32_t dCSWSignature; + uint32_t dCSWTag; + uint32_t dCSWDataResidue; + uint8_t bCSWStatus; +} __attribute__((packed)); + +struct sbc_sense_info { + uint8_t key; + uint8_t asc; + uint8_t ascq; +}; + +struct usb_msc_trans { + uint8_t cbw_cnt; /* Read until 31 bytes */ + union { + struct usb_msc_cbw cbw; + uint8_t buf[1]; + } cbw; + + uint32_t bytes_to_read; + uint32_t bytes_to_write; + uint32_t byte_count; /* Either read until equal to bytes_to_read or + write until equal to bytes_to_write. */ + uint32_t lba_start; + uint32_t block_count; + uint32_t current_block; + + uint8_t msd_buf[512]; + + bool csw_valid; + uint8_t csw_sent; /* Write until 13 bytes */ + union { + struct usb_msc_csw csw; + uint8_t buf[1]; + } csw; +}; + +struct _usbd_mass_storage { + usbd_device *usbd_dev; + uint8_t ep_in; + uint8_t ep_in_size; + uint8_t ep_out; + uint8_t ep_out_size; + + const char *vendor_id; + const char *product_id; + const char *product_revision_level; + uint32_t block_count; + + int (*read_block)(uint32_t lba, uint8_t *copy_to); + int (*write_block)(uint32_t lba, const uint8_t *copy_from); + + void (*lock)(void); + void (*unlock)(void); + + struct usb_msc_trans trans; + struct sbc_sense_info sense; +}; + +static usbd_mass_storage _mass_storage; + +/*-- SCSI Base Responses -----------------------------------------------------*/ + +static const uint8_t _spc3_inquiry_response[36] = { + 0x00, /* Byte 0: Peripheral Qualifier = 0, Peripheral Device Type = 0 */ + 0x80, /* Byte 1: RMB = 1, Reserved = 0 */ + 0x04, /* Byte 2: Version = 0 */ + 0x02, /* Byte 3: Obsolete = 0, NormACA = 0, HiSup = 0, Response Data Format = 2 */ + 0x20, /* Byte 4: Additional Length (n-4) = 31 + 4 */ + 0x00, /* Byte 5: SCCS = 0, ACC = 0, TPGS = 0, 3PC = 0, Reserved = 0, Protect = 0 */ + 0x00, /* Byte 6: BQue = 0, EncServ = 0, VS = 0, MultiP = 0, MChngr = 0, Obsolete = 0, Addr16 = 0 */ + 0x00, /* Byte 7: Obsolete = 0, Wbus16 = 0, Sync = 0, Linked = 0, CmdQue = 0, VS = 0 */ + /* Byte 8 - Byte 15: Vendor Identification */ + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + /* Byte 16 - Byte 31: Product Identification */ + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + /* Byte 32 - Byte 35: Product Revision Level */ + 0x20, 0x20, 0x20, 0x20 +}; + +static const uint8_t _spc3_request_sense[18] = { + 0x70, /* Byte 0: VALID = 0, Response Code = 112 */ + 0x00, /* Byte 1: Obsolete = 0 */ + 0x00, /* Byte 2: Filemark = 0, EOM = 0, ILI = 0, Reserved = 0, Sense Key = 0 */ + /* Byte 3 - Byte 6: Information = 0 */ + 0, 0, 0, 0, + 0x0a, /* Byte 7: Additional Sense Length = 10 */ + /* Byte 8 - Byte 11: Command Specific Info = 0 */ + 0, 0, 0, 0, + 0x00, /* Byte 12: Additional Sense Code (ASC) = 0 */ + 0x00, /* Byte 13: Additional Sense Code Qualifier (ASCQ) = 0 */ + 0x00, /* Byte 14: Field Replaceable Unit Code (FRUC) = 0 */ + 0x00, /* Byte 15: SKSV = 0, SenseKeySpecific[0] = 0 */ + 0x00, /* Byte 16: SenseKeySpecific[0] = 0 */ + 0x00 /* Byte 17: SenseKeySpecific[0] = 0 */ +}; + +/*-- SCSI Layer --------------------------------------------------------------*/ + +static void set_sbc_status(usbd_mass_storage *ms, + enum sbc_sense_key key, + enum sbc_asc asc, + enum sbc_ascq ascq) +{ + ms->sense.key = (uint8_t) key; + ms->sense.asc = (uint8_t) asc; + ms->sense.ascq = (uint8_t) ascq; +} + +static void set_sbc_status_good(usbd_mass_storage *ms) +{ + set_sbc_status(ms, + SBC_SENSE_KEY_NO_SENSE, + SBC_ASC_NO_ADDITIONAL_SENSE_INFORMATION, + SBC_ASCQ_NA); +} + +static uint8_t *get_cbw_buf(struct usb_msc_trans *trans) +{ + return &trans->cbw.cbw.CBWCB[0]; +} + +static void scsi_read_6(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + uint8_t *buf; + + buf = get_cbw_buf(trans); + + trans->lba_start = (buf[2] << 8) | buf[3]; + trans->block_count = buf[4]; + trans->current_block = 0; + + /* TODO: Check the lba & block_count for range. */ + + /* both are in terms of 512 byte blocks, so shift by 9 */ + trans->bytes_to_write = trans->block_count << 9; + + set_sbc_status_good(ms); + } +} + +static void scsi_write_6(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + (void) ms; + + if (EVENT_CBW_VALID == event) { + uint8_t *buf; + + buf = get_cbw_buf(trans); + + trans->lba_start = ((0x1f & buf[1]) << 16) | (buf[2] << 8) | buf[3]; + trans->block_count = buf[4]; + trans->current_block = 0; + + trans->bytes_to_read = trans->block_count << 9; + } +} + +static void scsi_write_10(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + (void) ms; + + if (EVENT_CBW_VALID == event) { + uint8_t *buf; + + buf = get_cbw_buf(trans); + + trans->lba_start = (buf[2] << 24) | (buf[3] << 16) | + (buf[4] << 8) | buf[5]; + trans->block_count = (buf[7] << 8) | buf[8]; + trans->current_block = 0; + + trans->bytes_to_read = trans->block_count << 9; + } +} + +static void scsi_read_10(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + uint8_t *buf; + + buf = get_cbw_buf(trans); + + trans->lba_start = (buf[2] << 24) | (buf[3] << 16) | (buf[4] << 8) | buf[5]; + trans->block_count = (buf[7] << 8) | buf[8]; + + /* TODO: Check the lba & block_count for range. */ + + /* both are in terms of 512 byte blocks, so shift by 9 */ + trans->bytes_to_write = trans->block_count << 9; + + set_sbc_status_good(ms); + } +} + +static void scsi_read_capacity(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + trans->msd_buf[0] = ms->block_count >> 24; + trans->msd_buf[1] = 0xff & (ms->block_count >> 16); + trans->msd_buf[2] = 0xff & (ms->block_count >> 8); + trans->msd_buf[3] = 0xff & ms->block_count; + + /* Block size: 512 */ + trans->msd_buf[4] = 0; + trans->msd_buf[5] = 0; + trans->msd_buf[6] = 2; + trans->msd_buf[7] = 0; + trans->bytes_to_write = 8; + set_sbc_status_good(ms); + } +} + +static void scsi_format_unit(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + uint32_t i; + + memset(trans->msd_buf, 0, 512); + + for (i = 0; i < ms->block_count; i++) { + (*ms->write_block)(i, trans->msd_buf); + } + + set_sbc_status_good(ms); + } +} + +static void scsi_request_sense(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + uint8_t *buf; + + buf = &trans->cbw.cbw.CBWCB[0]; + + trans->bytes_to_write = buf[4]; /* allocation length */ + memcpy(trans->msd_buf, _spc3_request_sense, sizeof(_spc3_request_sense)); + + trans->msd_buf[2] = ms->sense.key; + trans->msd_buf[12] = ms->sense.asc; + trans->msd_buf[13] = ms->sense.ascq; + } +} + +static void scsi_mode_sense_6(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + (void) ms; + + if (EVENT_CBW_VALID == event) { +#if 0 + uint8_t *buf; + uint8_t page_code; + uint8_t allocation_length; + + buf = &trans->cbw.cbw.CBWCB[0]; + page_code = buf[2]; + allocation_length = buf[4]; + + if (0x1C == page_code) { /* Informational Exceptions */ +#endif + trans->bytes_to_write = 4; + + trans->msd_buf[0] = 3; /* Num bytes that follow */ + trans->msd_buf[1] = 0; /* Medium Type */ + trans->msd_buf[2] = 0; /* Device specific param */ + trans->csw.csw.dCSWDataResidue = 4; +#if 0 + } else if (0x01 == page_code) { /* Error recovery */ + } else if (0x3F == page_code) { /* All */ + } else { + /* Error */ + trans->csw.csw.bCSWStatus = CSW_STATUS_FAILED; + set_sbc_status(ms, + SBC_SENSE_KEY_ILLEGAL_REQUEST, + SBC_ASC_INVALID_FIELD_IN_CDB, + SBC_ASCQ_NA); + } +#endif + } +} + +static void scsi_inquiry(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + uint8_t evpd; + uint8_t *buf; + + buf = get_cbw_buf(trans); + evpd = 1 & buf[1]; + + if (0 == evpd) { + size_t len; + trans->bytes_to_write = sizeof(_spc3_inquiry_response); + memcpy(trans->msd_buf, _spc3_inquiry_response, sizeof(_spc3_inquiry_response)); + + len = strlen(ms->vendor_id); + len = MIN(len, 8); + memcpy(&trans->msd_buf[8], ms->vendor_id, len); + + len = strlen(ms->product_id); + len = MIN(len, 16); + memcpy(&trans->msd_buf[16], ms->product_id, len); + + len = strlen(ms->product_revision_level); + len = MIN(len, 4); + memcpy(&trans->msd_buf[32], ms->product_revision_level, len); + + trans->csw.csw.dCSWDataResidue = sizeof(_spc3_inquiry_response); + + set_sbc_status_good(ms); + } else { + /* TODO: Add VPD 0x83 support */ + /* TODO: Add VPD 0x00 support */ + } + } +} + +static void scsi_command(usbd_mass_storage *ms, + struct usb_msc_trans *trans, + enum trans_event event) +{ + if (EVENT_CBW_VALID == event) { + /* Setup the default success */ + trans->csw_sent = 0; + trans->csw.csw.dCSWSignature = CSW_SIGNATURE; + trans->csw.csw.dCSWTag = trans->cbw.cbw.dCBWTag; + trans->csw.csw.dCSWDataResidue = 0; + trans->csw.csw.bCSWStatus = CSW_STATUS_SUCCESS; + + trans->bytes_to_write = 0; + trans->bytes_to_read = 0; + trans->byte_count = 0; + } + + switch (trans->cbw.cbw.CBWCB[0]) { + case SCSI_TEST_UNIT_READY: + case SCSI_SEND_DIAGNOSTIC: + /* Do nothing, just send the success. */ + set_sbc_status_good(ms); + break; + case SCSI_FORMAT_UNIT: + scsi_format_unit(ms, trans, event); + break; + case SCSI_REQUEST_SENSE: + scsi_request_sense(ms, trans, event); + break; + case SCSI_MODE_SENSE_6: + scsi_mode_sense_6(ms, trans, event); + break; + case SCSI_READ_6: + scsi_read_6(ms, trans, event); + break; + case SCSI_INQUIRY: + scsi_inquiry(ms, trans, event); + break; + case SCSI_READ_CAPACITY: + scsi_read_capacity(ms, trans, event); + break; + case SCSI_READ_10: + scsi_read_10(ms, trans, event); + break; + case SCSI_WRITE_6: + scsi_write_6(ms, trans, event); + break; + case SCSI_WRITE_10: + scsi_write_10(ms, trans, event); + break; + default: + set_sbc_status(ms, SBC_SENSE_KEY_ILLEGAL_REQUEST, + SBC_ASC_INVALID_COMMAND_OPERATION_CODE, + SBC_ASCQ_NA); + + trans->bytes_to_write = 0; + trans->bytes_to_read = 0; + trans->csw.csw.bCSWStatus = CSW_STATUS_FAILED; + break; + } +} + +/*-- USB Mass Storage Layer --------------------------------------------------*/ + +/** @brief Handle the USB 'OUT' requests. */ +static void msc_data_rx_cb(usbd_device *usbd_dev, uint8_t ep) +{ + usbd_mass_storage *ms; + struct usb_msc_trans *trans; + int len, max_len, left; + void *p; + + ms = &_mass_storage; + trans = &ms->trans; + + /* RX only */ + left = sizeof(struct usb_msc_cbw) - trans->cbw_cnt; + if (0 < left) { + max_len = MIN(ms->ep_out_size, left); + p = &trans->cbw.buf[0x1ff & trans->cbw_cnt]; + len = usbd_ep_read_packet(usbd_dev, ep, p, max_len); + trans->cbw_cnt += len; + + if (sizeof(struct usb_msc_cbw) == trans->cbw_cnt) { + scsi_command(ms, trans, EVENT_CBW_VALID); + if (trans->byte_count < trans->bytes_to_read) { + /* We must wait until there is something to + * read again. */ + return; + } + } + } + + if (trans->byte_count < trans->bytes_to_read) { + if (0 < trans->block_count) { + if ((0 == trans->byte_count) && (NULL != ms->lock)){ + (*ms->lock)(); + } + } + + left = trans->bytes_to_read - trans->byte_count; + max_len = MIN(ms->ep_out_size, left); + p = &trans->msd_buf[0x1ff & trans->byte_count]; + len = usbd_ep_read_packet(usbd_dev, ep, p, max_len); + trans->byte_count += len; + + if (0 < trans->block_count) { + if (0 == (0x1ff & trans->byte_count)) { + uint32_t lba; + + lba = trans->lba_start + trans->current_block; + if (0 != (*ms->write_block)(lba, trans->msd_buf)) { + /* Error */ + } + trans->current_block++; + } + } + } else if (trans->byte_count < trans->bytes_to_write) { + if (0 < trans->block_count) { + if ((0 == trans->byte_count) && (NULL != ms->lock)) { + (*ms->lock)(); + } + + if (0 == (0x1ff & trans->byte_count)) { + uint32_t lba; + + lba = trans->lba_start + trans->current_block; + if (0 != (*ms->read_block)(lba, trans->msd_buf)) { + /* Error */ + } + trans->current_block++; + } + } + + left = trans->bytes_to_write - trans->byte_count; + max_len = MIN(ms->ep_out_size, left); + p = &trans->msd_buf[0x1ff & trans->byte_count]; + len = usbd_ep_write_packet(usbd_dev, ms->ep_in, p, max_len); + trans->byte_count += len; + } else { + if (0 < trans->block_count) { + if (trans->current_block == trans->block_count) { + uint32_t lba; + + lba = trans->lba_start + trans->current_block; + if (0 != (*ms->write_block)(lba, trans->msd_buf)) { + /* Error */ + } + + trans->current_block = 0; + if (NULL != ms->unlock){ + (*ms->unlock)(); + } + } + } + if (false == trans->csw_valid) { + scsi_command(ms, trans, EVENT_NEED_STATUS); + trans->csw_valid = true; + } + + left = sizeof(struct usb_msc_csw) - trans->csw_sent; + if (0 < left) { + max_len = MIN(ms->ep_out_size, left); + p = &trans->csw.buf[trans->csw_sent]; + len = usbd_ep_write_packet(usbd_dev, ms->ep_in, p, max_len); + trans->csw_sent += len; + } + } +} + +/** @brief Handle the USB 'IN' requests. */ +static void msc_data_tx_cb(usbd_device *usbd_dev, uint8_t ep) +{ + usbd_mass_storage *ms; + struct usb_msc_trans *trans; + int len, max_len, left; + void *p; + + ms = &_mass_storage; + trans = &ms->trans; + + if (trans->byte_count < trans->bytes_to_write) { + if (0 < trans->block_count) { + if (0 == (0x1ff & trans->byte_count)) { + uint32_t lba; + + lba = trans->lba_start + trans->current_block; + if (0 != (*ms->read_block)(lba, trans->msd_buf)) { + /* Error */ + } + trans->current_block++; + } + } + + left = trans->bytes_to_write - trans->byte_count; + max_len = MIN(ms->ep_out_size, left); + p = &trans->msd_buf[0x1ff & trans->byte_count]; + len = usbd_ep_write_packet(usbd_dev, ep, p, max_len); + trans->byte_count += len; + } else { + if (0 < trans->block_count) { + if (trans->current_block == trans->block_count) { + trans->current_block = 0; + if (NULL != ms->unlock){ + (*ms->unlock)(); + } + } + } + if (false == trans->csw_valid) { + scsi_command(ms, trans, EVENT_NEED_STATUS); + trans->csw_valid = true; + } + + left = sizeof(struct usb_msc_csw) - trans->csw_sent; + if (0 < left) { + max_len = MIN(ms->ep_out_size, left); + p = &trans->csw.buf[trans->csw_sent]; + len = usbd_ep_write_packet(usbd_dev, ep, p, max_len); + trans->csw_sent += len; + } else if (sizeof(struct usb_msc_csw) == trans->csw_sent) { + /* End of transaction */ + trans->lba_start = 0xffffffff; + trans->block_count = 0; + trans->current_block = 0; + trans->cbw_cnt = 0; + trans->bytes_to_read = 0; + trans->bytes_to_write = 0; + trans->byte_count = 0; + trans->csw_sent = 0; + trans->csw_valid = false; + } + } +} + +/** @brief Handle various control requests related to the msc storage + * interface. + */ +static int msc_control_request(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, uint16_t *len, + void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req)) +{ + (void)complete; + (void)usbd_dev; + + switch (req->bRequest) { + case USB_MSC_REQ_BULK_ONLY_RESET: + /* Do any special reset code here. */ + return USBD_REQ_HANDLED; + case USB_MSC_REQ_GET_MAX_LUN: + /* Return the number of LUNs. We use 0. */ + *buf[0] = 0; + *len = 1; + return USBD_REQ_HANDLED; + } + + return USBD_REQ_NOTSUPP; +} + +/** @brief Setup the endpoints to be bulk & register the callbacks. */ +static void msc_set_config(usbd_device *usbd_dev, uint16_t wValue) +{ + usbd_mass_storage *ms = &_mass_storage; + + (void)wValue; + + usbd_ep_setup(usbd_dev, ms->ep_in, USB_ENDPOINT_ATTR_BULK, + ms->ep_in_size, msc_data_tx_cb); + usbd_ep_setup(usbd_dev, ms->ep_out, USB_ENDPOINT_ATTR_BULK, + ms->ep_out_size, msc_data_rx_cb); + + usbd_register_control_callback( + usbd_dev, + USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE, + USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, + msc_control_request); +} + +/** @addtogroup usb_msc */ +/** @{ */ + +/** @brief Initializes the USB Mass Storage subsystem. + +@note Currently you can only have this profile active. + +@param[in] usbd_dev The USB device to associate the Mass Storage with. +@param[in] ep_in The USB 'IN' endpoint. +@param[in] ep_in_size The maximum endpoint size. Valid values: 8, 16, 32 or 64 +@param[in] ep_out The USB 'OUT' endpoint. +@param[in] ep_out_size The maximum endpoint size. Valid values: 8, 16, 32 or 64 +@param[in] vendor_id The SCSI vendor ID to return. Maximum used length is 8. +@param[in] product_id The SCSI product ID to return. Maximum used length is 16. +@param[in] product_revision_level The SCSI product revision level to return. + Maximum used length is 4. +@param[in] block_count The number of 512-byte blocks available. +@param[in] read_block The function called when the host requests to read a LBA + block. Must _NOT_ be NULL. +@param[in] write_block The function called when the host requests to write a + LBA block. Must _NOT_ be NULL. + +@return Pointer to the usbd_mass_storage struct. +*/ +usbd_mass_storage *usb_msc_init(usbd_device *usbd_dev, + uint8_t ep_in, uint8_t ep_in_size, + uint8_t ep_out, uint8_t ep_out_size, + const char *vendor_id, + const char *product_id, + const char *product_revision_level, + const uint32_t block_count, + int (*read_block)(uint32_t lba, uint8_t *copy_to), + int (*write_block)(uint32_t lba, const uint8_t *copy_from)) +{ + _mass_storage.usbd_dev = usbd_dev; + _mass_storage.ep_in = ep_in; + _mass_storage.ep_in_size = ep_in_size; + _mass_storage.ep_out = ep_out; + _mass_storage.ep_out_size = ep_out_size; + _mass_storage.vendor_id = vendor_id; + _mass_storage.product_id = product_id; + _mass_storage.product_revision_level = product_revision_level; + _mass_storage.block_count = block_count - 1; + _mass_storage.read_block = read_block; + _mass_storage.write_block = write_block; + _mass_storage.lock = NULL; + _mass_storage.unlock = NULL; + + _mass_storage.trans.lba_start = 0xffffffff; + _mass_storage.trans.block_count = 0; + _mass_storage.trans.current_block = 0; + _mass_storage.trans.cbw_cnt = 0; + _mass_storage.trans.bytes_to_read = 0; + _mass_storage.trans.bytes_to_write = 0; + _mass_storage.trans.byte_count = 0; + _mass_storage.trans.csw_valid = false; + _mass_storage.trans.csw_sent = 0; + + set_sbc_status_good(&_mass_storage); + + usbd_register_set_config_callback(usbd_dev, msc_set_config); + + return &_mass_storage; +} + +/** @} */ diff --git a/libopencm3/lib/usb/usb_private.h b/libopencm3/lib/usb/usb_private.h new file mode 100644 index 0000000..81466df --- /dev/null +++ b/libopencm3/lib/usb/usb_private.h @@ -0,0 +1,163 @@ +/** @defgroup usb_private_defines USB Private Structures + +@brief Defined Constants and Types for the USB Private Structures + +@ingroup USB_defines + +@version 1.0.0 + +@author @htmlonly © @endhtmlonly 2010 +Gareth McMullin + +@date 10 March 2013 + +LGPL License Terms @ref lgpl_license +*/ + +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2010 Gareth McMullin + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/**@{*/ + +#ifndef __USB_PRIVATE_H +#define __USB_PRIVATE_H + +#define MAX_USER_CONTROL_CALLBACK 4 +#define MAX_USER_SET_CONFIG_CALLBACK 4 + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +/** Internal collection of device information. */ +struct _usbd_device { + const struct usb_device_descriptor *desc; + const struct usb_config_descriptor *config; + const char **strings; + int num_strings; + + uint8_t *ctrl_buf; /**< Internal buffer used for control transfers */ + uint16_t ctrl_buf_len; + + uint8_t current_address; + uint8_t current_config; + + uint16_t pm_top; /**< Top of allocated endpoint buffer memory */ + + /* User callback functions for various USB events */ + void (*user_callback_reset)(void); + void (*user_callback_suspend)(void); + void (*user_callback_resume)(void); + void (*user_callback_sof)(void); + + struct usb_control_state { + enum { + IDLE, STALLED, + DATA_IN, LAST_DATA_IN, STATUS_IN, + DATA_OUT, LAST_DATA_OUT, STATUS_OUT, + } state; + struct usb_setup_data req __attribute__((aligned(4))); + uint8_t *ctrl_buf; + uint16_t ctrl_len; + void (*complete)(usbd_device *usbd_dev, + struct usb_setup_data *req); + } control_state; + + struct user_control_callback { + usbd_control_callback cb; + uint8_t type; + uint8_t type_mask; + } user_control_callback[MAX_USER_CONTROL_CALLBACK]; + + void (*user_callback_ctr[8][3])(usbd_device *usbd_dev, uint8_t ea); + + /* User callback function for some standard USB function hooks */ + void (*user_callback_set_config[MAX_USER_SET_CONFIG_CALLBACK]) + (usbd_device *usbd_dev, uint16_t wValue); + + const struct _usbd_driver *driver; + + /* private driver data */ + + uint16_t fifo_mem_top; + uint16_t fifo_mem_top_ep0; + uint8_t force_nak[4]; + /* + * We keep a backup copy of the out endpoint size registers to restore + * them after a transaction. + */ + uint32_t doeptsiz[4]; + /* + * Received packet size for each endpoint. This is assigned in + * stm32f107_poll() which reads the packet status push register GRXSTSP + * for use in stm32f107_ep_read_packet(). + */ + uint16_t rxbcnt; +}; + +enum _usbd_transaction { + USB_TRANSACTION_IN, + USB_TRANSACTION_OUT, + USB_TRANSACTION_SETUP, +}; + +/* Do not appear to belong to the API, so are omitted from docs */ +/**@}*/ + +void _usbd_control_in(usbd_device *usbd_dev, uint8_t ea); +void _usbd_control_out(usbd_device *usbd_dev, uint8_t ea); +void _usbd_control_setup(usbd_device *usbd_dev, uint8_t ea); + +int _usbd_standard_request_device(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len); +int _usbd_standard_request_interface(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len); +int _usbd_standard_request_endpoint(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len); +int _usbd_standard_request(usbd_device *usbd_dev, struct usb_setup_data *req, + uint8_t **buf, uint16_t *len); + +void _usbd_reset(usbd_device *usbd_dev); + +/* Functions provided by the hardware abstraction. */ +struct _usbd_driver { + usbd_device *(*init)(void); + void (*set_address)(usbd_device *usbd_dev, uint8_t addr); + void (*ep_setup)(usbd_device *usbd_dev, uint8_t addr, uint8_t type, + uint16_t max_size, + void (*cb)(usbd_device *usbd_dev, uint8_t ep)); + void (*ep_reset)(usbd_device *usbd_dev); + void (*ep_stall_set)(usbd_device *usbd_dev, uint8_t addr, + uint8_t stall); + void (*ep_nak_set)(usbd_device *usbd_dev, uint8_t addr, uint8_t nak); + uint8_t (*ep_stall_get)(usbd_device *usbd_dev, uint8_t addr); + uint16_t (*ep_write_packet)(usbd_device *usbd_dev, uint8_t addr, + const void *buf, uint16_t len); + uint16_t (*ep_read_packet)(usbd_device *usbd_dev, uint8_t addr, + void *buf, uint16_t len); + void (*poll)(usbd_device *usbd_dev); + void (*disconnect)(usbd_device *usbd_dev, bool disconnected); + uint32_t base_address; + bool set_address_before_status; + uint16_t rx_fifo_size; +}; + +#endif + diff --git a/libopencm3/lib/usb/usb_standard.c b/libopencm3/lib/usb/usb_standard.c new file mode 100644 index 0000000..3b0dac9 --- /dev/null +++ b/libopencm3/lib/usb/usb_standard.c @@ -0,0 +1,532 @@ +/** @defgroup usb_standard_file Generic USB Standard Request Interface + +@ingroup USB + +@brief Generic USB Standard Request Interface + +@version 1.0.0 + +@author @htmlonly © @endhtmlonly 2010 +Gareth McMullin + +@date 10 March 2013 + +LGPL License Terms @ref lgpl_license +*/ + +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2010 Gareth McMullin + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/**@{*/ + +#include +#include +#include "usb_private.h" + +int usbd_register_set_config_callback(usbd_device *usbd_dev, + void (*callback)(usbd_device *usbd_dev, + uint16_t wValue)) +{ + int i; + + for (i = 0; i < MAX_USER_SET_CONFIG_CALLBACK; i++) { + if (usbd_dev->user_callback_set_config[i]) + continue; + + usbd_dev->user_callback_set_config[i] = callback; + return 0; + } + + return -1; +} + +static uint16_t build_config_descriptor(usbd_device *usbd_dev, + uint8_t index, uint8_t *buf, uint16_t len) +{ + uint8_t *tmpbuf = buf; + const struct usb_config_descriptor *cfg = &usbd_dev->config[index]; + uint16_t count, total = 0, totallen = 0; + uint16_t i, j, k; + + memcpy(buf, cfg, count = MIN(len, cfg->bLength)); + buf += count; + len -= count; + total += count; + totallen += cfg->bLength; + + /* For each interface... */ + for (i = 0; i < cfg->bNumInterfaces; i++) { + /* Interface Association Descriptor, if any */ + if (cfg->interface[i].iface_assoc) { + const struct usb_iface_assoc_descriptor *assoc = + cfg->interface[i].iface_assoc; + memcpy(buf, assoc, count = MIN(len, assoc->bLength)); + buf += count; + len -= count; + total += count; + totallen += assoc->bLength; + } + /* For each alternate setting... */ + for (j = 0; j < cfg->interface[i].num_altsetting; j++) { + const struct usb_interface_descriptor *iface = + &cfg->interface[i].altsetting[j]; + /* Copy interface descriptor. */ + memcpy(buf, iface, count = MIN(len, iface->bLength)); + buf += count; + len -= count; + total += count; + totallen += iface->bLength; + /* Copy extra bytes (function descriptors). */ + memcpy(buf, iface->extra, + count = MIN(len, iface->extralen)); + buf += count; + len -= count; + total += count; + totallen += iface->extralen; + /* For each endpoint... */ + for (k = 0; k < iface->bNumEndpoints; k++) { + const struct usb_endpoint_descriptor *ep = + &iface->endpoint[k]; + memcpy(buf, ep, count = MIN(len, ep->bLength)); + buf += count; + len -= count; + total += count; + totallen += ep->bLength; + } + } + } + + /* Fill in wTotalLength. */ + *(uint16_t *)(tmpbuf + 2) = totallen; + + return total; +} + +static int usb_descriptor_type(uint16_t wValue) +{ + return wValue >> 8; +} + +static int usb_descriptor_index(uint16_t wValue) +{ + return wValue & 0xFF; +} + +static int usb_standard_get_descriptor(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + int i, array_idx, descr_idx; + struct usb_string_descriptor *sd; + + descr_idx = usb_descriptor_index(req->wValue); + + switch (usb_descriptor_type(req->wValue)) { + case USB_DT_DEVICE: + *buf = (uint8_t *) usbd_dev->desc; + *len = MIN(*len, usbd_dev->desc->bLength); + return USBD_REQ_HANDLED; + case USB_DT_CONFIGURATION: + *buf = usbd_dev->ctrl_buf; + *len = build_config_descriptor(usbd_dev, descr_idx, *buf, *len); + return USBD_REQ_HANDLED; + case USB_DT_STRING: + sd = (struct usb_string_descriptor *)usbd_dev->ctrl_buf; + + if (descr_idx == 0) { + /* Send sane Language ID descriptor... */ + sd->wData[0] = USB_LANGID_ENGLISH_US; + sd->bLength = sizeof(sd->bLength) + + sizeof(sd->bDescriptorType) + + sizeof(sd->wData[0]); + + *len = MIN(*len, sd->bLength); + } else { + array_idx = descr_idx - 1; + + if (!usbd_dev->strings) { + /* Device doesn't support strings. */ + return USBD_REQ_NOTSUPP; + } + + /* Check that string index is in range. */ + if (array_idx >= usbd_dev->num_strings) { + return USBD_REQ_NOTSUPP; + } + + /* Strings with Language ID differnet from + * USB_LANGID_ENGLISH_US are not supported */ + if (req->wIndex != USB_LANGID_ENGLISH_US) { + return USBD_REQ_NOTSUPP; + } + + /* Ths string is returned as UTF16, hence the + * multiplication + */ + sd->bLength = strlen(usbd_dev->strings[array_idx]) * 2 + + sizeof(sd->bLength) + + sizeof(sd->bDescriptorType); + + *len = MIN(*len, sd->bLength); + + for (i = 0; i < (*len / 2) - 1; i++) { + sd->wData[i] = + usbd_dev->strings[array_idx][i]; + } + } + + sd->bDescriptorType = USB_DT_STRING; + *buf = (uint8_t *)sd; + + return USBD_REQ_HANDLED; + } + return USBD_REQ_NOTSUPP; +} + +static int usb_standard_set_address(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len) +{ + (void)req; + (void)buf; + (void)len; + + /* The actual address is only latched at the STATUS IN stage. */ + if ((req->bmRequestType != 0) || (req->wValue >= 128)) { + return 0; + } + + usbd_dev->current_address = req->wValue; + + /* + * Special workaround for STM32F10[57] that require the address + * to be set here. This is undocumented! + */ + if (usbd_dev->driver->set_address_before_status) { + usbd_dev->driver->set_address(usbd_dev, req->wValue); + } + + return 1; +} + +static int usb_standard_set_configuration(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + int i; + + (void)req; + (void)buf; + (void)len; + + /* Is this correct, or should we reset alternate settings. */ + if (req->wValue == usbd_dev->current_config) { + return 1; + } + + usbd_dev->current_config = req->wValue; + + /* Reset all endpoints. */ + usbd_dev->driver->ep_reset(usbd_dev); + + if (usbd_dev->user_callback_set_config) { + /* + * Flush control callbacks. These will be reregistered + * by the user handler. + */ + for (i = 0; i < MAX_USER_CONTROL_CALLBACK; i++) { + usbd_dev->user_control_callback[i].cb = NULL; + } + + for (i = 0; i < MAX_USER_SET_CONFIG_CALLBACK; i++) { + if (usbd_dev->user_callback_set_config[i]) { + usbd_dev->user_callback_set_config[i](usbd_dev, + req->wValue); + } + } + } + + return 1; +} + +static int usb_standard_get_configuration(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)req; + + if (*len > 1) { + *len = 1; + } + (*buf)[0] = usbd_dev->current_config; + + return 1; +} + +static int usb_standard_set_interface(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)usbd_dev; + (void)req; + (void)buf; + + /* FIXME: Adapt if we have more than one interface. */ + if (req->wValue != 0) { + return 0; + } + *len = 0; + + return 1; +} + +static int usb_standard_get_interface(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)usbd_dev; + (void)req; + (void)buf; + + /* FIXME: Adapt if we have more than one interface. */ + *len = 1; + (*buf)[0] = 0; + + return 1; +} + +static int usb_standard_device_get_status(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)usbd_dev; + (void)req; + + /* bit 0: self powered */ + /* bit 1: remote wakeup */ + if (*len > 2) { + *len = 2; + } + (*buf)[0] = 0; + (*buf)[1] = 0; + + return 1; +} + +static int usb_standard_interface_get_status(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)usbd_dev; + (void)req; + /* not defined */ + + if (*len > 2) { + *len = 2; + } + (*buf)[0] = 0; + (*buf)[1] = 0; + + return 1; +} + +static int usb_standard_endpoint_get_status(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)req; + + if (*len > 2) { + *len = 2; + } + (*buf)[0] = usbd_ep_stall_get(usbd_dev, req->wIndex) ? 1 : 0; + (*buf)[1] = 0; + + return 1; +} + +static int usb_standard_endpoint_stall(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)buf; + (void)len; + + usbd_ep_stall_set(usbd_dev, req->wIndex, 1); + + return 1; +} + +static int usb_standard_endpoint_unstall(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)buf; + (void)len; + + usbd_ep_stall_set(usbd_dev, req->wIndex, 0); + + return 1; +} + +/* Do not appear to belong to the API, so are omitted from docs */ +/**@}*/ + +int _usbd_standard_request_device(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len) +{ + int (*command)(usbd_device *usbd_dev, struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) = NULL; + + switch (req->bRequest) { + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + if (req->wValue == USB_FEAT_DEVICE_REMOTE_WAKEUP) { + /* Device wakeup code goes here. */ + } + + if (req->wValue == USB_FEAT_TEST_MODE) { + /* Test mode code goes here. */ + } + + break; + case USB_REQ_SET_ADDRESS: + /* + * SET ADDRESS is an exception. + * It is only processed at STATUS stage. + */ + command = usb_standard_set_address; + break; + case USB_REQ_SET_CONFIGURATION: + command = usb_standard_set_configuration; + break; + case USB_REQ_GET_CONFIGURATION: + command = usb_standard_get_configuration; + break; + case USB_REQ_GET_DESCRIPTOR: + command = usb_standard_get_descriptor; + break; + case USB_REQ_GET_STATUS: + /* + * GET_STATUS always responds with zero reply. + * The application may override this behaviour. + */ + command = usb_standard_device_get_status; + break; + case USB_REQ_SET_DESCRIPTOR: + /* SET_DESCRIPTOR is optional and not implemented. */ + break; + } + + if (!command) { + return 0; + } + + return command(usbd_dev, req, buf, len); +} + +int _usbd_standard_request_interface(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len) +{ + int (*command)(usbd_device *usbd_dev, struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) = NULL; + + switch (req->bRequest) { + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* not defined */ + break; + case USB_REQ_GET_INTERFACE: + command = usb_standard_get_interface; + break; + case USB_REQ_SET_INTERFACE: + command = usb_standard_set_interface; + break; + case USB_REQ_GET_STATUS: + command = usb_standard_interface_get_status; + break; + } + + if (!command) { + return 0; + } + + return command(usbd_dev, req, buf, len); +} + +int _usbd_standard_request_endpoint(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len) +{ + int (*command) (usbd_device *usbd_dev, struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) = NULL; + + switch (req->bRequest) { + case USB_REQ_CLEAR_FEATURE: + if (req->wValue == USB_FEAT_ENDPOINT_HALT) { + command = usb_standard_endpoint_unstall; + } + break; + case USB_REQ_SET_FEATURE: + if (req->wValue == USB_FEAT_ENDPOINT_HALT) { + command = usb_standard_endpoint_stall; + } + break; + case USB_REQ_GET_STATUS: + command = usb_standard_endpoint_get_status; + break; + case USB_REQ_SET_SYNCH_FRAME: + /* FIXME: SYNCH_FRAME is not implemented. */ + /* + * SYNCH_FRAME is used for synchronization of isochronous + * endpoints which are not yet implemented. + */ + break; + } + + if (!command) { + return 0; + } + + return command(usbd_dev, req, buf, len); +} + +int _usbd_standard_request(usbd_device *usbd_dev, struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + /* FIXME: Have class/vendor requests as well. */ + if ((req->bmRequestType & USB_REQ_TYPE_TYPE) != USB_REQ_TYPE_STANDARD) { + return 0; + } + + switch (req->bmRequestType & USB_REQ_TYPE_RECIPIENT) { + case USB_REQ_TYPE_DEVICE: + return _usbd_standard_request_device(usbd_dev, req, buf, len); + case USB_REQ_TYPE_INTERFACE: + return _usbd_standard_request_interface(usbd_dev, req, + buf, len); + case USB_REQ_TYPE_ENDPOINT: + return _usbd_standard_request_endpoint(usbd_dev, req, buf, len); + default: + return 0; + } +} + -- cgit v1.2.3