blob: 54c42ea8d3a446f5292e2f3ebec9c518c1ace5de [file] [log] [blame]
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/usb/usb_ch9.h>
#include "uhc_common.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(uhc, CONFIG_UHC_DRIVER_LOG_LEVEL);
K_MEM_SLAB_DEFINE_STATIC(uhc_xfer_pool, sizeof(struct uhc_transfer),
CONFIG_UHC_XFER_COUNT, sizeof(void *));
NET_BUF_POOL_VAR_DEFINE(uhc_ep_pool,
CONFIG_UHC_BUF_COUNT, CONFIG_UHC_BUF_POOL_SIZE,
0, NULL);
int uhc_submit_event(const struct device *dev,
const enum uhc_event_type type,
const int status,
struct uhc_transfer *const xfer)
{
struct uhc_data *data = dev->data;
struct uhc_event drv_evt = {
.type = type,
.xfer = xfer,
.status = status,
.dev = dev,
};
if (!uhc_is_initialized(dev)) {
return -EPERM;
}
return data->event_cb(dev, &drv_evt);
}
void uhc_xfer_return(const struct device *dev,
struct uhc_transfer *const xfer,
const int err)
{
sys_dlist_remove(&xfer->node);
xfer->queued = 0;
xfer->claimed = 0;
uhc_submit_event(dev, UHC_EVT_EP_REQUEST, err, xfer);
}
struct uhc_transfer *uhc_xfer_get_next(const struct device *dev)
{
struct uhc_data *data = dev->data;
struct uhc_transfer *xfer;
sys_dnode_t *node;
/* Draft, WIP */
node = sys_dlist_peek_head(&data->ctrl_xfers);
if (node == NULL) {
node = sys_dlist_peek_head(&data->bulk_xfers);
}
return (node == NULL) ? NULL : SYS_DLIST_CONTAINER(node, xfer, node);
}
int uhc_xfer_append(const struct device *dev,
struct uhc_transfer *const xfer)
{
struct uhc_data *data = dev->data;
sys_dlist_append(&data->ctrl_xfers, &xfer->node);
return 0;
}
struct uhc_transfer *uhc_xfer_alloc(const struct device *dev,
const uint8_t addr,
const uint8_t ep,
const uint8_t attrib,
const uint16_t mps,
const uint16_t timeout,
void *const owner)
{
const struct uhc_api *api = dev->api;
struct uhc_transfer *xfer = NULL;
api->lock(dev);
if (!uhc_is_initialized(dev)) {
goto xfer_alloc_error;
}
LOG_DBG("Allocate xfer, ep 0x%02x attrib 0x%02x owner %p",
ep, attrib, owner);
if (k_mem_slab_alloc(&uhc_xfer_pool, (void **)&xfer, K_NO_WAIT)) {
LOG_ERR("Failed to allocate transfer");
goto xfer_alloc_error;
}
memset(xfer, 0, sizeof(struct uhc_transfer));
k_fifo_init(&xfer->queue);
k_fifo_init(&xfer->done);
xfer->addr = addr;
xfer->ep = ep;
xfer->attrib = attrib;
xfer->mps = mps;
xfer->timeout = timeout;
xfer->owner = owner;
xfer_alloc_error:
api->unlock(dev);
return xfer;
}
int uhc_xfer_free(const struct device *dev, struct uhc_transfer *const xfer)
{
const struct uhc_api *api = dev->api;
struct net_buf *buf;
int ret = 0;
api->lock(dev);
if (xfer->queued || xfer->claimed) {
ret = -EBUSY;
LOG_ERR("Transfer is still claimed");
goto xfer_free_error;
}
while (!k_fifo_is_empty(&xfer->queue)) {
buf = net_buf_get(&xfer->queue, K_NO_WAIT);
uhc_xfer_buf_free(dev, buf);
}
while (!k_fifo_is_empty(&xfer->done)) {
buf = net_buf_get(&xfer->done, K_NO_WAIT);
uhc_xfer_buf_free(dev, buf);
}
k_mem_slab_free(&uhc_xfer_pool, (void **)&xfer);
xfer_free_error:
api->unlock(dev);
return ret;
}
struct net_buf *uhc_xfer_buf_alloc(const struct device *dev,
struct uhc_transfer *const xfer,
const size_t size)
{
const struct uhc_api *api = dev->api;
struct net_buf *buf = NULL;
api->lock(dev);
if (!uhc_is_initialized(dev)) {
goto buf_alloc_error;
}
if (xfer->queued || xfer->claimed) {
goto buf_alloc_error;
}
LOG_DBG("Allocate net_buf, ep 0x%02x, size %zd", xfer->ep, size);
buf = net_buf_alloc_len(&uhc_ep_pool, size, K_NO_WAIT);
if (!buf) {
LOG_ERR("Failed to allocate net_buf");
goto buf_alloc_error;
}
if (buf->size < size) {
LOG_ERR("Buffer is smaller than requested");
net_buf_unref(buf);
buf = NULL;
goto buf_alloc_error;
}
k_fifo_put(&xfer->queue, &buf->node);
buf_alloc_error:
api->unlock(dev);
return buf;
}
int uhc_xfer_buf_free(const struct device *dev, struct net_buf *const buf)
{
const struct uhc_api *api = dev->api;
int ret = 0;
api->lock(dev);
net_buf_unref(buf);
api->unlock(dev);
return ret;
}
int uhc_ep_enqueue(const struct device *dev, struct uhc_transfer *const xfer)
{
const struct uhc_api *api = dev->api;
int ret;
api->lock(dev);
if (!uhc_is_initialized(dev)) {
ret = -EPERM;
goto ep_enqueue_error;
}
xfer->claimed = 1;
ret = api->ep_enqueue(dev, xfer);
if (ret) {
xfer->claimed = 0;
}
ep_enqueue_error:
api->unlock(dev);
return ret;
}
int uhc_ep_dequeue(const struct device *dev, struct uhc_transfer *const xfer)
{
const struct uhc_api *api = dev->api;
int ret;
api->lock(dev);
if (!uhc_is_initialized(dev)) {
ret = -EPERM;
goto ep_dequeue_error;
}
ret = api->ep_dequeue(dev, xfer);
ep_dequeue_error:
api->unlock(dev);
return ret;
}
int uhc_enable(const struct device *dev)
{
const struct uhc_api *api = dev->api;
struct uhc_data *data = dev->data;
int ret;
api->lock(dev);
if (!uhc_is_initialized(dev)) {
ret = -EPERM;
goto uhc_enable_error;
}
if (uhc_is_enabled(dev)) {
ret = -EALREADY;
goto uhc_enable_error;
}
ret = api->enable(dev);
if (ret == 0) {
atomic_set_bit(&data->status, UHC_STATUS_ENABLED);
}
uhc_enable_error:
api->unlock(dev);
return ret;
}
int uhc_disable(const struct device *dev)
{
const struct uhc_api *api = dev->api;
struct uhc_data *data = dev->data;
int ret;
api->lock(dev);
if (!uhc_is_enabled(dev)) {
ret = -EALREADY;
goto uhc_disable_error;
}
ret = api->disable(dev);
atomic_clear_bit(&data->status, UHC_STATUS_ENABLED);
uhc_disable_error:
api->unlock(dev);
return ret;
}
int uhc_init(const struct device *dev, uhc_event_cb_t event_cb)
{
const struct uhc_api *api = dev->api;
struct uhc_data *data = dev->data;
int ret;
if (event_cb == NULL) {
return -EINVAL;
}
api->lock(dev);
if (uhc_is_initialized(dev)) {
ret = -EALREADY;
goto uhc_init_error;
}
data->event_cb = event_cb;
sys_dlist_init(&data->ctrl_xfers);
sys_dlist_init(&data->bulk_xfers);
ret = api->init(dev);
if (ret == 0) {
atomic_set_bit(&data->status, UHC_STATUS_INITIALIZED);
}
uhc_init_error:
api->unlock(dev);
return ret;
}
int uhc_shutdown(const struct device *dev)
{
const struct uhc_api *api = dev->api;
struct uhc_data *data = dev->data;
int ret;
api->lock(dev);
if (uhc_is_enabled(dev)) {
ret = -EBUSY;
goto uhc_shutdown_error;
}
if (!uhc_is_initialized(dev)) {
ret = -EALREADY;
goto uhc_shutdown_error;
}
ret = api->shutdown(dev);
atomic_clear_bit(&data->status, UHC_STATUS_INITIALIZED);
uhc_shutdown_error:
api->unlock(dev);
return ret;
}