usb: move USB transfer functions to appropriate file

Move USB transfer functions to appropriate file.

Signed-off-by: Johann Fischer <j.fischer@phytec.de>
This commit is contained in:
Johann Fischer 2019-12-12 16:38:28 +01:00 committed by Carles Cufí
parent 47a51d4698
commit 7bd0de0c61
4 changed files with 355 additions and 311 deletions

View file

@ -6,6 +6,7 @@ if(CONFIG_USB_DEVICE_STACK)
zephyr_sources(
usb_device.c
usb_descriptor.c
usb_transfer.c
)
add_subdirectory(class)

View file

@ -76,6 +76,7 @@ LOG_MODULE_REGISTER(usb_device);
#include <usb/bos.h>
#include <os_desc.h>
#include "usb_transfer.h"
#define MAX_DESC_HANDLERS 4 /** Device, interface, endpoint, other */
@ -100,8 +101,6 @@ LOG_MODULE_REGISTER(usb_device);
#define MAX_NUM_REQ_HANDLERS 4
#define MAX_STD_REQ_MSG_SIZE 8
#define MAX_NUM_TRANSFERS 4 /** Max number of parallel transfers */
/* Default USB control EP, always 0 and 0x80 */
#define USB_CONTROL_OUT_EP0 0
#define USB_CONTROL_IN_EP0 0x80
@ -112,31 +111,6 @@ extern struct usb_cfg_data __usb_data_end[];
K_MUTEX_DEFINE(usb_enable_lock);
struct usb_transfer_data {
/** endpoint associated to the transfer */
u8_t ep;
/** Transfer status */
int status;
/** Transfer read/write buffer */
u8_t *buffer;
/** Transfer buffer size */
size_t bsize;
/** Transferred size */
size_t tsize;
/** Transfer callback */
usb_transfer_callback cb;
/** Transfer caller private data */
void *priv;
/** Transfer synchronization semaphore */
struct k_sem sem;
/** Transfer read/write work */
struct k_work work;
/** Transfer flags */
unsigned int flags;
};
static void usb_transfer_work(struct k_work *item);
static struct usb_dev_priv {
/** Setup packet */
struct usb_setup_packet setup;
@ -169,8 +143,6 @@ static struct usb_dev_priv {
u8_t configuration;
/** Remote wakeup feature status */
bool remote_wakeup;
/** Transfer list */
struct usb_transfer_data transfer[MAX_NUM_TRANSFERS];
} usb_dev;
/*
@ -1175,281 +1147,6 @@ int usb_ep_read_continue(u8_t ep)
return usb_dc_ep_read_continue(ep);
}
/* Transfer management */
static struct usb_transfer_data *usb_ep_get_transfer(u8_t ep)
{
for (int i = 0; i < ARRAY_SIZE(usb_dev.transfer); i++) {
if (usb_dev.transfer[i].ep == ep) {
return &usb_dev.transfer[i];
}
}
return NULL;
}
bool usb_transfer_is_busy(u8_t ep)
{
struct usb_transfer_data *trans = usb_ep_get_transfer(ep);
if (trans && trans->status == -EBUSY) {
return true;
}
return false;
}
static void usb_transfer_work(struct k_work *item)
{
struct usb_transfer_data *trans;
int ret = 0;
u32_t bytes;
u8_t ep;
trans = CONTAINER_OF(item, struct usb_transfer_data, work);
ep = trans->ep;
if (trans->status != -EBUSY) {
/* transfer cancelled or already completed */
goto done;
}
if (trans->flags & USB_TRANS_WRITE) {
if (!trans->bsize) {
if (!(trans->flags & USB_TRANS_NO_ZLP)) {
usb_write(ep, NULL, 0, NULL);
}
trans->status = 0;
goto done;
}
ret = usb_write(ep, trans->buffer, trans->bsize, &bytes);
if (ret) {
LOG_ERR("Transfer error %d", ret);
/* transfer error */
trans->status = -EINVAL;
goto done;
}
trans->buffer += bytes;
trans->bsize -= bytes;
trans->tsize += bytes;
} else {
ret = usb_dc_ep_read_wait(ep, trans->buffer, trans->bsize,
&bytes);
if (ret) {
/* transfer error */
trans->status = -EINVAL;
goto done;
}
trans->buffer += bytes;
trans->bsize -= bytes;
trans->tsize += bytes;
/* ZLP, short-pkt or buffer full */
if (!bytes || (bytes % usb_dc_ep_mps(ep)) || !trans->bsize) {
/* transfer complete */
trans->status = 0;
goto done;
}
/* we expect mote data, clear NAK */
usb_dc_ep_read_continue(ep);
}
done:
if (trans->status != -EBUSY && trans->cb) { /* Transfer complete */
usb_transfer_callback cb = trans->cb;
int tsize = trans->tsize;
void *priv = trans->priv;
if (k_is_in_isr()) {
/* reschedule completion in thread context */
k_work_submit(&trans->work);
return;
}
LOG_DBG("transfer done, ep=%02x, status=%d, size=%zu",
trans->ep, trans->status, trans->tsize);
trans->cb = NULL;
k_sem_give(&trans->sem);
/* Transfer completion callback */
if (trans->status != -ECANCELED) {
cb(ep, tsize, priv);
}
}
}
void usb_transfer_ep_callback(u8_t ep, enum usb_dc_ep_cb_status_code status)
{
struct usb_transfer_data *trans = usb_ep_get_transfer(ep);
if (status != USB_DC_EP_DATA_IN && status != USB_DC_EP_DATA_OUT) {
return;
}
if (!trans) {
if (status == USB_DC_EP_DATA_OUT) {
u32_t bytes;
/* In the unlikely case we receive data while no
* transfer is ongoing, we have to consume the data
* anyway. This is to prevent stucking reception on
* other endpoints (e.g dw driver has only one rx-fifo,
* so drain it).
*/
do {
u8_t data;
usb_dc_ep_read_wait(ep, &data, 1, &bytes);
} while (bytes);
LOG_ERR("RX data lost, no transfer");
}
return;
}
if (!k_is_in_isr() || (status == USB_DC_EP_DATA_OUT)) {
/* If we are not in IRQ context, no need to defer work */
/* Read (out) needs to be done from ep_callback */
usb_transfer_work(&trans->work);
} else {
k_work_submit(&trans->work);
}
}
int usb_transfer(u8_t ep, u8_t *data, size_t dlen, unsigned int flags,
usb_transfer_callback cb, void *cb_data)
{
struct usb_transfer_data *trans = NULL;
int i, key, ret = 0;
LOG_DBG("transfer start, ep=%02x, data=%p, dlen=%zd",
ep, data, dlen);
key = irq_lock();
for (i = 0; i < MAX_NUM_TRANSFERS; i++) {
if (!k_sem_take(&usb_dev.transfer[i].sem, K_NO_WAIT)) {
trans = &usb_dev.transfer[i];
break;
}
}
if (!trans) {
LOG_ERR("no transfer slot available");
ret = -ENOMEM;
goto done;
}
if (trans->status == -EBUSY) {
/* A transfer is already ongoing and not completed */
k_sem_give(&trans->sem);
ret = -EBUSY;
goto done;
}
/* Configure new transfer */
trans->ep = ep;
trans->buffer = data;
trans->bsize = dlen;
trans->tsize = 0;
trans->cb = cb;
trans->flags = flags;
trans->priv = cb_data;
trans->status = -EBUSY;
if (usb_dc_ep_mps(ep) && (dlen % usb_dc_ep_mps(ep))) {
/* no need to send ZLP since last packet will be a short one */
trans->flags |= USB_TRANS_NO_ZLP;
}
if (flags & USB_TRANS_WRITE) {
/* start writing first chunk */
k_work_submit(&trans->work);
} else {
/* ready to read, clear NAK */
ret = usb_dc_ep_read_continue(ep);
}
done:
irq_unlock(key);
return ret;
}
void usb_cancel_transfer(u8_t ep)
{
struct usb_transfer_data *trans;
unsigned int key;
key = irq_lock();
trans = usb_ep_get_transfer(ep);
if (!trans) {
goto done;
}
if (trans->status != -EBUSY) {
goto done;
}
trans->status = -ECANCELED;
k_work_submit(&trans->work);
done:
irq_unlock(key);
}
void usb_cancel_transfers(void)
{
for (int i = 0; i < ARRAY_SIZE(usb_dev.transfer); i++) {
struct usb_transfer_data *trans = &usb_dev.transfer[i];
unsigned int key;
key = irq_lock();
if (trans->status == -EBUSY) {
trans->status = -ECANCELED;
k_work_submit(&trans->work);
LOG_DBG("Cancel transfer");
}
irq_unlock(key);
}
}
struct usb_transfer_sync_priv {
int tsize;
struct k_sem sem;
};
static void usb_transfer_sync_cb(u8_t ep, int size, void *priv)
{
struct usb_transfer_sync_priv *pdata = priv;
pdata->tsize = size;
k_sem_give(&pdata->sem);
}
int usb_transfer_sync(u8_t ep, u8_t *data, size_t dlen, unsigned int flags)
{
struct usb_transfer_sync_priv pdata;
int ret;
k_sem_init(&pdata.sem, 0, 1);
ret = usb_transfer(ep, data, dlen, flags, usb_transfer_sync_cb, &pdata);
if (ret) {
return ret;
}
/* Semaphore will be released by the transfer completion callback */
k_sem_take(&pdata.sem, K_FOREVER);
return pdata.tsize;
}
int usb_wakeup_request(void)
{
if (IS_ENABLED(CONFIG_USB_DEVICE_REMOTE_WAKEUP)) {
@ -1606,7 +1303,6 @@ int usb_set_config(const u8_t *device_descriptor)
int usb_enable(usb_dc_status_callback status_cb)
{
int ret;
u32_t i;
struct usb_dc_ep_cfg_data ep0_cfg;
/* Prevent from calling usb_enable form different contex.
@ -1635,6 +1331,11 @@ int usb_enable(usb_dc_status_callback status_cb)
goto out;
}
ret = usb_transfer_init();
if (ret < 0) {
goto out;
}
/* Configure control EP */
ep0_cfg.ep_mps = USB_MAX_CTRL_MPS;
ep0_cfg.ep_type = USB_DC_EP_CONTROL;
@ -1670,12 +1371,6 @@ int usb_enable(usb_dc_status_callback status_cb)
goto out;
}
/* Init transfer slots */
for (i = 0U; i < MAX_NUM_TRANSFERS; i++) {
k_work_init(&usb_dev.transfer[i].work, usb_transfer_work);
k_sem_init(&usb_dev.transfer[i].sem, 1, 1);
}
/* Enable control EP */
ret = usb_dc_ep_enable(USB_CONTROL_OUT_EP0);
if (ret < 0) {

327
subsys/usb/usb_transfer.c Normal file
View file

@ -0,0 +1,327 @@
/*
* Copyright (c) 2018 Linaro
* Copyright (c) 2019 PHYTEC Messtechnik GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <usb/usb_device.h>
#include <logging/log.h>
#include "usb_transfer.h"
LOG_MODULE_REGISTER(usb_transfer, CONFIG_USB_DEVICE_LOG_LEVEL);
#define MAX_NUM_TRANSFERS 4 /** Max number of parallel transfers */
struct usb_transfer_sync_priv {
int tsize;
struct k_sem sem;
};
struct usb_transfer_data {
/** endpoint associated to the transfer */
u8_t ep;
/** Transfer status */
int status;
/** Transfer read/write buffer */
u8_t *buffer;
/** Transfer buffer size */
size_t bsize;
/** Transferred size */
size_t tsize;
/** Transfer callback */
usb_transfer_callback cb;
/** Transfer caller private data */
void *priv;
/** Transfer synchronization semaphore */
struct k_sem sem;
/** Transfer read/write work */
struct k_work work;
/** Transfer flags */
unsigned int flags;
};
static struct usb_transfer_data ut_data[MAX_NUM_TRANSFERS];
/* Transfer management */
static struct usb_transfer_data *usb_ep_get_transfer(u8_t ep)
{
for (int i = 0; i < ARRAY_SIZE(ut_data); i++) {
if (ut_data[i].ep == ep) {
return &ut_data[i];
}
}
return NULL;
}
bool usb_transfer_is_busy(u8_t ep)
{
struct usb_transfer_data *trans = usb_ep_get_transfer(ep);
if (trans && trans->status == -EBUSY) {
return true;
}
return false;
}
static void usb_transfer_work(struct k_work *item)
{
struct usb_transfer_data *trans;
int ret = 0;
u32_t bytes;
u8_t ep;
trans = CONTAINER_OF(item, struct usb_transfer_data, work);
ep = trans->ep;
if (trans->status != -EBUSY) {
/* transfer cancelled or already completed */
goto done;
}
if (trans->flags & USB_TRANS_WRITE) {
if (!trans->bsize) {
if (!(trans->flags & USB_TRANS_NO_ZLP)) {
usb_write(ep, NULL, 0, NULL);
}
trans->status = 0;
goto done;
}
ret = usb_write(ep, trans->buffer, trans->bsize, &bytes);
if (ret) {
LOG_ERR("Transfer error %d", ret);
/* transfer error */
trans->status = -EINVAL;
goto done;
}
trans->buffer += bytes;
trans->bsize -= bytes;
trans->tsize += bytes;
} else {
ret = usb_dc_ep_read_wait(ep, trans->buffer, trans->bsize,
&bytes);
if (ret) {
/* transfer error */
trans->status = -EINVAL;
goto done;
}
trans->buffer += bytes;
trans->bsize -= bytes;
trans->tsize += bytes;
/* ZLP, short-pkt or buffer full */
if (!bytes || (bytes % usb_dc_ep_mps(ep)) || !trans->bsize) {
/* transfer complete */
trans->status = 0;
goto done;
}
/* we expect mote data, clear NAK */
usb_dc_ep_read_continue(ep);
}
done:
if (trans->status != -EBUSY && trans->cb) { /* Transfer complete */
usb_transfer_callback cb = trans->cb;
int tsize = trans->tsize;
void *priv = trans->priv;
if (k_is_in_isr()) {
/* reschedule completion in thread context */
k_work_submit(&trans->work);
return;
}
LOG_DBG("transfer done, ep=%02x, status=%d, size=%zu",
trans->ep, trans->status, trans->tsize);
trans->cb = NULL;
k_sem_give(&trans->sem);
/* Transfer completion callback */
if (trans->status != -ECANCELED) {
cb(ep, tsize, priv);
}
}
}
void usb_transfer_ep_callback(u8_t ep, enum usb_dc_ep_cb_status_code status)
{
struct usb_transfer_data *trans = usb_ep_get_transfer(ep);
if (status != USB_DC_EP_DATA_IN && status != USB_DC_EP_DATA_OUT) {
return;
}
if (!trans) {
if (status == USB_DC_EP_DATA_OUT) {
u32_t bytes;
/* In the unlikely case we receive data while no
* transfer is ongoing, we have to consume the data
* anyway. This is to prevent stucking reception on
* other endpoints (e.g dw driver has only one rx-fifo,
* so drain it).
*/
do {
u8_t data;
usb_dc_ep_read_wait(ep, &data, 1, &bytes);
} while (bytes);
LOG_ERR("RX data lost, no transfer");
}
return;
}
if (!k_is_in_isr() || (status == USB_DC_EP_DATA_OUT)) {
/* If we are not in IRQ context, no need to defer work */
/* Read (out) needs to be done from ep_callback */
usb_transfer_work(&trans->work);
} else {
k_work_submit(&trans->work);
}
}
int usb_transfer(u8_t ep, u8_t *data, size_t dlen, unsigned int flags,
usb_transfer_callback cb, void *cb_data)
{
struct usb_transfer_data *trans = NULL;
int i, key, ret = 0;
LOG_DBG("transfer start, ep=%02x, data=%p, dlen=%zd",
ep, data, dlen);
key = irq_lock();
for (i = 0; i < ARRAY_SIZE(ut_data); i++) {
if (!k_sem_take(&ut_data[i].sem, K_NO_WAIT)) {
trans = &ut_data[i];
break;
}
}
if (!trans) {
LOG_ERR("no transfer slot available");
ret = -ENOMEM;
goto done;
}
if (trans->status == -EBUSY) {
/* A transfer is already ongoing and not completed */
k_sem_give(&trans->sem);
ret = -EBUSY;
goto done;
}
/* Configure new transfer */
trans->ep = ep;
trans->buffer = data;
trans->bsize = dlen;
trans->tsize = 0;
trans->cb = cb;
trans->flags = flags;
trans->priv = cb_data;
trans->status = -EBUSY;
if (usb_dc_ep_mps(ep) && (dlen % usb_dc_ep_mps(ep))) {
/* no need to send ZLP since last packet will be a short one */
trans->flags |= USB_TRANS_NO_ZLP;
}
if (flags & USB_TRANS_WRITE) {
/* start writing first chunk */
k_work_submit(&trans->work);
} else {
/* ready to read, clear NAK */
ret = usb_dc_ep_read_continue(ep);
}
done:
irq_unlock(key);
return ret;
}
void usb_cancel_transfer(u8_t ep)
{
struct usb_transfer_data *trans;
unsigned int key;
key = irq_lock();
trans = usb_ep_get_transfer(ep);
if (!trans) {
goto done;
}
if (trans->status != -EBUSY) {
goto done;
}
trans->status = -ECANCELED;
k_work_submit(&trans->work);
done:
irq_unlock(key);
}
void usb_cancel_transfers(void)
{
for (int i = 0; i < ARRAY_SIZE(ut_data); i++) {
struct usb_transfer_data *trans = &ut_data[i];
unsigned int key;
key = irq_lock();
if (trans->status == -EBUSY) {
trans->status = -ECANCELED;
k_work_submit(&trans->work);
LOG_DBG("Cancel transfer");
}
irq_unlock(key);
}
}
static void usb_transfer_sync_cb(u8_t ep, int size, void *priv)
{
struct usb_transfer_sync_priv *pdata = priv;
pdata->tsize = size;
k_sem_give(&pdata->sem);
}
int usb_transfer_sync(u8_t ep, u8_t *data, size_t dlen, unsigned int flags)
{
struct usb_transfer_sync_priv pdata;
int ret;
k_sem_init(&pdata.sem, 0, 1);
ret = usb_transfer(ep, data, dlen, flags, usb_transfer_sync_cb, &pdata);
if (ret) {
return ret;
}
/* Semaphore will be released by the transfer completion callback */
k_sem_take(&pdata.sem, K_FOREVER);
return pdata.tsize;
}
/* Init transfer slots */
int usb_transfer_init(void)
{
for (int i = 0; i < ARRAY_SIZE(ut_data); i++) {
k_work_init(&ut_data[i].work, usb_transfer_work);
k_sem_init(&ut_data[i].sem, 1, 1);
}
return 0;
}

21
subsys/usb/usb_transfer.h Normal file
View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2019 PHYTEC Messtechnik GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_USB_TRANSFER_H_
#define ZEPHYR_USB_TRANSFER_H_
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Initialize USB transfer data
*
* @return 0 on success, negative errno code on fail.
*/
int usb_transfer_init(void);
#endif /* ZEPHYR_USB_TRANSFER_H_ */