/* * This file is part of the unicore-mx project. * * Copyright (C) 2016 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_USBH_PRIVATE_H #define UNICOREMX_USBH_PRIVATE_H /* * The design is inspired from libusb and Linux Kernel. * You can think of Linux kernel + libusb for microcontroller. * * Optimization * =========== * - We have no malloc/free/realloc (so compile time allocation only!) * - Flexibility over "low" memory wastage * - Use flash when ever possible to store Data structure that are readonly. * - Use RAM only when it is required (ie legimate reason), look for other ways. * - No Blocking calls, use callbacks * libusb async transfer like interface implementation is only possible * due to this requirement. * - If possible, try to make interface like libusb or Linux kernel. * This is not a requirement, just a recommendation. * Anything similar to a know information is easier to remember. * * Note * ==== * To keep footprint smaller, * It is not possible to keep track of which interface use which endpoint. * That would require * - fetching whole configuration descriptor * - parse it and extract information * - then store the runtime information (memory required) * * So, application has to do it and pass appropriately to drivers, * - SET_INTERFACE, Clear DTOG of the endpoints inside it. * @a usbh_device_ep_clear() will be handy. * - SET_CONFIGURATION, Clear ALL DTOG of the endpoint. * @a usbh_device_ep_all_clear() will be handy. * * Note * ==== * Also, the stack manage external device connect/disconnect (on hub). * so, you can see the code as (usb-stack + hub-driver) [WIP, not implemented] * * Note * ==== * Usually uC have one processor. so multithreaded is not an issue! */ /* * TODO * ==== * - HUB code NOT written. current code only handles root device connect/disconnect. * - Write helper/ functions to convert UTF-16 to UTF-8 (and possibly for ASCII too) * - Add more Standard request in helper/stdreq.{c, h} (backend independent!) * - Bulk (IN, OUT) transfer need testing * - Interrupt OUT transfer need testing * - Add Support for Isochronous (IN, OUT) * - Think of a generic "USB classes" driver API for application code. */ #include /** * USB Device * @param host USB Host (if NULL, marker of object as unused) * @param parent Parent USB Device * @param port Port at which connected to parent * @param address Address on bus (0 as address not assigned) * @param ep0_max_packet_size bMaxPacketSize0 of device descriptor * (or a compatible value) * @param speed Speed of device * @param dtog Data Toggle bits * @param disconnected Called when the device get disconnected. * @param hub_ports Number of port of the hub. * If the device is not an hub, should be 0. * @param buffer a static buffer to fetch some descriptor internally * Used at */ struct usbh_device { usbh_host *host; usbh_device *parent; uint8_t port; uint8_t address; uint8_t ep0_max_packet_size; usbh_speed speed; /* BITx (x=0...15) = OUT EPx DTOG * BITx (x=16...31) = IN EPy DTOG (y = x - 16). * * Inside the callback, DTOG bit is: * - the packet is going to be transmitted with * - the packet is received for */ uint32_t dtog; usbh_disconnected_callback disconnected; uint8_t hub_ports; }; /** * USB Request Block */ struct usbh_urb { /** URB (USB Request Block) ID (0 is invalid) */ usbh_urb_id id; /** A copy of Transfer object provided by application */ usbh_transfer transfer; /** Backend allocated channel to URB (all high as invalid) */ uint8_t backend_tag; /** * Timeout value (microseconds) at * which the URB will become invalid. * If the field is 0, that means the the timeout is not enabled. */ uint64_t timeout_on; }; typedef struct usbh_urb usbh_urb; struct usbh_backend { /** * Initalize the backend */ usbh_host *(*init)(const usbh_backend_config *config); /** @copydoc usbh_host_speed() */ usbh_speed (*speed)(usbh_host *host); /** * Poll the peripherial. * @param host USB Host * @param now Current time = `last_poll + us` */ void (*poll)(usbh_host *host, uint64_t now); /** @copydoc usbh_reset() */ void (*reset)(usbh_host *host); /** * Submit the @a urb to @a host * @param host USB Host * @param urb USB Request Block * @return Backend TAG */ void (*transfer_submit)(usbh_host *host, usbh_urb *urb); /** * Cancel the URB * @param host USB Host * @param urb USB Request Block */ void (*transfer_cancel)(usbh_host *host, usbh_urb *urb); #if defined(USBH_BACKEND_EXTRA) USBH_BACKEND_EXTRA #endif }; #define URB_ARRAY_LENGTH 12 /** The maximum number of devices the host can hold. */ #define DEVICE_ARRAY_LENGTH 8 /** * USB Host * @param backend Host backend * @param last_poll Last time the has was polled via usbh_poll() * @param connected called when new device is connected on bus * @param next_device_address Bus address for next device * @param devices Static array of devices to store information * If the list become full, new devices will be dropped if item on available. * The list will also INCLUDE ROOT HUB. * @param next_urb_id Next URB ID (start from 1) * @param buffer Buffer to fetch device descriptor for device at enumeration. * Used for one device at a time. */ struct usbh_host { const usbh_backend *backend; uint64_t last_poll; usbh_connected_callback connected; uint8_t next_device_address; usbh_device devices[DEVICE_ARRAY_LENGTH]; usbh_urb_id next_urb_id; usbh_urb urbs[URB_ARRAY_LENGTH]; uint8_t buffer[8]; /** Backend configuration */ const struct usbh_backend_config *config; #if defined(USBH_HOST_EXTRA) USBH_HOST_EXTRA #endif }; void usbh_device_ep_dtog_toggle(usbh_device *dev, uint8_t ep); /** * Divide @a a by @a b and return the ceiling of quotient * @param a Dividend * @param b Divisor * @return ceiling of quotient */ #define DIV_AND_CEIL(a, b) (((a) + (b) - 1) / (b)) /** Convert milliseconds to microseconds */ #define MS2US(ms) ((ms) * 1000) /** * Perform a callback for transfer * @param t Transfer * @param status Status of transfer * @param transferred Length of data transferred */ #define TRANSFER_CALLBACK(transfer, status, urb_id) \ if ((transfer)->callback != NULL) { \ (transfer)->callback((transfer), (status), (urb_id)); \ } /** * Perform callback when no channel (aka pipe) is available for transfer * @param t Transfer */ #define TRANSFER_NO_RES(t) \ TRANSFER_CALLBACK((t), USBH_ERR_RES_UNAVAIL, USBH_INVALID_URB_ID) /** * Perform callback when transfer is invalid * @param t Transfer */ #define TRANSFER_INVALID(t) \ TRANSFER_CALLBACK((t), USBH_ERR_INVALID, USBH_INVALID_URB_ID) #define URB_CALLBACK(urb, status) \ TRANSFER_CALLBACK(&(urb)->transfer, status, (urb)->id) #if defined(USBH_DEBUG) extern void usbh_log_puts(const char *arg); extern void usbh_log_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); # include # define LOG(str) usbh_log_puts(str) # define LOGF(fmt,...) usbh_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__); #define INVALID_BACKEND_TAG 0xFF #define IS_OUT_ENDPOINT(ep_addr) (!((ep_addr) & 0x80)) #define IS_IN_ENDPOINT(ep_addr) ((ep_addr) & 0x80) #define IS_ROOT_HUB(dev) ((dev)->parent == NULL) #define MIN(a, b) (((a) > (b)) ? (b) : (a)) #define IS_URB_ID_INVALID(urb_id) ((urb_id) == USBH_INVALID_URB_ID) #define IS_URB_INVALID(urb) IS_URB_ID_INVALID((urb)->id) #define IS_DEVICE_INVALID(dev) ((dev)->host == NULL) #define IS_DEVICE_VALID(dev) (!IS_DEVICE_INVALID(dev)) /** * Generate a mask for uint32_t so that Data Toggle can be extracted * from the device->dtog value * @param ep_addr Endpoint address * @return mask */ static inline uint32_t ep_dtog_mask(uint8_t ep_addr) { bool ep_in = IS_IN_ENDPOINT(ep_addr); uint8_t ep_num = ep_addr & 0x7F; return 1 << (ep_num + (ep_in ? 16 : 0)); } void usbh_device_enum_start(usbh_device *dev); void usbh_device_invalidate(usbh_device *dev); void usbh_device_disconnected(usbh_device *dev); void usbh_device_connected(usbh_host *host, usbh_device *parent, uint8_t port, usbh_speed speed); void usbh_root_device_disconnected(usbh_host *host); void usbh_root_device_connected(usbh_host *host, usbh_speed speed); void *usbh_urb_get_data_pointer(usbh_urb *urb, uint16_t len); void usbh_urb_inc_data_pointer(usbh_urb *urb, uint16_t len); void usbh_urb_free(usbh_urb *urb, usbh_transfer_status status); void usbh_urb_invalidate(usbh_urb *urb); void usbh_hub_reset_port(usbh_device *dev, uint8_t port); #endif