Files
simtrace2/firmware/libcommon/source/host_communication.c
Oliver Smith f721e69bc1 treewide: remove FSF address
Remove the paragraph about writing to the Free Software Foundation's
mailing address. The FSF has changed addresses in the past, and may do
so again. In 2021 this is not useful, let's rather have a bit less
boilerplate at the start of source files.

Change-Id: Ie0a3b2273383adbb3303faffd6ff96be7f4cae99
2021-12-14 11:47:21 +01:00

197 lines
4.8 KiB
C

/* USB communication methods
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*/
#include "board.h"
#include "llist_irqsafe.h"
#include "usb_buf.h"
#include "utils.h"
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/msgb.h>
#include <errno.h>
/***********************************************************************
* USBD Integration API
***********************************************************************/
/* call-back after (successful?) transfer of a write buffer on IN EP */
static void usb_write_cb(uint8_t *arg, uint8_t status, uint32_t transferred,
uint32_t remaining)
{
struct msgb *msg = (struct msgb *) arg;
struct usb_buffered_ep *bep = msg->dst;
uint16_t ep_size = USBD_GetEndpointSize(bep->ep);
unsigned long x;
TRACE_DEBUG("%s (EP=0x%02x)\r\n", __func__, bep->ep);
if (((msgb_length(msg) % ep_size) == 0) && (transferred == ep_size)) {
/* terminate with ZLP; pass in 'msg' again as 'arg' so we get
* called the second time and proceed with usb_buf_free below */
USBD_Write(bep->ep, 0, 0, (TransferCallback) &usb_write_cb, msg);
return;
}
local_irq_save(x);
bep->in_progress--;
local_irq_restore(x);
TRACE_DEBUG("%u: in_progress=%lu\r\n", bep->ep, bep->in_progress);
if (status != USBD_STATUS_SUCCESS)
TRACE_ERROR("%s error, status=%d\r\n", __func__, status);
usb_buf_free(msg);
}
/* check if the spcified IN endpoint is idle and submit the next buffer from queue */
int usb_refill_to_host(uint8_t ep)
{
struct usb_buffered_ep *bep = usb_get_buf_ep(ep);
struct msgb *msg;
unsigned long x;
int rc;
#if 0
if (bep->out_from_host) {
TRACE_ERROR("EP 0x%02x is not IN\r\n", bep->ep);
return -EINVAL;
}
#endif
local_irq_save(x);
if (bep->in_progress) {
local_irq_restore(x);
return 0;
}
if (llist_empty(&bep->queue)) {
local_irq_restore(x);
return 0;
}
bep->in_progress++;
msg = msgb_dequeue_count(&bep->queue, &bep->queue_len);
local_irq_restore(x);
TRACE_DEBUG("%s (EP=0x%02x), in_progress=%lu\r\n", __func__, ep, bep->in_progress);
msg->dst = bep;
rc = USBD_Write(ep, msgb_data(msg), msgb_length(msg),
(TransferCallback) &usb_write_cb, msg);
if (rc != USBD_STATUS_SUCCESS) {
TRACE_ERROR("%s error %x\r\n", __func__, rc);
/* re-insert to head of queue */
llist_add_irqsafe(&msg->list, &bep->queue);
local_irq_save(x);
bep->in_progress--;
local_irq_restore(x);
TRACE_DEBUG("%02x: in_progress=%lu\r\n", bep->ep, bep->in_progress);
return 0;
}
return 1;
}
/* call-back after (successful?) read transfer of a buffer on OUT EP */
static void usb_read_cb(uint8_t *arg, uint8_t status, uint32_t transferred,
uint32_t remaining)
{
struct msgb *msg = (struct msgb *) arg;
struct usb_buffered_ep *bep = msg->dst;
TRACE_DEBUG("%s (EP=%u, len=%lu, q=%p)\r\n", __func__,
bep->ep, transferred, &bep->queue);
bep->in_progress = 0;
if (status != USBD_STATUS_SUCCESS) {
TRACE_ERROR("%s error, status=%d\r\n", __func__, status);
usb_buf_free(msg);
return;
}
msgb_put(msg, transferred);
llist_add_tail_irqsafe(&msg->list, &bep->queue);
}
/* refill the read queue for data received from host PC on OUT EP, if needed */
int usb_refill_from_host(uint8_t ep)
{
struct usb_buffered_ep *bep = usb_get_buf_ep(ep);
struct msgb *msg;
unsigned long x;
int rc;
#if 0
if (!bep->out_from_host) {
TRACE_ERROR("EP 0x%02x is not OUT\r\n", bep->ep);
return -EINVAL;
}
#endif
if (bep->in_progress)
return 0;
TRACE_DEBUG("%s (EP=0x%02x)\r\n", __func__, bep->ep);
msg = usb_buf_alloc(bep->ep);
if (!msg)
return -ENOMEM;
msg->dst = bep;
msg->l1h = msg->head;
bep->in_progress = 1;
rc = USBD_Read(ep, msg->head, msgb_tailroom(msg),
(TransferCallback) &usb_read_cb, msg);
if (rc != USBD_STATUS_SUCCESS) {
TRACE_ERROR("%s error %d\r\n", __func__, rc);
usb_buf_free(msg);
bep->in_progress = 0;
}
return 1;
}
/* drain any buffers from the queue of the endpoint and release their memory */
int usb_drain_queue(uint8_t ep)
{
struct usb_buffered_ep *bep = usb_get_buf_ep(ep);
struct msgb *msg;
unsigned long x;
int ret = 0;
/* wait until no transfers are in progress anymore and block
* further interrupts */
while (1) {
local_irq_save(x);
if (!bep->in_progress) {
break;
}
local_irq_restore(x);
/* retry */
}
/* free all queued msgbs */
while ((msg = msgb_dequeue_count(&bep->queue, &bep->queue_len))) {
usb_buf_free(msg);
ret++;
}
/* re-enable interrupts and return number of free'd msgbs */
local_irq_restore(x);
return ret;
}