/** * @defgroup usbd_private_defines USB Private Structures * * @brief Defined Constants and Types for the USB Private Structures * * @ingroup USBD_defines * * @author @htmlonly © @endhtmlonly 2016 * Kuldeep Singh Dhaka * * @author @htmlonly © @endhtmlonly 2010 * Gareth McMullin * * @date 11 September 2016 * * LGPL License Terms @ref lgpl_license */ /* * This file is part of the unicore-mx project. * * Copyright (C) 2010 Gareth McMullin * Copyright (C) 2016, 2017 Kuldeep Singh Dhaka * * 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 UNICOREMX_USBD_PRIVATE_H #define UNICOREMX_USBD_PRIVATE_H #include /** * Compile time configuration: \n * USBD_URB_COUNT: Number of URB Object to allocate (default: 20) \n * USBD_ENABLE_TIMEOUT: Define to enable timeout functionality (default: undefined) */ #if defined(USBD_URB_COUNT) && (USBD_URB_COUNT < 1) # error "Sanity check failed!!! go get sleep." \ "USBD_URB_COUNT less than 1 is meaningless in our universe." #endif #if !defined(USBD_URB_COUNT) # define USBD_URB_COUNT 20 #endif #if defined(USBD_INTEFACE_MAX) && (USBD_INTEFACE_MAX < 0) # error "Sanity check failed!!! go get sleep." \ "USBD_INTEFACE_MAX less than 0 is meaningless in our universe." #endif /** * Maximum number of interface for memory to allocate. * The allocated memory is used keep track of the SET_INTERFACE setting * If the value is 0, no memory is allocated * * @note If a value could not be stored * (due to no memory allocated OR allocate memory not enought to store the value), * then the following behavour apply: * - SET_INTEFACE for other than alternate-setting = 0 will always result in STALL. * - GET_INTEFACE will always result in STALL */ #if !defined(USBD_INTERFACE_MAX) # define USBD_INTERFACE_MAX 8 #endif #if defined(__DOXYGEN__) # define USBD_ENABLE_TIMEOUT #endif /* define macro "USBD_ENABLE_TIMEOUT" to enable timeout functionality. * It can help save processing as well as RAM. * By default - disabled. */ #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) struct usbd_urb { uint64_t id; usbd_transfer transfer; #if defined(USBD_ENABLE_TIMEOUT) uint64_t timeout_on; #endif struct usbd_urb *next; }; typedef struct usbd_urb usbd_urb; /** Internal collection of device information. */ struct usbd_device { /** * Current configuration of the device. * If NULL, then the device is in {default, address} state * If not NULL, then device is in configured state. */ const struct usb_config_descriptor *current_config; #if (USBD_INTERFACE_MAX > 0) /** * Store the current alternate setting of interfaces. * Interface number is used as array index (used to store). */ const struct usb_interface_descriptor *current_iface[USBD_INTERFACE_MAX]; #endif /** Device descriptor and other details. */ const struct usbd_info *info; struct { /** invoked on bus-reset */ usbd_generic_callback reset; /** invoked on bus-suspend */ usbd_generic_callback suspend; /** invoked on bus-resume */ usbd_generic_callback resume; /** invoked when sof received */ usbd_generic_callback sof; /** invoked on SETUP packet */ usbd_setup_callback setup; /** invoked on SET_CONFIGURATION */ usbd_set_config_callback set_config; /** invoked on SET_INTERFACE */ usbd_set_interface_callback set_interface; } callback; /** Backend */ const struct usbd_backend *backend; /** Backend configuration */ const struct usbd_backend_config *config; #if defined(USBD_ENABLE_TIMEOUT) uint64_t last_poll; #endif /** * @a active - URB that are being processed * @a waiting - URB that are waiting to be added to @a active once * the endpoint become free. */ struct { /** * @a head - Head of the Queue * @a tail - Tail of the Queue */ struct usbd_urb_queue { usbd_urb *head, *tail; } active, waiting; /** * 1 Means the endpoint is free to be used. * 0 Means the endpoint is being used. * * BITn (where n = 0-15) - Endpoint OUT n * BIT(n+16) (where n = 0-15) - Endpoint IN n * * If the endpoint is bidirectional (control) then, * the IN and OUT need to be marked together. */ uint32_t ep_free; /** List of unused objects (invalid) and empty shell for transfer */ usbd_urb *unused; /** Array of URB allocated at compile time */ usbd_urb arr[USBD_URB_COUNT]; uint64_t next_id; /** Only allow EP0 transfer. * main use case is, ep_prepare_start and ep_prepare_end block */ bool force_all_new_urb_to_waiting; } urbs; #if defined(USBD_DEVICE_EXTRA) USBD_DEVICE_EXTRA #endif }; /* Functions provided by the hardware abstraction. */ struct usbd_backend { usbd_device * (*init)(const usbd_backend_config *config); void (*set_address)(usbd_device *dev, uint8_t addr); uint8_t (*get_address)(usbd_device *dev); void (*ep_prepare_start)(usbd_device *dev); void (*ep_prepare)(usbd_device *dev, uint8_t addr, usbd_ep_type type, uint16_t max_size, uint16_t interval, usbd_ep_flags flags); void (*ep_prepare_end)(usbd_device *dev); void (*set_ep_dtog)(usbd_device *dev, uint8_t addr, bool dtog); bool (*get_ep_dtog)(usbd_device *dev, uint8_t addr); void (*set_ep_stall)(usbd_device *dev, uint8_t addr, bool stall); bool (*get_ep_stall)(usbd_device *dev, uint8_t addr); void (*poll)(usbd_device *dev); void (*disconnect)(usbd_device *dev, bool disconnected); void (*enable_sof)(usbd_device *dev, bool enable); usbd_speed (*get_speed)(usbd_device *dev); void (*urb_submit)(usbd_device *dev, usbd_urb *urb); void (*urb_cancel)(usbd_device *dev, usbd_urb *urb); /* Frame number */ uint16_t (*frame_number)(usbd_device *dev); /* * this is to tell usb generic code * that address need to be set before status-stage. */ bool set_address_before_status; /* somewhere in backend header or code. * WARNING: this will make the struct size variable. * (cannot be used for array - anyway no one will be used it for array) */ #if defined(USBD_BACKEND_EXTRA) USBD_BACKEND_EXTRA #endif }; #if defined(USBD_DEBUG) extern void usbd_log_puts(const char *arg); extern void usbd_log_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); # include # define LOG(str) usbd_log_puts(str) # define LOGF(fmt,...) usbd_log_printf(fmt, ##__VA_ARGS__) #else # define LOG(str) # define LOGF(fmt,...) #endif #define NEW_LINE "\n" #define LOG_LN(str) LOG(str); LOG(NEW_LINE) #define LOGF_LN(fmt,...) LOGF(fmt, __VA_ARGS__); LOG(NEW_LINE) #define LOG_CALL LOG("inside "); LOG_LN(__func__); /** Convert milliseconds to microseconds */ #define MS2US(ms) ((ms) * 1000) #define DIVIDE_AND_CEIL(divident, divisor) \ ((divident) + (divisor) - 1) / (divisor) /** * Perform a callback for transfer * @param[in] t Transfer * @param[in] status Status of transfer * @param[in] transferred Length of data transferred */ #define TRANSFER_CALLBACK(dev, transfer, status, urb_id) \ if ((transfer)->callback != NULL) { \ (transfer)->callback((dev), (transfer), (status), (urb_id)); \ } /* Just for readability, nothing special */ #define IS_OUT_ENDPOINT(ep_addr) (!((ep_addr) & 0x80)) #define IS_IN_ENDPOINT(ep_addr) (!!((ep_addr) & 0x80)) #define ENDPOINT_NUMBER(ep_addr) ((ep_addr) & 0x7F) void usbd_urb_complete(usbd_device *dev, usbd_urb *urb, usbd_transfer_status status); void usbd_urb_detach_from_active(usbd_device *dev, usbd_urb *urb); void usbd_urb_schedule(usbd_device *dev); usbd_urb *usbd_find_active_urb(usbd_device *dev, uint8_t ep); void *usbd_urb_get_buffer_pointer(usbd_device *dev, usbd_urb *urb, size_t len); void usbd_urb_inc_data_pointer(usbd_device *dev, usbd_urb *urb, size_t len); #if defined(USBD_ENABLE_TIMEOUT) void usbd_timeout_checkup(usbd_device *dev, uint64_t now); #endif void usbd_purge_all_transfer(usbd_device *dev, usbd_transfer_status status); void usbd_put_all_urb_into_unused(usbd_device *dev); void usbd_purge_all_non_ep0_transfer(usbd_device *dev, usbd_transfer_status status); static inline uint32_t ep_free_mask(uint8_t ep_addr); static inline void usbd_handle_suspend(usbd_device *dev); static inline void usbd_handle_resume(usbd_device *dev); static inline void usbd_handle_sof(usbd_device *dev); static inline void usbd_handle_setup(usbd_device *dev, uint8_t ep, const struct usb_setup_data *setup_data); static inline void usbd_handle_reset(usbd_device *dev); static inline bool is_ep_free(usbd_device *dev, uint8_t ep_addr); static inline void mark_ep_as_free(usbd_device *dev, uint8_t ep_addr, bool yes); /** * Get the DTOG bit mask for @a ep_addr * @param[in] ep_addr Endpoint address (including direction) * @return mask */ static inline uint32_t ep_free_mask(uint8_t ep_addr) { uint32_t num = ENDPOINT_NUMBER(ep_addr); if (IS_IN_ENDPOINT(ep_addr)) { num += 16; } return 1 << num; } /** * SUSPEND detected on bus * @param[in] dev USB Device */ static inline void usbd_handle_suspend(usbd_device *dev) { LOG_LN("SUSPEND detected!"); if (dev->callback.suspend != NULL) { dev->callback.suspend(dev); } } /** * RESUME detected on bus * @param[in] dev USB Device */ static inline void usbd_handle_resume(usbd_device *dev) { LOG_LN("RESUME detected!"); if (dev->callback.resume != NULL) { dev->callback.resume(dev); } } /** * SOF detected on bus * @param[in] dev USB Device */ static inline void usbd_handle_sof(usbd_device *dev) { /* Oh no! dont print anything here... or else... */ if (dev->callback.sof != NULL) { dev->callback.sof(dev); } } /** * Called by backend to pass the setup data when a SETUP packet is recevied * on control endpoint. * @param[in] dev USB Device * @param[in] ep Endpoint on which SETUP was received * @param[in] setup_data Setup Data * @note setup_data is only expected to be valid till this function do not return. */ static inline void usbd_handle_setup(usbd_device *dev, uint8_t ep, const struct usb_setup_data *setup_data) { if (dev->callback.setup != NULL) { dev->callback.setup(dev, ep, setup_data); } else if (!ep) { /* No callback registered, route to ep0 by default */ usbd_ep0_setup(dev, setup_data); } else { LOGF_LN("WARNING: Application code need to handle SETUP packet on " "0x%"PRIx8", (stalling...)", ep); usbd_set_ep_stall(dev, ENDPOINT_NUMBER(ep) | 0x80, true); usbd_set_ep_stall(dev, ENDPOINT_NUMBER(ep), true); } } /** * RESET detected on bus * @param[in] dev USB Device */ static inline void usbd_handle_reset(usbd_device *dev) { LOG_LN("RESET detected"); usbd_purge_all_transfer(dev, USBD_ERR_CONN); dev->urbs.force_all_new_urb_to_waiting = false; dev->current_config = NULL; if (dev->callback.reset != NULL) { dev->callback.reset(dev); } } /** * Check if the endpoint is free * @param[in] dev USB Device * @param[in] ep_addr Endpoint (including direction) */ static inline bool is_ep_free(usbd_device *dev, uint8_t ep_addr) { return !!(dev->urbs.ep_free & ep_free_mask(ep_addr)); } /** * Mark the endpoint as unused on @a yes * @param[in] dev USB Device * @param[in] ep_addr Endpoint address (including direction) * @param[in] yes Yes (if true, mark it as unused) */ static inline void mark_ep_as_free(usbd_device *dev, uint8_t ep_addr, bool yes) { uint32_t mask = ep_free_mask(ep_addr); if (yes) { dev->urbs.ep_free |= mask; } else { dev->urbs.ep_free &= ~mask; } } /* Do not appear to belong to the API, so are omitted from docs */ /**@}*/ #endif