diff --git a/hal/mbed_usb_phy.cpp b/hal/mbed_usb_phy.cpp new file mode 100644 index 0000000000..c1f589c559 --- /dev/null +++ b/hal/mbed_usb_phy.cpp @@ -0,0 +1,29 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "usb_phy_api.h" +#include "mbed_error.h" + +#if !defined(DEVICE_USBDEVICE) || !DEVICE_USBDEVICE + +USBPhy *get_usb_phy() +{ + error("This board does not have a hardware USB driver"); + return NULL; +} + +#endif diff --git a/hal/usb_phy_api.h b/hal/usb_phy_api.h new file mode 100644 index 0000000000..a46346ae31 --- /dev/null +++ b/hal/usb_phy_api.h @@ -0,0 +1,36 @@ + +/** \addtogroup hal */ +/** @{*/ +/* mbed Microcontroller Library + * Copyright (c) 2018-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MBED_USB_PHY_API_H +#define MBED_USB_PHY_API_H + +#include "USBPhy.h" + +/** Return a the USBPhy instance for this hardware + * + * For details on adding support for a USBPhy see the specification in USBPhy.h. + * + * @return A pointer to a USBPhy instance + * @note Calling this function on platforms without a USBPhy will result in an + * error + */ +USBPhy *get_usb_phy(); + +#endif + +/** @}*/ diff --git a/platform/USBPhy.h b/platform/USBPhy.h new file mode 100644 index 0000000000..33f5870e1a --- /dev/null +++ b/platform/USBPhy.h @@ -0,0 +1,302 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef USBPHY_H +#define USBPHY_H + +#include "USBPhyTypes.h" +#include "USBPhyEvents.h" + +/** Abstract interface to physical USB hardware + * + * # Defined behavior + * * Any endpoint configurations which fit in the parameters of the table returned + * by USBPhy::endpoint_table can be used. + * * All endpoints in any valid endpoint configuration can be used concurrently + * * Device supports use of at least one control, bulk, interrupt and + * isochronous in each direction at the same time - at least 8 endpoints. + * * Device supports all standard endpoint sizes (wMaxPacketSize) + * * Device can handle an interrupt latency of at least 100ms if reset is not being performed and address is not being set + * * USBPhyEvents events are only sent when USBPhy is in the initialized state + * * When unpowered only the USBPhyEvents::power event can be sent + * * On USB reset all endpoints are removed except for endpoint 0 + * * USBPhyEvents::out and USBPhyEvents::in events only occur for endpoints which have been added + * * A call to USBPhy::ep0_write results in USBPhyEvents::in getting called if not + * interrupted by a power loss or reset + * * A call to endpoint_read followed by endpoint_read_result results in USBPhyEvents::out getting called if not + * interrupted by a power loss or reset + * * Endpoint 0 naks all transactions aside from setup packets until one + * of ep0_read, ep0_write or ep0_stall has been called + * * Endpoint 0 stall is automatically cleared on reception of a setup packet + * + * # Undefined behavior + * * Calling USBPhy::endpoint_add or USBPhy::endpoint_remove outside of the control requests SetInterface or SetConfiguration + * * Devices behavior is undefined if latency is greater than 2ms when address is being set - see USB spec 9.2.6.3 + * * Devices behavior is undefined if latency is greater than 10ms when a reset occurs - see USB spec 7.1.7.5 + * * Calling any of the USBPhy::endpoint_* functions on endpoint 0 + * + * # Notes + * * Make sure USB packets are processed in the correct order when multiple packets are present. + * Typically IN endpoints should be handled before OUT endpoints if both are pending. + * * Setup packets may be resent if there is noise on the USB line. The USBPhy should be able + * to gracefully handle this scenario and respond to the setup packet with an ACK. + * * Bi-directional protocols making use of alternating IN and OUT phases should not rely + * on the last ACK an IN transfer to indicate that the OUT phase should start. Instead, + * the OUT phase should be started at the same time the last IN transfer is started. This + * is because the ACK to the last in transfer may be dropped if there is noise on the USB + * line. If dropped it will only get re-sent on the next IN phase. More info on this can be + * found in section 8.5.3.3 of the USB spec. + * + * @ingroup usb_device_core + */ +class USBPhy { +public: + USBPhy() {}; + virtual ~USBPhy() {}; + + /** + * Initialize this USBPhy instance + * + * This function must be called before calling + * any other functions of this class, unless specifically + * noted. + * + * @param events Callback class to handle USB events + */ + virtual void init(USBPhyEvents *events) = 0; + + /** + * Power down this USBPhy instance + * + * Disable interrupts and stop sending events. + */ + virtual void deinit() = 0; + + /** + * Check if USB power is present + * + * Devices which don't support checking the USB power state + * must always return true. + * + * @return true if USB power is present, false otherwise + */ + virtual bool powered() = 0; + + /** + * Make the USB phy visible to the USB host + * + * Enable either the D+ or D- pullup so the host can detect + * the presence of this device. + */ + virtual void connect() = 0; + + /** + * Detach the USB phy + * + * Disable the D+ and D- pullup and stop responding to + * USB traffic. + */ + virtual void disconnect() = 0; + + /** + * Set this device to the configured state + * + * Enable added endpoints if they are not enabled + * already. + */ + virtual void configure() = 0; + + /** + * Leave the configured state + * + * This is a notification to the USBPhy indicating that the device + * is leaving the configured state. The USBPhy can disable all + * endpoints other than endpoint 0. + * + */ + virtual void unconfigure() = 0; + + /** + * Enable the start of frame interrupt + * + * Call USBPhyEvents::sof on every frame. + */ + virtual void sof_enable() = 0; + + /** + * Disable the start of frame interrupt + * + * Stop calling USBPhyEvents::sof. + */ + virtual void sof_disable() = 0; + + /** + * Set the USBPhy's address + * + * @param address This device's USB address + */ + virtual void set_address(uint8_t address) = 0; + + /** + * Wake upstream devices + */ + virtual void remote_wakeup() = 0; + + /** + * Get the endpoint table + * + * This function returns a table which describes the endpoints + * can be used, the functionality of those endpoints and the + * resource cost. + */ + virtual const usb_ep_table_t* endpoint_table() = 0; + + /** + * Set wMaxPacketSize of endpoint 0 + * + * @param max_packet The wMaxPacketSize value for endpoint 0 + * @return The actual size of endpoint 0 + */ + virtual uint32_t ep0_set_max_packet(uint32_t max_packet) = 0; + + /** + * Read the contents of the SETUP packet + * + * @param buffer Buffer to fill with data + * @param size Size of buffer passed in + */ + virtual void ep0_setup_read_result(uint8_t *buffer, uint32_t size) = 0; + + /** + * Start receiving a packet of up to wMaxPacketSize on endpoint 0 + */ + virtual void ep0_read() = 0; + + /** + * Read the contents of a received packet + * + * @param buffer Buffer to fill with the data read + * @param size Size of buffer + */ + virtual uint32_t ep0_read_result(uint8_t *buffer, uint32_t size) = 0; + + /** + * Write a packet on endpoint 0 + * + * @param buffer Buffer fill with data to send + * @param size Size of data to send + */ + virtual void ep0_write(uint8_t *buffer, uint32_t size) = 0; + + /** + * Protocol stall on endpoint 0 + * + * Stall all IN and OUT packets on endpoint 0 until a setup packet + * is received. + * @note The stall is cleared automatically when a setup packet is received + */ + virtual void ep0_stall() = 0; + + /** + * Configure and enable an endpoint + * + * @param endpoint Endpoint to configure and enable + * @param max_packet The maximum packet size that can be sent or received + * @param type The type of endpoint this should be configured as - + * USB_EP_TYPE_BULK, USB_EP_TYPE_INT or USB_EP_TYPE_ISO + * @note This function cannot be used to configure endpoint 0. That must be done + * with ep0_set_max_packet + */ + virtual bool endpoint_add(usb_ep_t endpoint, uint32_t max_packet, usb_ep_type_t type) = 0; + + /** + * Disable an endpoint + * + * @param endpoint Endpoint to disable + */ + virtual void endpoint_remove(usb_ep_t endpoint) = 0; + + /** + * Perform a functional stall on the given endpoint + * + * Set the HALT feature for this endpoint so that all further + * communication is aborted. + * + * @param endpoint Endpoint to stall + */ + virtual void endpoint_stall(usb_ep_t endpoint) = 0; + + /** + * Unstall the endpoint + * + * Clear the HALT feature on this endpoint so communication can + * resume. + * + * @param endpoint Endpoint to stall + */ + virtual void endpoint_unstall(usb_ep_t endpoint) = 0; + + /** + * Start a read on the given endpoint + * + * @param endpoint Endpoint to start the read on + * @param max_packet A hint as to the wMaxPacketSize of this endpoint. + * This must match the size in endpoint_add. + * @return true if the read was successfully started, false otherwise + */ + virtual bool endpoint_read(usb_ep_t endpoint, uint32_t max_packet) = 0; + + /** + * Finish a read on the given endpoint + * + * @param endpoint Endpoint to read data from + * @param data Buffer to fill with data + * @param size Size of buffer + * @param bytes_read The number of bytes in the current packet. This can be larger than + * the size parameter if the buffer passed in was too small. + * @return true if data was read false otherwise + */ + virtual bool endpoint_read_result(usb_ep_t endpoint, uint8_t *data, uint32_t size, uint32_t *bytes_read) = 0; + + /** + * Start a write on the given endpoint + * + * @param endpoint Endpoint to write to + * @param data Buffer to write + * @param size Size of data to write + * @return true if the data was prepared for transmit, false otherwise + */ + virtual bool endpoint_write(usb_ep_t endpoint, uint8_t *data, uint32_t size) = 0; + + /** + * Abort the current transfer if it has not yet been sent + * + * @param endpoint Endpoint to abort the transfer on. It is implementation defined + * if this function has an effect on receive endpoints. + */ + virtual void endpoint_abort(usb_ep_t endpoint) = 0; + + /** + * Callback used for performing USB processing + * + * USBPhy processing should be triggered by calling USBPhyEvents::start_process + * and done inside process. All USBPhyEvents callbacks aside from + * USBPhyEvents::start_process must be called in the context of process + */ + virtual void process() = 0; +}; + +#endif diff --git a/platform/USBPhyEvents.h b/platform/USBPhyEvents.h new file mode 100644 index 0000000000..a6cb0e43f2 --- /dev/null +++ b/platform/USBPhyEvents.h @@ -0,0 +1,107 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef USBPHY_EVENTS_H +#define USBPHY_EVENTS_H + +#include "USBPhyTypes.h" + +/** Event handler for USBPhy + * + * This class is the event handler for the USBPhy class. Any events generated + * by USBPhy are passed to this class via the virtual functions. + * + * @ingroup usb_device_core + * + */ +class USBPhyEvents { +public: + USBPhyEvents() {}; + virtual ~USBPhyEvents() {}; + + /** + * Callback called when a bus reset occurs + * @note called in the contex of USBPhy::process + */ + virtual void reset() = 0; + + /** + * Callback called when an endpoint 0 setup packet is received + * @note called in the contex of USBPhy::process + */ + virtual void ep0_setup() = 0; + + /** + * Callback called when an endpoint 0 out packet is received + * @note called in the contex of USBPhy::process + */ + virtual void ep0_out() = 0; + + /** + * Callback called when an endpoint 0 in packet is received + * @note called in the contex of USBPhy::process + */ + virtual void ep0_in() = 0; + + /** + * Callback called USB power is applied or removed + * + * @param powered true if USB power is present, false otherwise + * @note called in the contex of USBPhy::process + */ + virtual void power(bool powered) = 0; + + /** + * Callback called when entering or leaving suspend mode + * + * @param suspended true if entering suspend mode false otherwise + * @note called in the contex of USBPhy::process + */ + virtual void suspend(bool suspended) = 0; + + /** + * Callback called on start of frame + * + * @param frame_number The current frame number + * @note This callback is enabled/disabled by + * calling USBPhy::sof_enable / USBPhy::sof_disable + * @note called in the contex of USBPhy::process + */ + virtual void sof(int frame_number) = 0; + + /** + * Callback called on the reception of an OUT packet + * + * @param endpoint Endpoint which received the OUT packet + * @note called in the contex of USBPhy::process + */ + virtual void out(usb_ep_t endpoint) = 0; + + /** + * Callback called on the transmission of an IN packet + * + * @param endpoint Endpoint which sent the IN packet + * @note called in the contex of USBPhy::process + */ + virtual void in(usb_ep_t endpoint) = 0; + + /** + * Callback called to indicate the USB processing needs to be done + */ + virtual void start_process() = 0; +}; + +#endif diff --git a/platform/USBPhyTypes.h b/platform/USBPhyTypes.h new file mode 100644 index 0000000000..7e69b5b339 --- /dev/null +++ b/platform/USBPhyTypes.h @@ -0,0 +1,58 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef USBPHY_TYPES_H +#define USBPHY_TYPES_H + +#include + +typedef uint8_t usb_ep_t; + +typedef enum { + USB_EP_TYPE_CTRL = 0, + USB_EP_TYPE_ISO = 1, + USB_EP_TYPE_BULK = 2, + USB_EP_TYPE_INT = 3 +} usb_ep_type_t; + +enum { + USB_EP_ATTR_ALLOW_CTRL = 1 << USB_EP_TYPE_CTRL, + USB_EP_ATTR_ALLOW_BULK = 1 << USB_EP_TYPE_BULK, + USB_EP_ATTR_ALLOW_INT = 1 << USB_EP_TYPE_INT, + USB_EP_ATTR_ALLOW_ISO = 1 << USB_EP_TYPE_ISO, + USB_EP_ATTR_ALLOW_ALL = USB_EP_ATTR_ALLOW_CTRL | USB_EP_ATTR_ALLOW_BULK | + USB_EP_ATTR_ALLOW_INT | USB_EP_ATTR_ALLOW_ISO, + + USB_EP_ATTR_DIR_IN = 0 << 4, + USB_EP_ATTR_DIR_OUT = 1 << 4, + USB_EP_ATTR_DIR_IN_OR_OUT = 2 << 4, + USB_EP_ATTR_DIR_IN_AND_OUT = 3 << 4, + USB_EP_ATTR_DIR_MASK = 3 << 4 +}; +typedef uint8_t usb_ep_attr_t; + +struct usb_ep_entry_t { + usb_ep_attr_t attributes; + uint8_t byte_cost; + uint16_t base_cost; +}; + +struct usb_ep_table_t { + uint32_t resources; + usb_ep_entry_t table[16]; +}; + +#endif diff --git a/usb/device/USBDevice/EndpointResolver.cpp b/usb/device/USBDevice/EndpointResolver.cpp new file mode 100644 index 0000000000..ad45006bb2 --- /dev/null +++ b/usb/device/USBDevice/EndpointResolver.cpp @@ -0,0 +1,136 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mbed.h" +#include "EndpointResolver.h" + +static uint32_t logical_to_index(uint32_t logical, bool in_not_out) +{ + return (logical << 1) | (in_not_out ? 1 : 0); +} + +static uint32_t index_to_logical(uint32_t index) +{ + return index >> 1; +} + + +EndpointResolver::EndpointResolver(const usb_ep_table_t *table) : _table(table), _cost(0), _used(0), _valid(true) +{ + // Do nothing +} + +EndpointResolver::~EndpointResolver() +{ + // Do nothing +} + +void EndpointResolver::endpoint_ctrl(uint32_t size) +{ + endpoint_in(USB_EP_TYPE_CTRL, size); + endpoint_out(USB_EP_TYPE_CTRL, size); +} + +usb_ep_t EndpointResolver::endpoint_in(usb_ep_type_t type, uint32_t size) +{ + int index = next_index(type, true); + if (index < 0) { + _valid = false; + return 0; + } + + const usb_ep_entry_t &entry = _table->table[index_to_logical(index)]; + _cost += entry.base_cost + entry.byte_cost * size; + _used |= 1 << index; + + return index_to_endpoint(index); +} + +usb_ep_t EndpointResolver::endpoint_out(usb_ep_type_t type, uint32_t size) +{ + int index = next_index(type, false); + if (index < 0) { + _valid = false; + return 0; + } + + const usb_ep_entry_t &entry = _table->table[index_to_logical(index)]; + _cost += entry.base_cost + entry.byte_cost * size; + _used |= 1 << index; + + return index_to_endpoint(index); +} + +bool EndpointResolver::valid() +{ + return _valid && (_cost <= _table->resources); +} + +void EndpointResolver::reset() { + _cost = 0; + _used = 0; + _valid = true; +} + +usb_ep_t EndpointResolver::index_to_endpoint(int index) +{ + return index_to_logical(index) | ((index & 1) ? 0x80 : 0); +} + +int EndpointResolver::next_index(usb_ep_type_t type, bool in_not_out) +{ + for (int logical = 0; logical < (int)(sizeof(_table->table) / sizeof(_table->table[0])); logical++) { + uint32_t index = logical_to_index(logical, in_not_out); + uint32_t other = logical_to_index(logical, !in_not_out); + const usb_ep_entry_t &entry = _table->table[logical]; + + usb_ep_attr_t dir = entry.attributes & USB_EP_ATTR_DIR_MASK; + bool in_allowed = dir != USB_EP_ATTR_DIR_OUT; + bool out_allowed = dir != USB_EP_ATTR_DIR_IN; + bool shared = dir == USB_EP_ATTR_DIR_IN_OR_OUT; + + if (!(entry.attributes & (1 << type))) { + // This type is not supported + continue; + } + + if (in_not_out && !in_allowed) { + // In endpoint not supported + continue; + } + + if (!in_not_out && !out_allowed) { + // Out endpoint not supported + continue; + } + + if (_used & (1 << index)) { + // This endpoint is in use + continue; + } + + if (shared && (1 << other)) { + // This endpoint can only be one direction at a time and is in + // use by the other direction + continue; + } + + return index; + } + + // Not found + return -1; +} diff --git a/usb/device/USBDevice/EndpointResolver.h b/usb/device/USBDevice/EndpointResolver.h new file mode 100644 index 0000000000..04f7d188f8 --- /dev/null +++ b/usb/device/USBDevice/EndpointResolver.h @@ -0,0 +1,89 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ENDPOINT_RESOLVER_H +#define ENDPOINT_RESOLVER_H + +#include "mbed.h" + +#include "USBPhy.h" + +/** + * Utility class for resolving endpoints + * + * This class is intended to make the process of + * selecting the correct endpoint from a device endpoint + * table easier. It also provides a verification function + * to check if the device has enough resources for the + * given configuration. + * + * @ingroup usb_device_core + */ +class EndpointResolver { +public: + EndpointResolver(const usb_ep_table_t *table); + ~EndpointResolver(); + + /** + * Add control endpoint size + * + * @param size Space reserved for control in and control out + */ + void endpoint_ctrl(uint32_t size); + + /** + * Return a free IN endpoint of the given size + * + * @param type Desired endpoint type + * @param size Space to reserve for this endpoint + * @return Endpoint index or 0 if there are not enough resources + */ + usb_ep_t endpoint_in(usb_ep_type_t type, uint32_t size); + + /** + * Return a free OUT endpoint of the given size + * + * @param type Desired endpoint type + * @param size Space to reserve for this endpoint + * @return Endpoint index or 0 if there are not enough resources + */ + usb_ep_t endpoint_out(usb_ep_type_t type, uint32_t size); + + /** + * Check if the endpoint configuration created so far is valid + * + * @return true if all endpoint sizes are available and fit, false otherwise + */ + bool valid(); + + /** + * Reset this class's state to when it was constructed + */ + void reset(); + +private: + + usb_ep_t index_to_endpoint(int index); + int next_index(usb_ep_type_t type, bool in_not_out); + + const usb_ep_table_t *_table; + uint32_t _cost; + uint32_t _used; + bool _valid; +}; + + +#endif