438 lines
12 KiB
C
438 lines
12 KiB
C
/**
|
|
* @defgroup usbd_private_defines USB Private Structures
|
|
*
|
|
* @brief <b>Defined Constants and Types for the USB Private Structures</b>
|
|
*
|
|
* @ingroup USBD_defines
|
|
*
|
|
* @author @htmlonly © @endhtmlonly 2016
|
|
* Kuldeep Singh Dhaka <kuldeepdhaka9@gmail.com>
|
|
*
|
|
* @author @htmlonly © @endhtmlonly 2010
|
|
* Gareth McMullin <gareth@blacksphere.co.nz>
|
|
*
|
|
* @date 11 September 2016
|
|
*
|
|
* LGPL License Terms @ref lgpl_license
|
|
*/
|
|
|
|
/*
|
|
* This file is part of the unicore-mx project.
|
|
*
|
|
* Copyright (C) 2010 Gareth McMullin <gareth@blacksphere.co.nz>
|
|
* Copyright (C) 2016, 2017 Kuldeep Singh Dhaka <kuldeepdhaka9@gmail.com>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**@{*/
|
|
|
|
#ifndef UNICOREMX_USBD_PRIVATE_H
|
|
#define UNICOREMX_USBD_PRIVATE_H
|
|
|
|
#include <unicore-mx/usbd/usbd.h>
|
|
|
|
/**
|
|
* 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 <inttypes.h>
|
|
# 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
|