572 lines
11 KiB
C
572 lines
11 KiB
C
/*
|
|
* Copyright (c) 2018 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* @brief USB native_posix device driver
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <misc/byteorder.h>
|
|
#include <usb/usb_dc.h>
|
|
#include <usb/usb_device.h>
|
|
#include <net/net_ip.h>
|
|
|
|
#include "usb_dc_native_posix_adapt.h"
|
|
|
|
#define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(native_posix);
|
|
|
|
/* convert from endpoint address to hardware endpoint index */
|
|
#define USBIP_EP_ADDR2IDX(ep) ((ep) & ~USB_EP_DIR_MASK)
|
|
/* get direction from endpoint address */
|
|
#define USBIP_EP_ADDR2DIR(ep) ((ep) & USB_EP_DIR_MASK)
|
|
/* convert from hardware endpoint index and direction to endpoint address */
|
|
#define USBIP_EP_IDX2ADDR(idx, dir) ((idx) | ((dir) & USB_EP_DIR_MASK))
|
|
|
|
#define USBIP_IN_EP_NUM 8
|
|
#define USBIP_OUT_EP_NUM 8
|
|
|
|
#define USBIP_MAX_PACKET_SIZE 64
|
|
|
|
K_THREAD_STACK_MEMBER(thread_stack, CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE);
|
|
static struct k_thread thread;
|
|
|
|
static void thread_main(void *a, void *b, void *c)
|
|
{
|
|
LOG_DBG("");
|
|
|
|
usbip_start();
|
|
}
|
|
|
|
/*
|
|
* USBIP private structures and logic initially copied from
|
|
* Designware USB driver
|
|
*/
|
|
|
|
/*
|
|
* USB endpoint private structure.
|
|
*/
|
|
struct usb_ep_ctrl_prv {
|
|
u8_t ep_ena;
|
|
u16_t mps;
|
|
usb_dc_ep_callback cb;
|
|
u32_t data_len;
|
|
u8_t buf[64];
|
|
u8_t buf_len;
|
|
};
|
|
|
|
/*
|
|
* USB controller private structure.
|
|
*/
|
|
static struct usbip_ctrl_prv {
|
|
usb_dc_status_callback status_cb;
|
|
struct usb_ep_ctrl_prv in_ep_ctrl[USBIP_IN_EP_NUM];
|
|
struct usb_ep_ctrl_prv out_ep_ctrl[USBIP_OUT_EP_NUM];
|
|
u8_t attached;
|
|
} usbip_ctrl;
|
|
|
|
static u8_t usbip_ep_is_valid(u8_t ep)
|
|
{
|
|
u8_t ep_idx = USBIP_EP_ADDR2IDX(ep);
|
|
|
|
/* Check if ep is valid */
|
|
if ((USBIP_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) &&
|
|
ep_idx < USBIP_OUT_EP_NUM) {
|
|
return 1;
|
|
} else if ((USBIP_EP_ADDR2DIR(ep) == USB_EP_DIR_IN) &&
|
|
ep_idx < USBIP_IN_EP_NUM) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u8_t usbip_ep_is_enabled(u8_t ep)
|
|
{
|
|
u8_t ep_idx = USBIP_EP_ADDR2IDX(ep);
|
|
|
|
LOG_DBG("ep %x", ep);
|
|
|
|
/* Check if ep enabled */
|
|
if ((USBIP_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) &&
|
|
usbip_ctrl.out_ep_ctrl[ep_idx].ep_ena) {
|
|
return 1;
|
|
} else if ((USBIP_EP_ADDR2DIR(ep) == USB_EP_DIR_IN) &&
|
|
usbip_ctrl.in_ep_ctrl[ep_idx].ep_ena) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_attach(void)
|
|
{
|
|
LOG_DBG("");
|
|
|
|
if (usbip_ctrl.attached) {
|
|
LOG_WRN("Already attached");
|
|
return 0;
|
|
}
|
|
|
|
k_thread_create(&thread, thread_stack,
|
|
CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE,
|
|
thread_main, NULL, NULL, NULL,
|
|
K_PRIO_COOP(2), 0, K_NO_WAIT);
|
|
|
|
usbip_ctrl.attached = 1U;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_detach(void)
|
|
{
|
|
LOG_DBG("");
|
|
|
|
if (!usbip_ctrl.attached) {
|
|
return 0;
|
|
}
|
|
|
|
usbip_ctrl.attached = 0U;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_reset(void)
|
|
{
|
|
LOG_DBG("");
|
|
|
|
/* Clear private data */
|
|
memset(&usbip_ctrl, 0, sizeof(usbip_ctrl));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_set_address(const u8_t addr)
|
|
{
|
|
LOG_DBG("");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg)
|
|
{
|
|
u8_t ep_idx = USBIP_EP_ADDR2IDX(cfg->ep_addr);
|
|
|
|
LOG_DBG("ep %x, mps %d, type %d", cfg->ep_addr, cfg->ep_mps,
|
|
cfg->ep_type);
|
|
|
|
if ((cfg->ep_type == USB_DC_EP_CONTROL) && ep_idx) {
|
|
LOG_ERR("invalid endpoint configuration");
|
|
return -1;
|
|
}
|
|
|
|
if (cfg->ep_mps > USBIP_MAX_PACKET_SIZE) {
|
|
LOG_WRN("unsupported packet size");
|
|
return -1;
|
|
}
|
|
|
|
if ((USBIP_EP_ADDR2DIR(cfg->ep_addr) == USB_EP_DIR_OUT) &&
|
|
(ep_idx >= USBIP_OUT_EP_NUM)) {
|
|
LOG_WRN("OUT endpoint address out of range");
|
|
return -1;
|
|
}
|
|
|
|
if ((USBIP_EP_ADDR2DIR(cfg->ep_addr) == USB_EP_DIR_IN) &&
|
|
(ep_idx >= USBIP_IN_EP_NUM)) {
|
|
LOG_WRN("IN endpoint address out of range");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data * const cfg)
|
|
{
|
|
u16_t ep_mps = cfg->ep_mps;
|
|
u8_t ep = cfg->ep_addr;
|
|
u8_t ep_idx = USBIP_EP_ADDR2IDX(ep);
|
|
|
|
if (usb_dc_ep_check_cap(cfg)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) {
|
|
LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (USBIP_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
usbip_ctrl.out_ep_ctrl[ep_idx].mps = ep_mps;
|
|
} else {
|
|
usbip_ctrl.in_ep_ctrl[ep_idx].mps = ep_mps;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_set_stall(const u8_t ep)
|
|
{
|
|
LOG_DBG("ep %x", ep);
|
|
|
|
if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) {
|
|
LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Use standard reply for now */
|
|
usb_dc_ep_write(0x80, NULL, 0, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_clear_stall(const u8_t ep)
|
|
{
|
|
u8_t ep_idx = USBIP_EP_ADDR2IDX(ep);
|
|
|
|
LOG_DBG("ep %x", ep);
|
|
|
|
if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) {
|
|
LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ep_idx) {
|
|
/* Not possible to clear stall for EP0 */
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_halt(const u8_t ep)
|
|
{
|
|
u8_t ep_idx = USBIP_EP_ADDR2IDX(ep);
|
|
|
|
LOG_DBG("ep %x", ep);
|
|
|
|
if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) {
|
|
LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ep_idx) {
|
|
/* Cannot disable EP0, just set stall */
|
|
usb_dc_ep_set_stall(ep);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_is_stalled(const u8_t ep, u8_t *const stalled)
|
|
{
|
|
LOG_DBG("ep %x", ep);
|
|
|
|
if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) {
|
|
LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!stalled) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_enable(const u8_t ep)
|
|
{
|
|
u8_t ep_idx = USBIP_EP_ADDR2IDX(ep);
|
|
|
|
LOG_DBG("ep %x", ep);
|
|
|
|
if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) {
|
|
LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Enable Ep */
|
|
if (USBIP_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
usbip_ctrl.out_ep_ctrl[ep_idx].ep_ena = 1U;
|
|
} else {
|
|
usbip_ctrl.in_ep_ctrl[ep_idx].ep_ena = 1U;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_disable(const u8_t ep)
|
|
{
|
|
LOG_DBG("ep %x", ep);
|
|
|
|
if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) {
|
|
LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_flush(const u8_t ep)
|
|
{
|
|
LOG_DBG("ep %x", ep);
|
|
|
|
if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) {
|
|
LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (USBIP_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
/* RX FIFO is global and cannot be flushed per EP */
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_write(const u8_t ep, const u8_t *const data,
|
|
const u32_t data_len, u32_t * const ret_bytes)
|
|
{
|
|
LOG_DBG("ep %x len %u", ep, data_len);
|
|
|
|
if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) {
|
|
LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check if IN ep */
|
|
if (USBIP_EP_ADDR2DIR(ep) != USB_EP_DIR_IN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check if ep enabled */
|
|
if (!usbip_ep_is_enabled(ep)) {
|
|
LOG_WRN("ep %x disabled", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (USBIP_EP_ADDR2IDX(ep) == 0) {
|
|
usbip_send_common(ep, data_len);
|
|
usbip_send(ep, data, data_len);
|
|
} else {
|
|
u8_t ep_idx = USBIP_EP_ADDR2IDX(ep);
|
|
struct usb_ep_ctrl_prv *ctrl = &usbip_ctrl.in_ep_ctrl[ep_idx];
|
|
|
|
memcpy(ctrl->buf, data, data_len);
|
|
ctrl->buf_len = data_len;
|
|
}
|
|
|
|
if (ret_bytes) {
|
|
*ret_bytes = data_len;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_read_wait(u8_t ep, u8_t *data, u32_t max_data_len,
|
|
u32_t *read_bytes)
|
|
{
|
|
u32_t bytes;
|
|
|
|
if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) {
|
|
LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check if OUT ep */
|
|
if (USBIP_EP_ADDR2DIR(ep) != USB_EP_DIR_OUT) {
|
|
LOG_ERR("Wrong endpoint direction");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Allow to read 0 bytes */
|
|
if (!data && max_data_len) {
|
|
LOG_ERR("Wrong arguments");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check if ep enabled */
|
|
if (!usbip_ep_is_enabled(ep)) {
|
|
LOG_ERR("Not enabled endpoint");
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_DBG("ep %x max_data_len %u", ep, max_data_len);
|
|
|
|
bytes = usbip_recv(data, max_data_len);
|
|
|
|
if (read_bytes) {
|
|
*read_bytes = bytes;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_read_continue(u8_t ep)
|
|
{
|
|
u8_t ep_idx = USBIP_EP_ADDR2IDX(ep);
|
|
|
|
if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) {
|
|
LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check if OUT ep */
|
|
if (USBIP_EP_ADDR2DIR(ep) != USB_EP_DIR_OUT) {
|
|
LOG_ERR("Wrong endpoint direction");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!usbip_ctrl.out_ep_ctrl[ep_idx].data_len) {
|
|
/* TODO: continue read */
|
|
/* usbip_prep_rx(ep_idx, 0); */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_read(const u8_t ep, u8_t *const data,
|
|
const u32_t max_data_len, u32_t * const read_bytes)
|
|
{
|
|
LOG_DBG("ep %x max_data_len %u", ep, max_data_len);
|
|
|
|
if (usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!data && !max_data_len) {
|
|
/* When both buffer and max data to read are zero the above
|
|
* call would fetch the data len and we simply return.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
if (usb_dc_ep_read_continue(ep) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_set_callback(const u8_t ep, const usb_dc_ep_callback cb)
|
|
{
|
|
u8_t ep_idx = USBIP_EP_ADDR2IDX(ep);
|
|
|
|
LOG_DBG("ep %x callback %p", ep, cb);
|
|
|
|
if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) {
|
|
LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (USBIP_EP_ADDR2DIR(ep) == USB_EP_DIR_IN) {
|
|
usbip_ctrl.in_ep_ctrl[ep_idx].cb = cb;
|
|
} else {
|
|
usbip_ctrl.out_ep_ctrl[ep_idx].cb = cb;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void usb_dc_set_status_callback(const usb_dc_status_callback cb)
|
|
{
|
|
usbip_ctrl.status_cb = cb;
|
|
}
|
|
|
|
int usb_dc_ep_mps(const u8_t ep)
|
|
{
|
|
u8_t ep_idx = USBIP_EP_ADDR2IDX(ep);
|
|
|
|
LOG_DBG("ep %x", ep);
|
|
|
|
if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) {
|
|
LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (USBIP_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
return usbip_ctrl.out_ep_ctrl[ep_idx].mps;
|
|
} else {
|
|
return usbip_ctrl.in_ep_ctrl[ep_idx].mps;
|
|
}
|
|
}
|
|
|
|
int handle_usb_control(struct usbip_header *hdr)
|
|
{
|
|
u8_t ep_idx = USBIP_EP_ADDR2IDX(ntohl(hdr->common.ep));
|
|
usb_dc_ep_callback ep_cb = usbip_ctrl.out_ep_ctrl[ep_idx].cb;
|
|
|
|
LOG_DBG("ep %x idx %u", ntohl(hdr->common.ep), ep_idx);
|
|
|
|
if (ep_cb) {
|
|
LOG_DBG("Call ep_cb");
|
|
ep_cb(ntohl(hdr->common.ep), USB_DC_EP_SETUP);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void usbip_skip_setup(void)
|
|
{
|
|
u64_t setup;
|
|
|
|
LOG_DBG("Skip 8 bytes");
|
|
|
|
usbip_recv((void *)&setup, sizeof(setup));
|
|
}
|
|
|
|
int handle_usb_data(struct usbip_header *hdr)
|
|
{
|
|
u8_t ep_idx = ntohl(hdr->common.ep);
|
|
usb_dc_ep_callback ep_cb;
|
|
int bytes;
|
|
u8_t ep;
|
|
|
|
LOG_DBG("ep_idx %u", ep_idx);
|
|
|
|
if (ntohl(hdr->common.direction) == USBIP_DIR_OUT) {
|
|
if (ep_idx >= USBIP_OUT_EP_NUM) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ep = ep_idx | USB_EP_DIR_OUT;
|
|
ep_cb = usbip_ctrl.out_ep_ctrl[ep_idx].cb;
|
|
|
|
ep_cb(ep, USB_DC_EP_DATA_OUT);
|
|
|
|
/* Send ACK reply */
|
|
bytes = usbip_send_common(ep, 0);
|
|
} else {
|
|
if (ep_idx >= USBIP_IN_EP_NUM) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ep = ep_idx | USB_EP_DIR_IN;
|
|
ep_cb = usbip_ctrl.in_ep_ctrl[ep_idx].cb;
|
|
|
|
usbip_skip_setup();
|
|
|
|
LOG_DBG("Send %u bytes", usbip_ctrl.in_ep_ctrl[ep_idx].buf_len);
|
|
|
|
/* Send queued data */
|
|
usbip_send_common(ep, usbip_ctrl.in_ep_ctrl[ep_idx].buf_len);
|
|
usbip_send(ep, usbip_ctrl.in_ep_ctrl[ep_idx].buf,
|
|
usbip_ctrl.in_ep_ctrl[ep_idx].buf_len);
|
|
|
|
LOG_HEXDUMP_DBG(usbip_ctrl.in_ep_ctrl[ep_idx].buf,
|
|
usbip_ctrl.in_ep_ctrl[ep_idx].buf_len, ">");
|
|
|
|
/* Indicate data sent */
|
|
ep_cb(ep, USB_DC_EP_DATA_IN);
|
|
}
|
|
|
|
LOG_DBG("ep %x ep_cb %p", ep, ep_cb);
|
|
|
|
return 0;
|
|
}
|