diff --git a/subsys/usb/CMakeLists.txt b/subsys/usb/CMakeLists.txt index e4852fed520..b912eae78e8 100644 --- a/subsys/usb/CMakeLists.txt +++ b/subsys/usb/CMakeLists.txt @@ -6,6 +6,7 @@ if(CONFIG_USB_DEVICE_STACK) zephyr_sources( usb_device.c usb_descriptor.c + usb_transfer.c ) add_subdirectory(class) diff --git a/subsys/usb/usb_device.c b/subsys/usb/usb_device.c index ab8fddd4f76..b3923869c14 100644 --- a/subsys/usb/usb_device.c +++ b/subsys/usb/usb_device.c @@ -76,6 +76,7 @@ LOG_MODULE_REGISTER(usb_device); #include #include +#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) { diff --git a/subsys/usb/usb_transfer.c b/subsys/usb/usb_transfer.c new file mode 100644 index 00000000000..5c48c3e7b82 --- /dev/null +++ b/subsys/usb/usb_transfer.c @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2018 Linaro + * Copyright (c) 2019 PHYTEC Messtechnik GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#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; +} diff --git a/subsys/usb/usb_transfer.h b/subsys/usb/usb_transfer.h new file mode 100644 index 00000000000..c91e7b45f5e --- /dev/null +++ b/subsys/usb/usb_transfer.h @@ -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_ */